从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();
}
}
然后,随着应用程序的发展,我会在注册表中添加新的服务工厂方法,在逻辑上合适的地方创建隔离的接口。
这种做法是否合理?
答案 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,而无需更改任何其他代码!