9

我有一个小框架,我像这样编码它。我不确定它是否称为依赖注入。我不知道它是否像一种设计模式。我也不知道并想知道$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参数是一种不好的做法吗?如果是这样,为什么?

附言。我知道在一个主题中问 3 个问题不是 stackoverflow 喜欢的,但我不想复制粘贴整个文本来问他们。

4

2 回答 2

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(因此称为控制反转- 背后的基本概念依赖注入)。

进行这种连接的组件称为依赖注入容器

现在,最简单形式的依赖注入容器只不过是一个能够在运行时基于某种形式的配置连接您的对象的对象。

我说你快到了,但你的实现存在一些问题:

  • 布线应该是懒惰的(你不想在你的构造函数中做所有的工作,因为你的应用程序会随着它的增长而大大减慢)
  • 您不应该将整个容器 ( $this) 作为依赖项传递,因为那样您会退回到较弱的控制反转,即服务定位器。您应该将具体的依赖项传递给您的服务构造函数

问题 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()

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

  • 提供一种共享服务的方式——即只初始化一次
  • 锁定- 提供一种在配置后锁定它的方法
  • 能够与其他容器“合并” - 这样您的应用程序将真正模块化
  • 允许可选依赖
  • 允许范围
  • 支持标记服务

准备使用 DI 容器实现

所有这些都附有关于依赖注入的清晰文档

于 2013-03-10T16:41:59.883 回答
6
  1. FrameWork_Engine_Model是一个注册表(注册表模式)。将注册表作为依赖注入到所有对象中是一种被误解的依赖注入。从技术上讲,它DI,但是您创建了从一切到一切的依赖关系,并且还剥夺了 DI 应该提供的灵活性。
  2. 如果您FrameWork_Engine_Model打算实例化服务并管理它们的依赖项,您可以将其更改为控制反转容器(与 DI 相关的典型模式)
  3. 不,一般不会。

我不会争论您选择的类名以及您的服务和控制器的职责,因为我认为这不在这个问题的范围内。只是一个评论:看起来你的控制器做的太多了。如果你对干净的代码感兴趣,你可能想看看单一职责原则并保持你的控制器“瘦”,将业务逻辑和数据库查询移动到服务层和输出机制,如 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 的全局变量。

于 2013-03-10T16:40:51.723 回答