Is it dependency injection and is it a bad practice?

后端 未结 2 586
名媛妹妹
名媛妹妹 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
    0 讨论(0)
  • 2021-02-06 04:03
    1. Your FrameWork_Engine_Model is a registry (Registry Pattern). Injecting the registry as dependency into all objects is kind of a misunderstood dependency injection. Technically it is DI, but you create a dependency from everything to everything and also take away the flexibility that DI should offer.
    2. If your FrameWork_Engine_Model was meant to instantiate services and manage their dependencies, you could change it to an Inversion of Control Container (typical pattern related to DI)
    3. No, not in general.

    I won't argue your choice of class names and the responsibilities of your services and controllers, as I don't think it is within scope of this question. Just a remark: it looks like your controllers do too much. If you are interested in clean code, you might want to have a look at the Single Responsibility Principle and keep your controllers "thin", moving business logic and database queries to a service layer and output mechanisms like bbcode to views.

    So, back to your example and how to change it to a sensible usage of Dependency Injection. A primitive IoC container could look like that:

    public function createRegisterController()
    {
        $controller = new RegisterController();
        $controller->setImage($this->getImageService());
        // ...
        return $controller;
    }
    public function getImageService()
    {
        if ($this->imageService === null) {
            $this->imageService = new Image();
            // inject dependencies of Image here
        }
        return $this->imageService;
    }
    

    The important point here is: Only inject the dependencies that are needed. And don't create a bunch of global variables disguised as DI.

    0 讨论(0)
提交回复
热议问题