扩展类时的构造函数注入

Posted

技术标签:

【中文标题】扩展类时的构造函数注入【英文标题】:Constructor-Injection when extending a class 【发布时间】:2012-02-28 20:26:19 【问题描述】:

这个问题与 Symfony 2 没有严格的关系,但由于我使用 Symfony 2 组件并且以后可能会使用 Symfony\Component\DependencyInjection\Container 作为 DI-Container,所以它可能是相关的。

我目前正在使用 Symfony 2 中的组件构建一个小型库,例如HttpFoundation,验证器,Yaml。 My Domain Services 都在扩展一个基本的 AbstractService,只提供 Doctrine\ORM\EntityManager 和 Symfony\Component\Validator\Validator 通过构造函数注入,如下所示:

abstract class AbstractService

    protected $em;

    protected $validator;

    /**
     * @param Doctrine\ORM\EntityManager $em
     * @param Symfony\Component\Validator\Validator $validator
     */
    public function __construct(EntityManager $em, Validator $validator)
    
        $this->em = $em;
        $this->validator = $validator;
    

扩展此 AbstractService 的服务类现在可能需要注入附加组件,例如 Symfony\Component\HttpFoundation\Session。至于我这样做:

class MyService extends AbstractService

    /**
     * @var Symfony\Component\HttpFoundation\Session
     */
    protected $session;

    /**
     * @param Symfony\Component\HttpFoundation\Session $session
     * @param Doctrine\ORM\EntityManager $em
     * @param Symfony\Component\Validator\Validator $validator
     */
    public function __construct(Session $session, EntityManager $em, Validator $validator)
    
        parent::__construct($em, $validator);
        $this->session = $session;
    

有没有更优雅的方法来解决这个问题,而不必重申父级的构造函数参数,例如改为使用 Setter-Injection for Session?

正如我所看到的,当我对 Session 使用 Setter-Injection 时,我必须在我的方法中访问它之前添加检查,它是否已经被注入,这是我想要避免的。另一方面,我不想“重复”注入所有服务共享的基本组件。

【问题讨论】:

(旁注) 您应该保持子类中参数的顺序与超类型中的顺序相同。 @Gordon 你的意思是 Session 应该追加而不是前置?你能解释一下为什么吗?在前面添加“新信息”对我来说似乎更自然。 子类型是超类型的特化。您可以像超类型一样创建它,但带有一个附加参数,因此将它放在最后更自然,因为它只会添加到超类型的 ctor 签名中,而不是完全更改它。它提供了一个更可预测的 API。 @Gordon 这是我一直觉得奇怪的事情,如果您有两个具有默认值的参数,但您想添加 没有 的第三个参数一个默认值......放置它的逻辑位置是在其他两个之前 好的!现在我明白他们为什么制作 Laravel 【参考方案1】:

另一种方法是不要从抽象类型扩展您的服务。将抽象类型更改为真正的类,然后将其注入到您的服务中,例如

class MyService
…
    public function __construct(ServiceHelper $serviceHelper)
    
        $this->serviceHelper = $serviceHelper;
    

另一种选择是在需要之前不传递依赖项,例如

class MyService
…
    public function somethingRequiringEntityManager($entityManager)
    
        // do something with EntityManager and members of MyService
    

【讨论】:

我喜欢 ServiceHelper 的想法,但我尽量避免根据需要传递依赖项,因为我可能希望稍后使用 Zend_Soap(_Autodiscover) 使用 Service 类作为 WebService 的基础。跨度> 【参考方案2】:

有没有更优雅的方法来解决这个问题,而不必重申父级的构造函数参数,例如改为使用 Setter-Injection for Session?

好吧,通过使用你提到的 setter-injection。除此之外,我看到了两种可能的解决方案来解决您的直接问题:

    使父构造函数也接受一个 Session 对象,但默认为 null。这对我来说很难看,因为父级实际上并不需要会话对象,如果你有其他实现,这将导致父级构造函数的参数列表很长,所以这实际上不是一个选项。

    传入一个更抽象的对象。无论是该特定类型对象的 ParameterObject,还是只是一个简单的注册表,这都应该有效。再次;这不是可取的,而且当您使用依赖注入时,您可能已经知道原因了。

不过我确实得问一下;使用当前方式的危害在哪里?我实际上没有看到缺点。我会继续前进。如果您发现您在构造函数中使用了更多参数,请考虑一下服务的意图是什么以及它为什么需要它,那么可能会将特定的行为片段转移到您请求的对象中。

【讨论】:

我也没有看到缺点,我只是好奇。只是我想“隐藏”旧的构造函数参数,因为它们与这个类没有直接关系。将行为放入请求的对象是我目前正在研究的事情;就像将 MailTransport 注入到我的 MailMessage 类中,而不是发送消息的服务。【参考方案3】:

你可以对 Session 类使用单例并在任何地方访问它的实例,但如果你想限制它的使用,这可能不是一个选项。 但话又说回来Session::getInstance()->getStuff 似乎与$this->session->getStuff 几乎相同所以回答你的问题 - 我认为这里没有太多选择,你的方法似乎很好。

哦,就像 Gordon 说的:你不应该改变参数的顺序。

【讨论】:

Session 是一个 Symfony\Component\HttpFoundation\Session,它本身不是一个 Singleton。正如我所看到的,应用程序可以选择使用单例实例并将其传递给它使用的服务,但是库应该不知道是否使用单例。例如,当我有多个会话(例如,用于身份验证的专用会话)时会发生什么。 如果您有多个会话,您可以使用一种方法来获取所需的会话,例如 SessionWrapper::getSession('yoursession')->doStuff();虽然我不确定这是否是正确的方法。 虽然这是可行的,但我想避免编写与实际域无关的代码,而是注入最低公分母并让使用库的应用程序关心这一点。跨度> 【参考方案4】:

我在开发 Abstract Controller Bundle 时遇到了同样的问题——当控制器被定义为服务时——最终在 the base class 中使用了 setter 注入。

【讨论】:

以上是关于扩展类时的构造函数注入的主要内容,如果未能解决你的问题,请参考以下文章

依赖注入:在无参数构造函数中实例化具体类时你会失去啥

如何使用 Laravel IoC 将数据库注入构造函数

对于单元测试中没有构造函数的扩展类,Angular 10 依赖注入失败

子父类中的构造函数

react 组件的构造函数

构造函数与析构函数