构造函数注入过度使用

时间:2012-06-16 21:41:32

标签: c# .net unit-testing dependency-injection repository-pattern

我正在寻找避免构造函数注入过度使用的最佳实践。例如,我有会议实体,其子实体很少,如下所示:

  • 会议
    1. MeetingContacts
    2. MeetingAttendees
    3. MeetingType
    4. 地址
    5. MeetingCompanies
    6. MeetingNotes

MeetingService类如下所示:

public class MeetingService
{
    private readonly IMeetingContactRepository _meetingContactRepository;
    private readonly IMeetingAttendeeRepository _meetingAttendeeRepository;
    private readonly IMeetingTypeRepository _meetingTypeRepository;
    private readonly IAddressRepository _addressRepository;
    private readonly IMeetingCompanyRepository _meetingCompanyRepository;
    private readonly IMeetingNoteRepository _meetingNoteRepository;
    private readonly IMeetingRepositoy _meetingReposity;

    public MeetingService(IMeetingRepositoy meetingReposity, IMeetingContactRepository meetingContactRepository, IMeetingAttendeeRepository meetingAttendeeRepository, 
        IMeetingTypeRepository meetingTypeRepository, IAddressRepository addressRepository, 
        IMeetingCompanyRepository meetingCompanyRepository, IMeetingNoteRepository meetingNoteRepository)
    {
        _meetingReposity = meetingReposity;
        _meetingContactRepository = meetingContactRepository;
        _meetingAttendeeRepository = meetingAttendeeRepository;
        _meetingTypeRepository = meetingTypeRepository;
        _addressRepository = addressRepository;
        _meetingCompanyRepository = meetingCompanyRepository;
        _meetingNoteRepository = meetingNoteRepository;
    }

    public void SaveMeeting(Meeting meeting)
    {
        meetingReposity.Save();
        if(Condition1())
            _meetingContactRepository.Save();
        if(Condition2())
            _meetingAttendeeRepository.Save();
        if(Condition3())
            _meetingTypeRepository.Save();
        if(Condition4())
            _addressRepository.Save();
        if(Condition5())
            _meetingCompanyRepository.Save();
        if(Condition6())
            _meetingNoteRepository.Save();
    }
    //... other methods
}

这里只有七个依赖项,但真正的代码包含更多。我使用了"Dependency Injection Constructor Madness"中描述的不同技术,但我还没有找到如何处理存储库依赖项。

有什么方法可以减少依赖项的数量并保持代码可测试?

4 个答案:

答案 0 :(得分:4)

构造函数过度使用只是一种症状 - 似乎你通过拥有一个知道消息持久性的各种元素的“主”类来近似unit of work,并将它们插入到整体保存中。

缺点是每个存储库通过公开专用的Save方法来传达其他存储库的独立性;但这是不正确的,因为SaveMeeting明确指出存储库不是独立的。

我建议识别或创建存储库使用的类型;这可以集中您的更改,并允许您从一个地方保存它们。示例包括DataContext (LINQ to SQL)ISession (NHibernate)ObjectContext (Entity Framework)

您可以在我之前的答案中找到有关存储库如何工作的更多信息:

Advantage of creating a generic repository vs. specific repository for each object?

拥有存储库后,您将确定它们将采取的操作上下文。这通常映射到单个Web请求:在请求开始时创建公共工作单元的实例,并将其交给所有存储库。在请求结束时,将更改保存在工作单元中,使存储库可以自由地担心 访问哪些数据。

这样可以将所有内容整齐地捕获并保存为一个单元。这与源代码管理系统的工作副本非常相似:您将系统的当前状态拉入本地上下文,使用它,并在完成后保存更改。您不能单独保存每个文件 - 您可以将它们全部保存为离散版本。

答案 1 :(得分:3)

在我上面的评论中稍微扩展一下:

由于这个问题是针对如何管理存储库依赖项,我必须假设MeetingService正在管理某种持久性提交。在过去,当我看到像MeetingService这样的类具有很多依赖关系时,很明显它们做得太多了。所以,你必须问自己,“我的交易边界是什么”。换句话说,您可以进行的最小提交是什么意味着会议已成功保存。

如果答案是在调用meetingReposity.Save();后成功保存了会议,那么就是MeetingService应该管理的所有内容(用于提交)。

其他一切基本上都是会议被保存的副作用(注意现在我们用过去时态说话)。此时,每个其他存储库的事件订阅更有意义。

这也具有将所有条件中的逻辑分离为遵循SRP来处理该逻辑的订户类的良好效果。例如,当 联系人存储库提交的逻辑进行更改时,这变得很重要。

希望这有帮助。

答案 2 :(得分:1)

前三个答案中的每一个都给出了在抽象中处理问题的重要建议和想法。但是,我可能正在阅读上面的示例,但这看起来像是一个聚合根太多的问题,而不是太多的依赖本身。这与缺少存储库注入基础结构的持久性机制或者配置错误有关。

简单地说,联系人,与会者,笔记,& c。应该是会议本身的复合属性(如果仅作为单独管理的联系人,& c。对象/数据的链接);因此,您的持久性机制应该自动保存它们。

听从Bryan Watts的说法“构造函数过度使用只是一种症状”,其他几种可能性:

  • 您的持久性机制应该自动处理会议图的持久性,并且配置错误或缺乏执行此操作的能力(Bryan建议执行此操作的所有三个,我将添加DbContext (EF 4.1+))。在这种情况下,实际上应该只有一个依赖项 - IMeetingRepositoy - 并且它可以处理会议及其组合本身的原子保存。
  • SaveMeeting()不仅保存了与其他对象的链接(联系人,与会者等),而且还保存了这些对象,在这种情况下,我必须同意dtryon {{1}和MeetingService做的远不止名字所暗示,而且他的机制可以缓解它。

答案 3 :(得分:0)

您是否真的需要将存储库功能拆分为多个接口?你需要单独嘲笑它们吗?如果没有,您可以使用更多方法来减少接口。

但是我们假设你的课真的需要那么多的依赖。在这种情况下,您可以:

  • 创建一个提供所有依赖项的配置对象(MeetingServiceBindings)。每个模块可以有一个配置对象,而不仅仅是单个服务。我不认为这个解决方案有什么问题。
  • 使用依赖注入工具,例如NInject。这很简单,您可以在一个地方配置代码中的依赖项,而不需要任何疯狂的XML文件。