使用IoC将依赖项注入到基类和子类中?

7
如果我的基类通过构造函数依赖注入了一些服务:在子类中声明构造函数时,是否有可能不使用: base(params)
public MyBaseClass
{
   private IServiceA _serviceA;
   private IServiceB _serviceB;
   private IServiceC _serviceC;

   public MyBaseClass(null, null, null)
   public MyBaseClass(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC)
   {
       _serviceA = serviceA;
       _serviceB = serviceB;
       _serviceC = serviceC;
   }
}

还有一个子类,注入了一些额外的依赖项:

public MySubClassA : MyBaseClass
{
   private IServiceD _serviceD;

   public MySubClassA (null, null, null, null)
   public MySubClassA (IServiceA serviceA, IServiceB serviceB, 
                       IServiceC serviceC, IServiceD serviceD)
          : base (serviceA, serviceB, serviceC)
   {
       _serviceD = serviceD;
   }
}

这里的问题是我有多个子类,目前只有10个左右,但这个数字会增加。每次我需要向基类添加另一个依赖项时,都必须逐个检查每个子类并手动将该依赖项添加到那里。这种手动工作让我觉得我的设计存在问题。
因此,是否可能在不在子类的构造函数中声明基类所需服务的情况下声明 MyBaseClassA 的构造函数?例如,使 MyBaseClassA 的构造函数只具有这么简单的代码:
   public MySubClassA (null)
   public MySubClassA (IServiceD serviceD)
   {
       _serviceD = serviceD;
   }

我需要在基类中做哪些修改,才能让依赖注入发生在那里,并且不需要在子类中添加?我正在使用LightInject IoC。


2
你的IoC容器不能注入属性而不是构造函数吗?我认为这样会更简单,特别是当你有深度嵌套类和大量注入时。 - Simon Belanger
2
除了Simon的想法之外,你可以将这些依赖项包装起来(如果你愿意,可以将它们作为依赖项容器),作为另一种解决方案。虽然我更喜欢属性方法。 - Simon Whitehead
@SimonBelanger 应用程序中的所有其他部分都使用参数注入。我记得应用程序的某个部分尝试过属性注入,但遇到了一些问题,现在我已经忘记了 - 我需要重新审视一下。 - JK.
@SimonWhitehead 是的,包装器可能会起作用。我可以尝试创建一个依赖模型,然后只有一个参数传递到基类,如果我需要添加更多依赖项,我只需将它们添加到模型中,所有子类都不需要更改。 - JK.
1
无论如何,拥有这么多的子类肯定是设计上的疏忽(回答你的一个问题)。@SimonWhitehead的选项也更简单,但这样你就会绑定到服务定位器。 - Simon Belanger
显示剩余2条评论
1个回答

17
这份手动工作让我觉得我的设计有问题。很可能是这样的。根据你提供的例子很难具体说明,但通常这种问题是由以下原因之一引起的:
  • 您使用基类来实现横切关注点(如日志记录、事务处理、安全检查、验证等),而不是使用装饰器。
  • 您使用基类来实现共享行为,而不是将该行为放置在可注入的单独组件中。
基类经常被滥用来添加横切关注点到实现中。在这种情况下,基类很快就会成为God object:一个做太多事情并且知道太多的类。它违反了Single Responsibility Principle(SRP),导致它经常变化,变得复杂,并使其难以测试。
那个问题的解决方案是完全删除基类,而是使用多个装饰器代替单个基类。你应该为每个横切关注点编写一个装饰器。这样,每个装饰器都很小,专注于一个目标(单一职责)。通过使用基于通用接口的设计,您可以创建通用装饰器,可以包装一整套在体系结构上相关的类型。例如,一个单一的装饰器可以包装系统中所有用例实现,或者一个装饰器可以包装系统中所有查询并具有特定返回类型的内容。
基类经常被滥用来包含一组不相关的功能,这些功能被多个实现重复使用。而不是将所有这些逻辑放入基类中(使基类变成维护噩梦),应该将此功能提取到多个服务中,实现可以依赖于这些服务。
当你开始朝着这样的设计进行重构时,你经常会发现实现变得依赖很多,这是一种反模式,称为“构造函数过度注入”。构造函数过度注入通常表明一个类违反了SRP(使其复杂且难以测试)。但是将逻辑从基类移动到依赖项并没有使实现更难测试,事实上,具有基类的模型也存在同样的问题,但不同之处在于依赖项被隐藏起来。
在仔细查看实现中的代码时,您经常会看到某种重复的代码模式。多个实现以相同的方式使用相同的依赖项。这表明缺少一种抽象。这些代码及其依赖项可以提取到聚合服务中。聚合服务减少了实现所需的依赖关系,并包装了共同的行为。
使用聚合服务看起来像@SimonWhitehead在评论中谈到的“包装器”,但请注意,聚合服务是关于抽象化依赖和行为。如果您创建一个依赖项的“容器”并通过公共属性公开这些依赖项以供实现使用,则不会减少实现所依赖的依赖项数量,也不会降低类的复杂度,也不会使该实现更易于测试。另一方面,聚合服务确实降低了类的依赖项数量和复杂性,使其更易于理解和测试。
遵循这些规则,在大多数情况下不需要使用基类。

+1 引入横切关注点并使用装饰器解决方案。(实际上应该是 +2) - Fendy
时不时地,我认为清理并创建“上帝类”是个好主意 - 然后我再次看到这个答案,意识到这是一个愚蠢的想法:D - Piotr Kula

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接