了解IoC, DI和引用方法


Understanding IoC, DI and reference methods

我正在学习依赖注入和控制反转的过程中,我想我开始理解这是如何工作的:

  • 对象不应该关心自己的依赖关系的创建
  • 依赖项应该传递给对象(通过构造函数或setter方法)
  • DI容器可以创建具有所有所需依赖项的对象

如果这些都是正确的,我可以不再在我的对象中使用我所谓的"引用方法"吗?

这就是我所说的引用方法。假设我有两个模型分别代表家庭和家庭成员。我发现创建引用与该模型相关的对象的方法非常有帮助。在下面的示例中,当调用$family->members()时,我可以快速访问所有家庭成员。但是,这意味着我的family对象正在实例化family_member类…这不违反国际奥委会的规则吗?

如果family_member类有一个超出family类作用域的依赖项,该怎么办?输入这里将是非常感谢!

<?php
    class family
    {
        public $id;
        public function members()
        {
            // Return an array of family_member objects
        }
    }
    class family_member
    {
        public $family_id;
        public $first_name;
        public $last_name;
        public $age;
    }

免责声明:我只是自己学习DI。对这个答案持保留态度。

依赖注入只是关于注入依赖。如果你的面向对象设计导致Family对象有责任创建Member的实例,那么无论如何,让Family对象创建Member,因为在这种情况下,Member不再被认为是Family 的依赖,而是一个责任。因此:

class Family
{
    /**
     * Constructor.
     * 
     * Since you have decided in your OO design phase that this
     * object should have the responsibility of creating members,
     * Member is no longer a dependency. MySQLi is, since you need
     * it to get the information to create the member. Inject it.
     *
     */
    public function __construct($id, MySQLi $mysqli)
    {
        $this->id = $id;
        $this->mysqli = $mysqli;
    }
    /**
     * Query the database for members data, instantiates them and
     * return them.
     *
     */
    public function getMembers()
    {
        // Do work using MySQLi
    }
}

但是如果你仔细想想, Family真的应该负责创建Member吗?一个更好的设计是让另一个对象,比如FamilyMapper创建Family以及它的成员。这样的:

class FamilyMapper
{
    /**
     * Constructor.
     * 
     * A better OO design, imho is using the DataMapper pattern.
     * The mapper's responsibility is instantiating Family,
     * which means it's going to have to connect to the database,
     * which makes MySQLi its dependency. So we inject it.
     *
     */
    public function __construct(MySQLi $mysqli)
    {
        $this->mysqli = $mysqli;
    }
    public function findByID($familyID)
    {
        // Query database for family and members data
        // Instantiate and return them
    }
}
class Family
{
    /**
     * Constructor.
     * 
     * Family is an object representing a Family and its members,
     * along with methods that *operate* on the data, so Member
     * in this OO design is a dependency. Inject it.
     *
     */
    public function __construct($id, MemberCollection $members)
    {
        $this->id;
        $this->members;
    }
    public function getMembers()
    {
        return $this->members;
    }
}

使用此模式,您的域对象及其方法(可能包含业务逻辑)将与数据访问代码解耦。这就是依赖注入的好处——它迫使你重新考虑你的OO设计,这样你就能得到更干净的代码。

很多人认为使用依赖注入意味着不使用工厂之类的东西。这是错误的!依赖注入只是关于注入依赖。你也可以对工厂对象使用依赖注入,将依赖注入到工厂中,而不是让工厂实例化自己的依赖项。

的有用链接:

  1. http://martinfowler.com/articles/injection.html
  2. 有没有人有一个很好的类比依赖注入?
  3. 如何向一个5岁的孩子解释依赖注入?

添加

同样,对下面的内容持保留态度。

还请注意,依赖注入依赖注入容器是有区别的。第一个是注入依赖关系的简单概念,而不是让对象自己创建依赖关系(这会导致非常高的耦合)。我们可以从上面的例子中看到这一点。

后者是处理依赖注入的框架/库的术语,这样你就不必手工注入了。容器的职责是连接依赖项,这样您就不必做那些繁琐的工作。这个想法是你定义一个依赖注入配置,它告诉容器Foo对象有哪些依赖,以及如何注入它们。容器读取文档并为您执行注入。这就是像Pimple, SimpleDIC这样的DIC库所做的。

你可以比较依赖注入容器和工厂,因为它们都是创建对象,它们的唯一职责是创建对象。虽然工厂通常是专门的(即FamilyMemberFactory创建MemberInterface的实例),但依赖注入容器更通用。有些人说使用依赖注入容器减轻了对工厂的需求,但您应该记住,这意味着您必须创建和维护依赖注入配置文件,这可能是数千行XML/PHP。

虽然您可以只使用依赖注入,但在我看来,在保持其可维护性的同时试图在设计中找到平衡是一种更合理的方法。

因此,依赖注入和工厂的结合将使你的工作更容易。

class family {
    protected $_members;
    public function __construct($members = array()) {
        $this->_members = array_filter($members, function($obj){
            return ($obj instanceof family_member);
        });
    }
    public function addMember() {
        $this->_members[] = $member = $this->_createMember();
        return $member;
    }
    protected function _createMember() {
        return new family_member;
    }
}

工厂模式在某种程度上与控制反转和依赖注入兼容。依赖项注入使对象不必创建依赖项,而工厂模式则允许它创建依赖项的基本实现。最后,它仍然允许您在需要时重写对象依赖项:

class familyExtended extends family {
    protected function _createMember() {
        return new familyExtended_member;
    }
}

这在测试时特别有用:

class familyTestable extends family {
    protected function _createMember() {
        return new family_memberFake;
    }
}

依赖注入很好,但它不能解决所有的设计问题。当然,它们是不可互换的。工厂模式可以做DI不能做的事情,反之亦然。

问题是DI背后的整个思想是Family不应该知道如何创建特定的FamilyMember,例如,您应该有一个IFamilyMember,并且它的实现可能在应用程序的每个模块/层上有所不同。

应该是这样的:

public function GetMembers()
{
    $members = array();
    foreach ( $member in $this->internalMemberList ){
       // get the current implementation of IFamilyMember
       $memberImpl = $diContainer->Resolve("IFamilyMember");
       $memberImpl->Name = $member->Name;
       //.... Not the best example....
    }
    return $members;
}

基本上,一切都归结为组件应该忽略它们的依赖关系,在您的情况下,Family依赖于FamilyMember,因此任何FamilyMember的实现都需要从Family本身抽象出来。

对于PHP的特殊情况,考虑检查Symfony,它大量使用DI模式,或者你可以考虑使用Symfony依赖注入框架,它是Symfony的一部分,但可以作为一个单独的组件使用。此外,Fabien Potencier也写了一篇关于这个问题的好文章。

希望我能帮上忙!