了解IoC,DI和参考方法

时间:2011-10-24 20:11:36

标签: php dependency-injection inversion-of-control

我正在学习依赖注入和控制反转,我想我已经开始明白这是如何工作的:

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

如果这一切都正确,我可以不再使用我的对象中的“参考方法”吗?

这就是我所说的参考方法。说我有两个家庭和家庭成员模特。我发现创建引用与该模型相关的对象的方法非常有用。在下面的示例中,当呼叫$family->members()时,我可以快速访问所有家庭成员。但是,这意味着我的family对象正在实例化family_member类......这不会破坏IoC的规则吗?

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

3 个答案:

答案 0 :(得分:5)

免责声明:我自己就是在学习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吗?更好的设计是拥有另一个对象,例如{{1创建FamilyMapper及其成员。像这样:

Family

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

许多人认为使用依赖注入意味着不使用工厂等。 这是错误的!依赖注入只是关于注入依赖项。您也可以通过将依赖项注入工厂而不是让工厂实例化自己的依赖项来对工厂对象使用依赖注入。

有用的链接:

  1. http://martinfowler.com/articles/injection.html
  2. Does anyone have a good analogy for dependency injection?
  3. How to explain dependency injection to a 5-year-old?

  4. 附加

    再次,在这里拿下一粒盐。

    另请注意,依赖注入依赖注入容器之间存在差异。第一个是注入依赖项的简单概念,而不是让对象自己创建它(这导致非常高的耦合)。我们从上面的例子中看到了这一点。

    后者是处理依赖注入的框架/库的术语,因此您不必手动注入。容器的责任是布线依赖性,因此您不必进行肮脏的工作。我们的想法是定义依赖注入配置,它告诉容器对象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; } } 具有哪些依赖关系,以及如何注入它们。容器读取文档并为您执行注射。这就是像Pimple,SimpleDIC这样的DIC库。

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

    我希望这会有所帮助。

答案 1 :(得分:1)

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

因此,依赖注入和工厂的结合将使您的生活更加轻松。

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不能,反之亦然。

答案 2 :(得分:0)

问题在于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实现都需要抽象来自家庭本身。

对于PHP的特定情况,请考虑检出Symfony,它会大量使用DI模式,或者您可以考虑使用Symfony Dependency Injection framework,这是Symfony的一部分,但可以使用作为一个单独的组件。 Fabien Potencier也就此事写了a nice article

希望我能帮忙!