假设:(1)有一个带有构造函数的类,该类的方法使用注入的对象,例如:
class SomeClass
{
protected $object1;
protected $object2;
public function __construct(
Object1Interface $object1,
Object2Interface $object2
) {
$this->object1 = $object1;
$this->object2 = $object2;
}
// methods that use Object1 and Object2 classes by $this->object1 and $this->object2.
}
(2)那里是同一个类,没有构造函数,但类方法接受Object1
和Object2
作为依赖项,如下所示:
class SomeClass
{
public function doStuff1(Object1Interface $object1)
{// do the stuff}
public function doStuff2(Object2Interface $object2)
{// do the stuff}
}
互联网上有许多倡导第一种变体的例子。
但是这些之间有什么区别?
它们是不同的,并不是真正地相互倡导。
(1)被广泛称为构造函数依赖注入。与setter依赖项注入相比,它是更好的首选形式,而不是(2)。这些依赖关系对使用者是“隐藏的”,并且通常在对象生存期内不会更改。
考虑抽象的http客户端:
$httpClient = new HttpClient(new CurlAdapter());
// or
$httpClient = new HttpClient(new SocketAdapter());
交换适配器不会影响客户端的使用方式:
$response = $httpClient->get($url);
提倡构造函数DI优于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);
在此示例中,我们明确要求登机总线要求票证,并在登机时对票证进行打孔。但是,如果我们要在总线实例化时注入TicketingService,即使在根本没有上车的情况下,我们也会得到更加复杂的依赖关系图。通过服务执行操作,但从不存储它,可以在很大程度上降低复杂性并显着提高可测试性。
这种技术被称为领域驱动设计方面双重分派(不要与混淆双重分派),并广泛使用。
您可能会认为我们本可以在登机之前检查票证并完全消除对票务服务的依赖。好吧,这是另一个主题。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句