繁体   English   中英

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

[英]Is this a sane implementation of constructor injection?

我对服务定位器提出问题之后, 决定改用构造函数注入。 请考虑以下代码:

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

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

这种方法理智吗?

尽管我通常不阅读php,但我认为我大部分都可以理解。 从技术上讲,它看起来不错,但随后您编写

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

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

向每个类中注入这样的服务注册表违反了单一职责原则 (SRP),因为一旦类可以访问注册表,就很难请求比最初设想的又一个依赖,并且最终将得到God Object

最好将所需的依赖项直接注入需要它们的类中。 因此,您的Foo类的构造函数应采用db,记录器和fooModel,而BarBaz类应仅采用db和记录器参数。

接下来的问题可能是:如果我需要很多不同的依赖项来执行工作该怎么办? 这将需要一个带有许多参数的真正丑陋的构造函数,这与其他著名的OO做法背道而驰。

的确如此,但是如果您需要大量依赖关系,则可能违反了SRP,应尝试将设计拆分为更细粒度的对象:)

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

要问的一个好问题是,在查看对象的类时,是否了解对象的所有事项才能完成其工作。 就您而言,您仍然不知道。

如果Foo需要数据库,记录器和模型,则可以通过在构造函数中进行查询来弄清楚这一点。

这是一本很好的文章:

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

我最近一直在努力解决这种问题。 服务定位器与掺杂注入。

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

解决此问题的简单方法是使用某些方法为您完成艰苦的工作。 最好的例子是Google的Guice,在我看来,这是处理事情的一种非常好的方法。 可悲的是它是为Java编写的! 有关于的PHP版本; 我不确定在这一点上他们中的任何一个都能达到Guice的标准。

我已经写了一篇有关该主题文章,其中有更详细的内容。 您可能会发现有趣。 它包括依赖项注入框架的简单实现。

底线是,如果您有一个具有许多要求的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中,而无需更改任何其他代码!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM