假设:(1)有一个带有构造函数的类,其方法使用注入的对象,如:
Object1
和(2)有同一个类,没有构造函数,但类方法接受Object2
和class SomeClass
{
public function doStuff1(Object1Interface $object1)
{// do the stuff}
public function doStuff2(Object2Interface $object2)
{// do the stuff}
}
作为依赖,如下所示:
{{1}}
互联网上有许多提倡第一种变体的例子。
但这些之间的区别是什么?
答案 0 :(得分:3)
他们是不同的,并没有真正提倡一个人。
(1)被广泛称为构造函数依赖注入。它被提倡作为更好的,首选的形式而不是setter依赖注入,而不是(2)。这些依赖关系从消费者“隐藏”,通常在对象生命周期内不会改变。
考虑抽象的http客户端:
$httpClient = new HttpClient(new CurlAdapter());
// or
$httpClient = new HttpClient(new SocketAdapter());
切换适配器不会影响客户端的使用方式:
$response = $httpClient->get($url);
构造函数DI被认为优于setter,因为它强制执行依赖注入。此外,setter通常允许在对象生存期间更改依赖项,改变其行为并打开棘手和难以调试错误的可能性。
$dataContainer = $serviceLocator->get('SomeDataContainer');
$dataContainer->setStorage(new ArrayStorage());
$dataContainer->set('a','b');
$dataContainer->get('a'); // => 'b'
// called somewhere else
{
$serviceLocator
->get('SomeDataContainer')
->setStorage(new RedisStorage());
}
$dataContainer->get('a'); // => 'foobar' WAT
(2)用于不同的用例,通常不与DI重叠。 以这种方式传递依赖关系有多种原因。它们可能是交互界面的一部分,在对象生命周期中经常更改或在通话时决定。依赖关系可能在概念上不属于对象,但仍然需要该特定操作。重要的是,它们不应存储在对象中,并以可能导致副作用的方式使用!
class SomeClass
{
protected $dep;
public function doSomething(DepInterface $dep)
{
// do the stuff
// store dep for later
$this->dep = $dep;
// This is similar to issue with setters but much worse.
// Side effect is not obvious
}
public function doSomethingElse()
{
$this->dep->increment();
}
}
for ($i = 0; $i < 100; $i++) {
$foo->doSomething($bar);
if (rand(0, 100) == $i) {
// represents conditions out of direct control.
// such as conditional or timing errors, alternative flows,
// unanticipated code changes
$foo->doSomethng($baz);
}
$foo->doSomethingElse();
}
// what will be the result?
考虑这个DDD示例解决一些不切实际的问题:
class Bus
{
public function board(Passenger $passenger, TicketingService $tservice)
{
// @todo check bus have seats
// throws exception if ticket is invalid
$tservice->punchPassengerTicket($this, $passenger);
$this->passengers[] = $passenger;
}
}
if ($bus->isIntercity()) {
$ticketingService = new BookingService();
} else {
$ticketingService = new PrepaidCityTransportCardsService();
}
$bus->board($passenger, $ticketingService);
在这个例子中,我们明确强制要求登机巴士需要门票,并在登机时打票。但是如果我们要在Bus实例化中注入TicketingService,即使对于根本没有发生登机的情况,我们也会得到更复杂的依赖图。传递服务以执行操作但从不存储它会降低复杂性,并在这种情况下显着提高可测试性。
此技术在域驱动设计上下文中称为双重调度(不要与Double Dispatch混淆)并广泛使用。
您可能认为我们可以在登机前检查门票并完全取消对票务服务的依赖。嗯,这是另一个话题。
答案 1 :(得分:1)
第一个好处是你总是处于对象“正确”的状态
如果您的点类包含2个变量x和y
第一种方法确保x和y始终存在于类中(不为null),因此方法可以只使用它们。
第二种方法是每种方法在使用之前必须检查x和y是否设置正确
虽然
都有优点和缺点