【问题标题】:IoC and constructor over-injection anti-pattern resolutionIoC 和构造函数过度注入反模式解析
【发布时间】:2011-01-07 08:42:39
【问题描述】:

这个问题是 Jeffery Palermo 关于如何绕过分支代码和依赖注入 http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/

的帖子的结果

在他的帖子中,Jeffery 有一个类 (public class OrderProcessor : IOrderProcessor),它在构造函数上采用 2 个接口。一个是验证器IOrderValidatorIOrderShipper 接口。他的方法代码在只使用IOrderValidator 接口上的方法后分支,从不使用IOrderShipper 接口上的任何东西。

他建议创建一个工厂,该工厂将调用静态方法来获取接口的委托。他正在重构代码中创建一个新对象,这似乎是不必要的。

我想问题的症结在于我们正在使用 IoC 来构建我们所有的对象,无论它们是否被使用。 如果你用 2 个接口实例化一个对象,并且有代码可以分支到不使用其中一个,你如何处理它?

在这个例子中,我们假设 _validator.Validate(order) 总是返回 false 并且永远不会调用 IOrderShipper.Ship() 方法。

原代码:

public class OrderProcessor : IOrderProcessor
{
    private readonly IOrderValidator _validator;
    private readonly IOrderShipper _shipper;

    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
    {
      _validator = validator;
      _shipper = shipper;
    }

    public SuccessResult Process(Order order)
    {
      bool isValid = _validator.Validate(order);
      if (isValid)
      {
          _shipper.Ship(order);
      }
      return CreateStatus(isValid);
    }

    private SuccessResult CreateStatus(bool isValid)
    {
        return isValid ? SuccessResult.Success : SuccessResult.Failed;
    }
}

public class OrderShipper : IOrderShipper
{
  public OrderShipper()
  {
      Thread.Sleep(TimeSpan.FromMilliseconds(777));
  }

  public void Ship(Order order)
  {
      //ship the order
  }
}

重构代码

public class OrderProcessor : IOrderProcessor
{
    private readonly IOrderValidator _validator;

    public OrderProcessor(IOrderValidator validator)
    {
      _validator = validator;
    }

    public SuccessResult Process(Order order)
    {
      bool isValid = _validator.Validate(order);
      if (isValid)
      {
          IOrderShipper shipper = new OrderShipperFactory().GetDefault();
          shipper.Ship(order);
      }
      return CreateStatus(isValid);
    }

    private SuccessResult CreateStatus(bool isValid)
    {
        return isValid ? SuccessResult.Success : SuccessResult.Failed;
    }
}   

public class OrderShipperFactory
{
    public static Func<IOrderShipper> CreationClosure;
    public IOrderShipper GetDefault()
    {
        return CreationClosure(); //executes closure
    }
}

这里是在启动时配置这个工厂的方法(用于 ASP.NET 的 global.asax):

private static void ConfigureFactories()
{
    OrderShipperFactory.CreationClosure =
        () => ObjectFactory.GetInstance<IOrderShipper>();
}

【问题讨论】:

  • 大概重构后的代码不应该在构造函数中使用 IOrderShipper ......否则重构的意义何在?
  • 在您链接的帖子中,他的重构代码没有在构造函数中使用 IOrderShipper,但在您重构的代码中它确实如此。在这种情况下,我不一定同意 Jeffrey Pallermo,但这是您的实现与他的实现之间的重要区别。
  • 猜我复制的代码不正确。我认为把它们都放在这里会更容易=(更正

标签: c# .net dependency-injection inversion-of-control


【解决方案1】:

我刚刚发布了rebuttal of Jeffrey Palermos post

简而言之,我们不应该让具体的实现细节影响我们的设计。这将违反建筑尺度上的 Liskov 替换原则。

一个更优雅的解决方案让我们通过引入延迟加载的 OrderShipper 来保持设计。

【讨论】:

  • +1 我同意你的反驳。这种反模式对我来说似乎是错误的,因为您的 OrderProcessor 现在依赖于 IOrderShipper,而您没有通知您的消费者。
  • 另一个 +1 提到了Liskov Substitution Principle
【解决方案2】:

我开会要迟到了,但有几个要点...

坚持只使用一个依赖的代码分支,有两个分支建议:

  • 应用 DDD 实践,您将不会有一个依赖于 IOrderValidator 的 OrderProcessor。相反,您应该让 Order() 实体负责自己的验证。或者,坚持使用您的 IOrderValidator,但在实现的 OrderShipper() 中有它的依赖项 - 因为它会返回任何错误代码。
  • 确保注入的依赖项是使用单例方法构建的(并在所使用的 IoC 容器中配置为单例)。这解决了所有内存问题。

就像这里提到的其他人一样,关键是要打破对具体类的依赖,并使用依赖注入将依赖关系松散耦合到一些正在使用的 IoC 容器。

通过限制未来产生的技术债务,这使得未来大规模重构和替换遗留代码变得更加容易。一旦您的项目接近 500,000 行代码,并进行了 3000 次单元测试,您就会直接了解 IoC 为何如此重要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多