【问题标题】:Avoid long lists of arguments in constructor service Symfony避免构造函数服务 Symfony 中的长参数列表
【发布时间】:2017-12-28 10:32:26
【问题描述】:

我一直在使用 Symfony 开发我的 Web 应用程序,但我一直遇到一个问题。由于我希望能够正确地对我的服务进行单元测试,因此我的服务构造函数总是过于混乱。

理论用例

假设我需要一个服务,它允许我处理 XML 文件并将其内容保存到数据库中。

<?xml version="1.0" encoding="UTF-8" ?>
<users>
    <user>
        <id>1234</id>
        <username>Example User</username>
        <email>user@example.com</email>
        <usergroup>
            <id>567</id>
            <name>Example User Group</name>
        </usergroup>
        <permissions>
            <item>ALLOWED_TO_CREATE</item>
            <item>ALLOWED_TO_UPDATE</item>
            <item>ALLOWED_TO_DELETE</item>
            <item>ALLOWED_TO_view</item>
        </permissions>
    </user>
</users>

您已经想到了很多需要注入此服务的东西:

  • DomCrawler(读取 XML 文件)
  • UserRepository(获取现有用户)
  • UserGroupRepository(获取现有用户组)
  • PermissionsRepository(获取现有权限)
  • EntityManager(保持刷新新/更新的对象)

我正在使用的真实 XML 文件包含更多数据,这需要我注入更多存储库和其他处理某些逻辑的服务。

解决方案 1:使用 Doctrine 服务

将 Doctrine 服务目录注入到我的服务中,并通过 $doctrine-&gt;getRepository(User::class) 获取存储库

优点

  • 显着减少参数数量

缺点

  • 单元测试变得更加困难,因为我不知道服务访问什么,这使得它们变得无用

解决方案 2:使用 setter 注入

从构造函数中删除所有服务和存储库并创建setter方法,然后在services.yml中调用

services:
  AppBundle\Service\MyImportService:
    calls:
      - [setUserRepository, ['@app.user_repository']]

优点

  • 可以说更容易阅读

缺点

  • 更多代码
  • 服务不再是强制性的,可能需要可选的验证

问题

有哪些解决方案可以使我的服务中的参数列表在可读性和单元测试能力方面更易于维护?

一长串论据甚至被认为是不好的做法吗?

【问题讨论】:

  • 考虑例如将 UserRespository、UserGroupRepository 和 PermissionRepository 组合到某种 UserFinder 服务中。对于您注入的所有单个服务,如果您认为自己正在进行适当的单元测试,我认为您可能是在自欺欺人。一个技巧是将持久化/刷新添加到您的存储库中,这通常避免了直接与实体管理器打交道的需要。
  • 可能还会从域驱动设计人员那里了解聚合根的概念。现在你的脑海里有一个印象,User UserGroup 和 Permission 都是单独的对象,但事实上,在这个特定的设计环境中,你真正拥有的只是一个更复杂的用户对象。

标签: php symfony unit-testing dependency-injection


【解决方案1】:

在你的构造函数中有很多参数是一个名为Constructor Over-Injectioncode smell。这种代码异味通常表明该类承担了过多的责任,这意味着它违反了Single Responsibility Principle (SRP)。违反 SRP 会导致维护问题,因此您应该密切关注它们。

尽管重构为属性注入可能会减少构造函数参数的数量,从而减少构造函数中的混乱程度,但它并没有解决根本问题,即此类变得过于复杂。因此,属性注入不是构造函数过度注入的解决方案。

解决这种代码异味的方法是降低手头类的复杂性。有很多方法可以做到这一点,但一个非常常见的方法是Facade Services refactoring

Facade Service [is] 与 Parameter Objects 密切相关,但主要区别在于 Parameter Object 仅将参数移动到公共根,而 Facade Service 将聚合行为隐藏在新抽象后面强>。虽然外观服务可能会因纯粹的机械重构而开始其生命,但事实证明,提取的行为本身就代表了一个领域概念。

优点:

  • 降低类的复杂性,从而解决根本问题
  • 防止依赖于Doctrine服务(这是Service Locator anti-pattern的一个应用)
  • 为领域语言引入新概念和改进。
  • 防止属性注入(因为属性注入导致Temporal Coupling

缺点:

这些是Mark 和我在我们的书Dependency Injection Principles, Practices, and Patterns 中的主题。例如,Section 6.1 专门讨论了从构造函数过度注入到外观服务或领域事件的重构,而chapters 910 则深入探讨了使用装饰器。

【讨论】:

    猜你喜欢
    • 2021-03-31
    • 2010-09-21
    • 1970-01-01
    • 1970-01-01
    • 2021-02-09
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多