了解 IoC、DI 和参考方法

发布于 2024-12-11 13:16:42 字数 847 浏览 0 评论 0原文

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

  • 对象不应该关心自己依赖项的创建
  • 依赖项应该传递给对象(通过构造函数或 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;
    }

I'm in the process of learning depenency injection and inversion of control, and I think I'm starting to understand how this works:

  • Objects should not concern themselves with the creation of their own dependencies
  • Dependencies should be passed to the object (via the constructor or setter methods)
  • A DI container can do the work of creating objects with all of their required dependencies

If this is all correct, can I no longer use what I call "reference methods" in my objects?

Here is what I mean by reference methods. Say I have two models for families and family members. I find it very helpful to create methods that reference objects that relate to that model. In the example below, when calling $family->members(), I can quickly gain access to all the family members. But, this would mean that my family object is instantiating family_member classes...and doesn't this break the rules of IoC?

What if the family_member class had a dependency that was outside of the scope of the family class? Input here would be much appriciated!

<?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;
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

盛夏已如深秋| 2024-12-18 13:16:42

免责声明:我只是自己学习 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;
    }
}

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

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

有用的链接:

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

补充

再次强调,对下面的内容持保留态度。

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

后者是处理依赖项注入的框架/库的术语,因此您不必进行手动注入。容器的职责是连接依赖关系,因此您不必做肮脏的工作。这个想法是您定义一个依赖项注入配置,它告诉容器Foo对象有哪些依赖项,以及如何注入它们。容器会读取文档并为您执行注入。这就是 Pimple、SimpleDIC 等 DIC 库所做的事情。

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

我希望这有帮助。

Disclaimer: I'm just learning DI myself. Take the answer with a grain of salt.

Dependency injection is only about injecting dependencies. If your object oriented design results in Family object having the responsibility to create instances of Member, then by all means, have the Family object create the Member, because in that case, Member is no longer considered a dependency of Family, but a responsibility. Therefore:

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
    }
}

But if you think about it, does Family really should have the responsibility of creating Member? A better design is to have another object, such as FamilyMapper create Family along with its members. Like this:

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

Using this pattern, your domain objects, along with their methods (which may contain business logic) will be decoupled from your data access code. That's the good thing about dependency injection - it forces you to rethink your OO design, so that you end up with cleaner code.

Many people think that using dependency injection means not using factories and such. This is wrong! Dependency injection is only about injecting dependencies. You can use dependency injection with factory objects too, by injecting dependencies to the factory instead of having the factory instantiating its own dependency.

Useful links:

  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?

Additions

Again, take the stuff below here with a grain of salt.

Please also note that there is a difference between dependency injection and dependency injection container. The first one is a simple concept of injecting dependencies instead of having objects creating it themselves (which results in very high coupling). We see this from the example above.

The latter is a term for frameworks/libraries that deal with dependency injection so you don't have to do manual injection. The container's responsibility is wiring dependencies so you don't have to do the dirty work. The idea is you define a dependency injection configuration, which tells the container what dependencies Foo object has, and how to inject them. The container reads the documentation and performs the injection for you. This is what DIC libraries like Pimple, SimpleDIC do.

You can compare dependency injection containers with factories, since both are a creational objects, whose sole responsibility is to create objects. While factories are often specialized (i.e. FamilyMemberFactory creating instances of MemberInterface), dependency injection container is more general. Some people say using dependency injection container relieves you of the need for factories, but you should remember that it means you have to create and maintain dependency injection configuration files, which could be thousands of XML/PHP lines.

I hope this helps.

迟月 2024-12-18 13:16:42

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

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

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

While you can go with dependency injection only and all the way, trying to find a balance in your design while keeping it maintainable is a more sane approach, in my opinion.

As such, a combination of dependency injection and factories will make your life much easier.

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

The factory pattern is somewhat compatible with inversion of control and dependency injection. While dependency injection frees your object from creating its dependencies, the factory pattern lets it create a base implementation of a dependency. And in the end, it still allows you to override object dependencies when required:

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

This is especially useful when testing:

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

Dependency injection is great, but it cannot solve all your design problems. Of course, they are not interchangeable. The factory pattern do things DI can't and vice-versa.

柳絮泡泡 2024-12-18 13:16:42

问题在于 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,所以任何< code>FamilyMember 实现需要从 Family 本身中抽象出来。

对于 PHP 的具体情况,请考虑查看 Symfony,它大量使用了 DI 模式,或者您也可以考虑使用 Symfony 依赖注入框架,它是 Symfony 的一部分,但可以用作一个单独的组件。 Fabien Potencier 还针对此事撰写了一篇不错的文章

希望我能帮忙!

The problem is that the whole idea behind DI is that Family should not know how to create a specific FamilyMember, for instance, you should have an IFamilyMember and implementations for it may vary on each module / layer of your application.

Should be something like this:

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

Basically, everything sums up to the idea that components should be ignorants of their dependencies, and in your case, Family depends on FamilyMember, so any FamilyMember implementation needs to be abstracted from the Family itself.

For the specific case of PHP, consider checking out Symfony, which does heavy use of the DI pattern or you can consider using the Symfony Dependency Injection framework, which is a part of Symfony, but can be used as a separate component. Also Fabien Potencier has written a nice article on the matter.

Hope I can help!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文