Is it dependency injection and is it a bad practice?

后端 未结 2 593
名媛妹妹
名媛妹妹 2021-02-06 03:18

I have a small framework and I coded it like this. I\'m not sure if it is called dependency injection or not. I don\'t know if it is like a design pattern or not. I also don\'t

2条回答
  •  孤独总比滥情好
    2021-02-06 03:50

    You are almost there.

    Question 1

    No, I don't see it as a valid dependency injection example. It resembles a bit a service locator (because you're injecting the entire container into your services and use it to "locate" dependent services).

    Question 2

    You're making a small confusion between dependency injection and a dependency injection container.

    First of, dependency injection means pushing dependencies into an object at runtime instead of creating/pulling them.

    To exemplify this:

    //hardcoded dependecies
    class BadService
    {
        public function __construct() 
        {
            $this->dep1 = new ConcreteObject1();
            $this->dep2 = new ConcreteObject2();
        }
    }
    

    So in the example above, the BadService makes it imposible to wire other dependencies at runtime because they are already hard pulled into the constructor itself.

    //service locator pattern
    class AlmostGoodService
    {
        public function __construct(Container $container)
        {
            $this->dep1 = $container->getADep1();
            $this->dep2 = $container->getADep2();
        }
    }
    

    In the AlmostGoodService example, we've removed the hard dependencies from the previous example but we are still depending on a specific implementation of our container (meaning that our service is not reusable without providing the implementation for that container). This is the example that matches what you're doing.

    //dependecy injection    
    class GoodService
    {
        public function __construct($dep1, OptionalInterface $dep2)
        {
            $this->dep1 = $dep1;
            $this->dep2 = $dep2;
        }
    }
    

    The GoodService service is not concerned with the creation of it's concrete dependencies and can easily be "wired" at runtime with any dependencies that implement the "protocol" of the $dep1 or the OptionalInterface for the $dep2 (therefore the name of Inversion of Control - the underlying concept behind Dependency Injection).

    The component that does this wiring is called a dependency injection container.

    Now, a dependency injection container, in it's simplest form, is nothing more than an object capable of wiring up your objects at runtime based on some form of configuration.

    I said that you are almost there but there are some problems with your implementation:

    • the wiring should be lazy (you do not want to make all that work in your constructor, as your application would slow down considerably as it grows)
    • you should not pass the entire container ($this) as a dependency because then you fallback to a weaker inversion of control, namely a service locator. You should instead pass the concrete dependencies to your service constructors

    Question 3

    There are some cases when you'll find yourself wanting to pass the entire $container as a dependency to a service (namely controllers or lazy service factories), but generally it will be better to stay away from this practice as it will make your services more reusable and easier to test. When you're feeling that your service has too many dependencies, then that it's a good sign that you're service does too much and it's a good time to split it.

    Prototype Container Implementation

    So, based on my answers above, here is a revised (far from perfect) implementation:

    /* This is the revised engine model */
    class FrameWork_Engine_Model
    {
        function __construct($config)
        {
                $this->config = $cofig; 
        }
    
        public function database()
        {
            require_once('Database.class.php');
            return new Database($this->config['configParams']);
        }
    
        public function bbcode()
        {
            require_once('BBCode.class.php');
            return new BBCode($this->database());
        }
    
        public function image()
        {
            require_once('Image.class.php');
            $this->image = new Image($this->config['extensionName']);
        }
        ....
    
        public function register_controller($shared = true)
        {
            if ($shared && $this->register_controller) {
              return $this->register_controller;
            }
    
            return $this->register_controller = new Register_Controller($this->database(), $thus->image(), $this->bbcode());
        }
     }
    

    Now, to use your services:

    $container = new FrameWork_Engine_Model(); 
    $container->register_controller()->doSomeAction()
    

    What could be improved? Your container should:

    • provide a way to share services - that is, to initialise them only once
    • be lockable - provide a way to lock it after configuration
    • be able to "merge" with other containers - that way your application will be really modular
    • allow optional dependencies
    • allow scopes
    • support tagging services

    Ready to use DI Container Implementations

    All of these are accompanied with clear documentation about Dependency Injection

    • Pimple - PHP 5.3 lightweight DI container
    • Symfony2 DI Container - PHP 5.3 feature full DI container
    • Juice DI - Small PHP 5.2 DI container

提交回复
热议问题