这是构造函数注入的理智实现吗?

时间:2009-12-14 12:07:37

标签: php design-patterns oop dependency-injection

my question on service locators开始,我决定使用构造函数注入。请考虑以下代码:

<?php

    interface IAppServiceRegistry {
        public function getDb();
        public function getLogger();
    }

    interface IFooServiceRegistry extends IAppServiceRegistry {
        public function getFooBarBazModel();
    }

    class AppServiceRegistry
        implements IAppServiceRegistry, IFooServiceRegistry
    {
        private $logger;
        private $db;
        private $fooBarBazModel;

        public function getDb() {
            // return db (instantiate if first call)
        }

        public function getLogger() {
            // return logger (instantiate if first call)
        }

        public function getFooBarBazModel() {
            if (!isset($this->fooBarBazModel)) {
                $this->fooBarBazModel = new FooBarBazModel( $this->getDb() );
            }
            return $this->fooBarBazModel;
        }
    }

    // Example client classes:

    /**
     * Depends on db, logger and foomodel.
     */
    class Foo {
        private $db;
        private $logger;
        private $fooModel;

        public function __construct(IFooServiceRegistry $services) {
            $this->db = $services->getDb();
            $this->logger = $services->getLogger();
            $this->fooModel = $services->getFooModel();
        }
    }

    /**
     * Depends on only db and logger.
     */
    class BarBaz {
        private $db;
        private $logger;

        public function __construct(IAppServiceRegistry $services) {
            $this->db = $services->getDb();
            $this->logger = $services->getLogger();
        }
    }

然后,随着应用程序的发展,我会在注册表中添加新的服务工厂方法,在逻辑上合适的地方创建隔离的接口。

这种做法是否合理?

3 个答案:

答案 0 :(得分:4)

虽然我通常不会阅读php,但我认为我理解其中的大部分内容。从技术上讲,它看起来不错,但是你写了

  

随着应用程序的发展,我会在注册表中添加新的服务工厂方法

这往往会损害松散耦合的想法,因为您现在拥有一个专门的服务定位器,而不是通用的服务定位器。

在每个类中注入这样一个服务注册表会对Single Responsibility Principle(SRP)起作用,因为一旦一个类可以访问注册表,就很容易请求另一个依赖项而不是最初设想的,并且你最终会使用God Object

最好将所需的依赖项直接注入需要它们的类中。因此,你的Foo类的构造函数应该使用db,logger和fooModel,而BarBaz类应该只使用db和logger参数。

接下来的问题可能是:如果我需要很多不同的依赖项来执行工作该怎么办?这将需要一个具有大量参数的真正丑陋的构造函数,这与其他众所周知的OO实践相悖。

是的,但是如果您需要很多依赖项,那么您可能违反了SRP并且应该尝试将您的设计拆分为更精细的对象:)

答案 1 :(得分:1)

此实现与您之前向我们展示的服务定位器几乎相同。

一个很好的问题是,在查看对象的类时,您知道对象的所有内容都需要完成它们的工作。在你的情况下,你仍然不知道。

如果Foo需要db,logger和model,你可以通过在构造函数中询问这些内容来明确这一点。

以下是关于此事的好读物:

http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/

答案 2 :(得分:1)

我最近一直在努力解决这类问题。服务定位器与依赖注入。

我同意Mark的观点,即可以根据需要将单个细粒度对象注入构造函数中。正如Mark强调的那样,唯一的缺点是,当你构建一个复杂的对象图时,你不可避免地必须在某处开始。这意味着您的高级对象将注入 lot 服务(对象)。

这方面的简单方法是使用一些东西来为你做艰苦的工作。最重要的例子是谷歌的Guice,在我看来这是一个非常好的方式。可悲的是,它是为Java编写的!有PHP版本;我不确定他们中的任何一个在这一点上非常适合Guice。

我写了一篇更详细的post on this topic;你可能会觉得有趣。它包括依赖注入框架的简单实现。

最重要的是,如果你有一个有很多要求的课程 Foo ,你可以这样创建你的课程:

/**
 * Depends on db, logger and foomodel.
 */
class Foo
{
    private $db;
    private $logger;
    private $fooModel;

    /**
     * (other documentation here)
     * @inject
     */
    public function __construct(IDbService $db, ILoggerService $logger, $iModelService $model)
    {
       // do something
    }
}

如果您想要一个新的 Foo 对象,只需要让依赖注入框架创建一个

$foo = $serviceInjector->getInstance('Foo');

依赖注入器将完成艰苦的工作,确保它注入依赖项。这包括依赖项的任何依赖项,如果这是有意义的。换句话说,它将在树上递归排序。

稍后,当您发现需要 IBarService 对象时,您只需将其添加到Construtor,而无需更改任何其他代码!