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

Lis*_*sky 9 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.
    }
 ...
 }
Run Code Online (Sandbox Code Playgroud)
 /* 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.
 }
Run Code Online (Sandbox Code Playgroud)

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

有了这些,我的问题是:

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

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

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

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

cat*_*che 19

你快到了.

问题1

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

问题2

你在依赖注入和依赖注入容器之间做了一点混淆.

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

举例说明:

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

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

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

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

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

GoodService服务不关心它的具体依赖关系的创建,并且可以在运行时轻松地与实现$dep1或者可选接口的"协议"的任何依赖关系"连接" $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());
    }
 }
Run Code Online (Sandbox Code Playgroud)

现在,使用您的服务:

$container = new FrameWork_Engine_Model(); 
$container->register_controller()->doSomeAction()
Run Code Online (Sandbox Code Playgroud)

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

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

准备使用DI容器实现

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

  • +1表示DI和DI/IoC容器之间的差异 (2认同)

Fab*_*ler 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;
}
Run Code Online (Sandbox Code Playgroud)

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