注入的类型
把一个类的依赖进行显式地和必需地“注入”,是一个很好的实践,这可以令类更能复用、更易测试、相对于其他类的藕合度更低。
有若干方法能够把dependency进行注入。每种注入方式各有优缺点,使用服务容器时,它们各自的执行方式也有所不同。
构造器注入 ¶
最常见的方式是构造器注入,通过类的constructor实现。你需要一个构造器参数来接收依赖:
1 2 3 4 5 6 7 8 9 10 11 |
class NewsletterManager
{
protected $mailer;
public function __construct(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
} |
你可以把想要注入的服务写在服务容器的配置里面:
1 2 3 4 5 6 |
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
arguments: ['@my_mailer'] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="Http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<argument type="service" id="my_mailer"/>
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 |
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager',
array(new Reference('my_mailer'))
)); |
对注入的对象进行类型提示(type hinting),意味着你可以确保一个合适的依赖被注入。通过类型提示,如果不合适的依赖被注入,你会立即得到一个清晰的错误信息。如果在类型提示中使用接口而不是类的话,你能做出更有弹性的依赖选择。
使用构造器注入有如下优点:
如果依赖是必需的、类不能无此依赖,那么注入通过构造器注入可以确保该依赖在类被使用时存在,因为类在没有依赖时根本不能被构造。
当对象被实例化时构造器只被调用一次,所以你可以确保依赖在对象的生命周期中不发生改变。
以上优点也确实说明构造器注入不适合与“可选的依赖(非必须的依赖)”一起工作。当类存在继承关系时,这种注入也很困难:如果一个类使用构造器注入,那么继承它和覆写该构造器时,会变得充满不确定性。
Setter注入 ¶
另一个可能的注入方式是添加一个setter方法,用来接收依赖:
1 2 3 4 5 6 7 8 9 10 11 |
class NewsletterManager
{
protected $mailer;
public function setMailer(\Mailer $mailer)
{
$this->mailer = $mailer;
}
// ...
} |
1 2 3 4 5 6 7 |
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
calls:
- [setMailer, ['@my_mailer']] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<call method="setMailer">
<argument type="service" id="my_mailer" />
</call>
</service>
</services>
</container> |
1 2 3 4 5 6 7 |
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->addMethodCall('setMailer', array(new Reference('my_mailer'))); |
此时的优点有:
Setter注入可以很好地与可选依赖一起工作。如果你不需要这个依赖,你可以不调用setter方法。
你可以多次调用setter。当该方法要将依赖添加到一个collection(数组)中时特别有用。然后你即拥有一个可变数量的依赖。
缺点在于:
相较于服务在构造时注入,setter可以被调用多次,所以你不能确定依赖在类对象的生命周期之内“不被取代”。(除非在setter方法中显式地添加判断“setter是否被调用过”)
你不能确定Setter是否被调用过,所以你需要添加校验,判断该类的“必要依赖”是否被注入。
属性注入 ¶
另一个可能的方式是对类的public公共属性进行注入:
1 2 3 4 5 6 |
class NewsletterManager
{
public $mailer;
// ...
} |
1 2 3 4 5 6 7 |
services:
my_mailer:
# ...
newsletter_manager:
class: NewsletterManager
properties:
mailer: '@my_mailer' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="my_mailer">
<!-- ... -->
</service>
<service id="newsletter_manager" class="NewsletterManager">
<property name="mailer" type="service" id="my_mailer" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 |
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$container->setDefinition('my_mailer', ...);
$container->setDefinition('newsletter_manager', new Definition(
'NewsletterManager'
))->setProperty('mailer', new Reference('my_mailer')); |
属性注入基本只有缺点,它类似setter注入但是有以下严重问题:
你完全不能控制何时依赖会被设置,它可以在类对象的生命周期内的任何时间点被改变。
无法应用“类型提示”,所以你不能确定到底哪个依赖被注入,除非在类的代码中写入显式的判断,以在使用该依赖之前,对其实例化的对象进行测试。
但是,在服务容器中知道有这样一种注入方式也是有用的,特别是在当你要操作一些你难以控制的代码时,比如第三方的类库,它可能会使用public属性来接收dependency。