IoC和构造函数过注入反模式解析

时间:2010-01-20 16:30:45

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

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

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

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

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

在此示例中,我们假设_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>();
}

3 个答案:

答案 0 :(得分:22)

我刚刚发布了rebuttal of Jeffrey Palermos post

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

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

答案 1 :(得分:1)

我参加会议的时间很晚,但有几个快点...

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

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

与此处提到的其他人一样,重点是打破对具体类的依赖,并使用依赖注入将依赖项与使用中的某个IoC容器松散耦合。

通过限制未来发生的技术债务,这使得未来的重构和交换遗留代码变得更加容易。一旦你有一个项目接近500,000行代码,有3000个单元测试,你就会知道为什么IoC如此重要。

答案 2 :(得分:-2)

我认为这里的反模式是过度使用接口。