是依赖注入,这是一个不好的做法?

时间:2013-03-10 15:27:13

标签: php oop dependency-injection

我有一个小框架,我这样编码。我不确定它是否被称为依赖注入。我不知道它是否像设计模式。我也不知道并想知道将$this作为参数传递是一种不好的做法。

看看这个; (不是一个有效的例子,只是将这些代码写入浏览器进行解释。)

/* This is engine model */
require_once('Database.class.php');
require_once('Image.class.php');
require_once('Misc.class.php');
require_once('BBCode.class.php');

class FrameWork_Engine_Model
{
    public $database, $config, $misc, $bbcode, $controller, $image;

    function __construct($config)
    {
            $this->database = new Database($configParams);
            $this->image = new Image($this);
            $this->misc = new Misc($this);
            $this->bbcode = new BBCode($this);
            $this->controller = new Controller($this); //here I call Register controller depending on routing, in this case, register controller.
    }
 ...
 }

 /* This is register controller */
 class Register extends Base_Controller
 {
       /*I can access anything over Engine Model in my controllers */
       $this->engine->database->query(); //I access database model
       $this->engine->bbcode->tag('you'); //I access bbcode model
       $this->engine->image->sanitizeUploadedFile(); //I access image model

       //etc. I can access others models like this.
 }

基本上,我的控制器可以通过引擎模型访问任何模型。我相信dependency injection is all about injecting dependencies into controllers?就像,我的注册控制器需要数据库模型,路由模型和模板模型才能工作。这里有它依赖的一切。我错了吗?

有了这些说法,我的问题是:

  1. 它是一个有效的依赖注入示例吗?如果不是,它是什么?它在设计模式中是否有名称?

  2. 如果它与依赖注入无关,那么DI需要做哪些更改?

  3. 在新创建的类上传递$this参数是不是很糟糕?如果是这样,为什么?

  4. 聚苯乙烯。我知道在一个主题中问3个问题不是stackoverflow喜欢的东西,但我不想复制粘贴整个文本来问他们。

2 个答案:

答案 0 :(得分:19)

你快到了。

问题1

不,我不认为它是一个有效的依赖注入示例。它类似于服务定位器(因为您将整个容器注入到服务中并使用它来“定位”相关服务)。

问题2

您在依赖注入和依赖注入容器之间存在一点混淆。

首先,依赖注入意味着在运行时将依赖项推送到对象中,而不是创建/拉取它们。

举例说明:

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

因此,在上面的示例中,BadService使得在运行时连接其他依赖项变得不可能,因为它们已经很难被引入构造函数本身。

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

AlmostGoodService示例中,我们已从前一个示例中删除了硬依赖项,但我们仍依赖于容器的特定实现(这意味着如果不为该容器提供实现,我们的服务就不可重用)。这是与你正在做的事情相匹配的例子。

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

GoodService服务不关心它的具体依赖关系的创建,并且可以在运行时轻松地与任何实现$dep1或者OptionalInterface的“协议”的依赖关系“连线”。 $dep2(因此Inversion of Control的名称 - 依赖注入背后的基本概念)。

执行此连线的组件称为dependency injection container

现在,一个dependency injection container,最简单的形式,只不过是一个能够在运行时基于某种形式的配置连接对象的对象。

我说你差不多了,但是你的实施存在一些问题:

  • 接线应该是懒惰的(你不想在你的构造函数中完成所有工作,因为你的应用程序会随着它的增长而大幅减速)。
  • 您不应将整个容器($this)作为依赖项传递,因为这样您就会回退到较弱的inversion of control,即service locator。您应该将具体的依赖项传递给服务构造函数

问题3

在某些情况下,您会发现自己想要将整个$container作为依赖项传递给服务(即控制器或懒惰的服务工厂),但通常最好远离这种做法因为它将使您的服务更可重用,更容易测试。如果你觉得你的服务有太多的依赖关系,那么这是一个好的迹象,表明你的服务做得太多了,现在是拆分服务的好时机。

原型容器实施

因此,根据我上面的答案,这是一个修订的(远非完美的)实现:

/* 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());
    }
 }

现在,要使用您的服务:

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

有什么可以改进的?你的容器应该:

  • 提供了一种共享服务的方法 - 即只初始化一次
  • be lockable - 提供一种在配置后锁定它的方法
  • 能够与其他容器“合并” - 这样您的应用程序将真正模块化
  • 允许optional dependencies
  • 允许scopes
  • 支持tagging services

准备使用DI容器实施

所有这些都附有关于Dependency Injection

的明确文档

答案 1 :(得分:6)

  1. 您的FrameWork_Engine_Model是注册表(Registry Pattern)。将注册表作为依赖项注入所有对象是一种误解的依赖注入。从技术上讲, DI,但是您可以创建从一切到的依赖,并且还会消除DI应该提供的灵活性。
  2. 如果您的FrameWork_Engine_Model旨在实例化服务并管理其依赖关系,则可以将其更改为Inversion of Control Container(与DI相关的典型模式)
  3. 不,不是一般的。
  4. 我不会争论你选择的班级名称以及你的服务和管理员的职责,因为我认为这不属于这个问题的范围。只是一句话:看起来你的控制器做得太多了。如果您对干净的代码感兴趣,可能需要查看Single Responsibility Principle并保持控制器“瘦”,将业务逻辑和数据库查询移动到服务层,并将bbcode等输出机制发送到视图。

    因此,回到您的示例以及如何将其更改为依赖注入的合理用法。原始的IoC容器看起来像这样:

    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;
    }
    

    重要的一点是:只注入所需的依赖项。并且不要创建一堆伪装成DI的全局变量。