依赖注入几乎每个类都依赖于其他几个类

时间:2015-02-05 12:13:51

标签: php oop dependency-injection

我在学习PHP OOP时尝试实施最佳实践。我理解这个概念,但对正确的实施有点怀疑。在我试图找出基本的实现原则时,我没有在这段代码中实现DI容器。

结构

  • Db 数据库连接类。

  • 设置类,从db检索设置。

  • 语言类,检索特定语言的信息。

  • 页面类,产品类,客户类等等。

设置类需要 Db 类来检索设置。

语言类需要 Db 设置才能根据数据库中的设置检索信息。

页面课程需要 Db 设置语言。它可能还需要一些其他类。

简化代码

Db.php扩展了PDO

的settings.php

class Settings
{
    /* Database instance */
    protected $db;

    /* Cached settings */
    private $settings   = array();

    public function __construct(Db $db)
    {
        $this->db = $db;
    }

    public function load ()
    {
        $selq = $this->db->query('SELECT setting, value FROM settings');
        $this->settings = $selq->fetchAll();
    }
}

Languages.php

class Languages
{

    public $language;

    protected $db;
    protected $settings;

    private $languages = array();

    public function __construct(Db $db, Settings $settings)
    {
        $this->db = $db;
        $this->settings = $settings;
        // set value for $this->language based on user choice or default settings
        ...
    }

    public function load() 
    {
        $this->languages = array();
        $selq = $this->db->query('SELECT * FROM languages');
        $this->languages = $selq->fetchAll();
    }

}

page.php文件

class Page
{
    protected $db;
    protected $settings;
    protected $language;

    public function __construct(Db $db, Settings $settings, Languages $languages)
    {
        $this->db = $db;
        $this->settings = $settings;
        $this->languages = $languages;
    }

    public function load() 
    {
        // load page info from db with certain settings and in proper language
        ...
    }

}

的config.php

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($db, $settings);
$languages->load();

/* Instantiate page */
$page   = new Page($db, $settings, $languages);

我不喜欢一遍又一遍地注射相同课程的想法。通过这种方式,我将达到这一点,我需要注入10个课程。所以,我的代码从一开始就错了。

也许,更好的方法是执行以下操作:

的config.php

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($settings);
$languages->load();

/* Instantiate page */
$page   = new Page($languages);

因为设置已经可以访问$ db和$ db和$ settings的语言。 但是,通过这种方式,我必须拨打电话,例如$ this-> languages-> settings-> db-> ...

我所有的代码架构似乎都是完全错误的:) 应该如何做?

3 个答案:

答案 0 :(得分:3)

我试着回答我自己的问题,正如我在研究了很多很多材料后所看到的那样。

<强> 1。最佳做法是创建以下对象:

$db = new Db();

$settings = new Settings ($db);

$languages = new Languages ($db, $settings);

// ...

<强> 2。使用DI容器。

如果你不能写一个,请使用现有的。有些人称自己的DI容器不是它们,就像疙瘩(在这个网站上有几篇关于它的帖子)。有些往往更慢,更复杂(Zend,Symfony),然后其他,但也提供更多的功能。如果您正在阅读本文,那么您应该选择更简单的一个,如Aura,Auryn,Dice,PHP-DI(按字母顺序排列)。 同样重要的是要知道,正确的DI容器(我认为)应该具有递归遍历依赖关系的能力,这意味着找到某个对象所需的依赖关系。它们还应提供共享同一对象的能力(如$ db实例)。

第3。在尝试动态创建对象时(如果使用前端控制器和路由),手动注入依赖项会导致很多问题。这就是为什么要看第2点。

请参阅此处的好例子:

https://github.com/rdlowrey/Auryn#user-content-recursive-dependency-instantiation

https://github.com/rdlowrey/Auryn#instance-sharing

要观看的视频:

https://www.youtube.com/watch?v=RlfLCWKxHJ0 (它不是PHP,但尝试了解这个想法)

答案 1 :(得分:1)

有一些想法摆脱那些丑陋的链依赖。想想三个具体的类A,B,C,其中A需要B,B需要C,所以隐式A需要C.这是非常糟糕的软件设计。

,测试更加复杂
  1. 如果想要进行一些集成测试,则需要这三个对象
  2. 如果你想测试A被隔离,那么你需要一个模拟B和禁用ctor或者还要模拟C
  3. 此时你应该停止设计。要将A与C分离,您应该创建一个依赖于B A和B实现的接口。

    自:

    A -> B
    B -> C
    

    我们现在有:

    A -> BI
    B impl BI
    B -> C
    

    我们可以将它们与另一个接口CI完全分离:

    A -> BI
    B impl BI
    B -> CI
    C impl CI
    

    现在我们的具体类是分离的,但是如果我们将它们分开(例如通过DI)它们就连接它们就没有利润了。所以下一步是缩小接口以便例如A类只依赖于只有A需要的方法的BI。

    让我们说这已经完成了,我们需要一个A2类需要B的一些方法。你发现实际上BI对A2来说会很好,因为BI有一些A2需要的方法(我们现在留下C和CI离开):

    A -> BI
    A2 -> BI
    B -> BI
    

    几天后你发现我们的界面BI实际上对于A来说太大了,你想重构BI以保持BI尽可能小。但是你不能重构它,因为A2使用了你想要删除的一些方法。

    这也可能是糟糕的设计。我们现在可以做的是(界面隔离原则):

    A -> BI
    A2 -> BI2
    B impl BI
    B impl BI2
    

    在这种情况下,我们现在可以独立更改接口。无论您的项目使用多少个类,我总是建议您为应用程序的某些部分创建某种类型的创建模式(参考第一个示例)来自实现CI接口的未知实现类的对象。

    现在是真正的OOP。今天你可能有一些过度设计的依赖链来获得CI,但是一夜之间你会想到一个突破性的想法,如何获得CI并以一种你不需要改变使用CI的代码的方式改变它!这很重要。

    我总是说:这一切都是为了隐藏实现(当然是在正确的位置)。

    现在不要开始让每个类都成为一个接口,但是让一些类的耦合发生,然后在你看到需要的地方重构它们。

    应用于你的课程我认为设置是一个吸气/设定怪物。让只需要一些设置的类依赖于设置并不是一个好主意。我认为这样的方式更好:

    Settings -> DB
    Settings impl PageSettings
    Settings impl CustomerSettings
    Settings impl ProductSettings
    Page -> PageSettings
    Customer -> CustomerSettings
    Product -> ProductSettings
    

    我不知道你如何使用你的语言课,但我希望你现在有设计软件的想法。当然还有更多。

答案 2 :(得分:0)

如果在另一个对象中使用一个对象的依赖关系,那么您将在这两个组件之间创建依赖关系。这意味着如果您需要更改Settings类的依赖关系,它会破坏依赖于Settings类的所有依赖关系。

依赖注入库

如果您担心每次都必须手动构建新对象,请查看依赖注入库以处理对象创建(如PimpleAuraPHP DI)。

使用Pimple:

// Define this once in your bootstrap
use Pimple\Container;
$container = new Container()

$container['Db'] = function ($c) {
    return new Db();
};

$container['Settings'] = function ($c) {
    return new Settings($c['Db']);
};

$container['Languages'] = function ($c) {
    return new Languages($c['Db'], $c['Settings']);
};

$container['Page'] = function ($c) {
    return new Page($c['Db'], $c['Settings'], $c['Languages']);
};


// Where ever you have access to your $container you can use this
// (and it knows how to build your object for you every time).
$page = $container['Page'];

使用访问者注入依赖关系

您可以使用访问器函数来设置依赖关系:

class Settings {
    protected $db;

    function setDb(Db $db) {
        $this->db = $db;
    }

    // ...
}

$settings = new Settings();
$settings->setDb(new Db());

AuraPHP DI支持使用访问器进行依赖注入。

use Aura\Di\Container;
use Aura\Di\Factory;

$di = new Container(new Factory());
$di->set('db', new Db());
$di->set('settings', new Settings());
$di->setter['settings']['setDb'] = $di->get('db');

您可以进一步扩展它,并根据您扩展的接口注入,自动注入您不希望在每个类上手动设置的常见依赖项(如PSR-3 Logger)。

use Aura\Di\Container;
use Aura\Di\Factory;
use Psr\Log\LoggerAwareTrait;

class Db {
    use LoggerAwareTrait;
    // ...
}

$di = new Container(new Factory());
$di->set('logger', new MyCustomLogger());
$di->set('db', new Db());
$di->setter['LoggerAwareTrait']['setLogger'] = $di->get('logger');

// $db->logger will contain an instance of MyCustomLogger
// so will any other class that uses LoggerAwareTrait
$db = $di->get('db');

单一责任原则

当然,如果你的类有10个不同的依赖关系,那么问题可能出在设计上。一个班级应该有一个single responsibility

  

可能违反单一责任的课程的症状   原理:

     
      
  • 该类有许多实例变量
  •   
  • 该课程有很多公共方法
  •   
  • 该类的每个方法都使用其他实例变量
  •   
  • 特定任务委托给私有方法
  •   
     

来自Matthias Noback的“Principles of Package Design