【问题标题】:Injecting dependencies into both the base class and subclass with IoC?使用 IoC 将依赖项注入到基类和子类中?
【发布时间】:2013-06-28 02:33:48
【问题描述】:

如果我有一个通过构造函数依赖注入服务的基类:是否可以在不使用: 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。

【问题讨论】:

  • 你的 IoC 容器不能注入属性而不是构造函数吗?我认为它会简单得多,特别是如果你是深度嵌套的类和大量的注入。
  • 除了西蒙的其他想法..您可以围绕这些依赖项(如果您愿意的话,一个依赖项的容器)抛出一个包装器作为另一种解决方案。虽然我更喜欢属性方法。
  • @SimonBelanger 应用程序中的所有其他内容都使用参数注入。我认为在应用程序的某个部分尝试了属性注入,并且有一些我已经忘记的问题 - 我将不得不重新考虑。
  • @SimonWhitehead 是的,一个包装器可能会起作用,我可以尝试创建一个依赖模型,然后只有一个参数进入基类,如果我需要添加更多依赖项,我只需添加将它们添加到模型中,并且所有子类都不需要更改
  • 无论如何,拥有这么多子类绝对是对设计疏忽的暗示(回答您的一个问题)。 @SimonWhitehead 选项也会更简单,但是您将自己绑定到服务定位器。

标签: c# dependency-injection inversion-of-control subclass base-class


【解决方案1】:

这份手工工作让我觉得我的 设计。

可能有。您给出的示例很难具体说明,但通常此类问题是由以下原因之一引起的:

  • 您使用基类来实现横切关注点(例如日志记录、事务处理、安全检查、验证等)而不是装饰器。
  • 您使用基类来实现共享行为,而不是将该行为放在可以注入的单独组件中。

基类经常被滥用来为实现添加横切关注点。在这种情况下,基类很快就变成了God object:一个做的太多,知道的太多的类。它违反了Single Responsibility Principle (SRP),导致它经常更改、变得复杂并且难以测试。

该问题的解决方案是一起删除基类并使用多个decorators 而不是单个基类。您应该为每个横切关注点编写一个装饰器。这样每个装饰器都小而专注,并且只有一个改变的理由(单一职责)。通过使用a design that is based on generic interfaces,您可以创建可以包装一整套与架构相关的类型的通用装饰器。例如,一个装饰器可以包装系统中的所有用例实现,或者一个装饰器可以包装系统中具有特定返回类型的所有查询。

基类经常被滥用以包含一组被多个实现重用的不相关功能。与其将所有这些逻辑放在一个基类中(使基类变成维护噩梦),不如将此功能提取到实现可以依赖的多个服务中。

当您开始针对这种设计进行重构时,您会经常看到实现开始获得许多依赖项,这是一种称为构造函数过度注入的反模式。构造函数过度注入通常表明一个类违反了 SRP(使其复杂且难以测试)。但是将逻辑从基类转移到依赖项并没有使实现更难测试,实际上带有基类的模型也有同样的问题,但不同之处在于依赖项被隐藏了。

仔细查看实现中的代码时,您经常会看到某种重复出现的代码模式。多个实现以相同的方式使用同一组依赖项。这是缺少抽象的信号。可以将此代码及其依赖项提取到aggregate service 中。聚合服务减少了实现所需的依赖数量,并封装了常见行为。

使用聚合服务看起来像 @SimonWhitehead 在 cmets 中所说的“包装器”,但请注意聚合服务是关于抽象依赖关系和行为的。如果您创建依赖项的“容器”并通过公共属性公开这些依赖项以供实现使用它们,那么您并没有降低实现所依赖的依赖项的数量,也没有降低此类的复杂性,并且您没有该实现更易于测试。另一方面,聚合服务确实降低了依赖项的数量和类的复杂性,使其更易于掌握和测试。

当遵循这些规则时,大多数情况下不需要基类。

【讨论】:

  • +1 使用装饰器引入横切关注点和解决方案。 (其实应该是+2)
  • 我不时认为清理并创建“神级”是个好主意 - 然后我再次遇到这个答案,并意识到这是一个愚蠢的想法:D
猜你喜欢
  • 1970-01-01
  • 2011-01-04
  • 1970-01-01
  • 1970-01-01
  • 2012-07-16
  • 2011-12-12
  • 2019-06-25
  • 2016-01-27
  • 2012-03-08
相关资源
最近更新 更多