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

发布于 2024-08-18 08:38:08 字数 2778 浏览 8 评论 0原文

这个问题是 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>();
}

This question is a result of a post by Jeffery Palermo on how to get around branched code and dependency injection http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/

In his post, Jeffery has a class (public class OrderProcessor : IOrderProcessor) that takes 2 interfaces on the constructor. One is a validator IOrderValidator and an IOrderShipper interface. His method code branches after only using methods on the IOrderValidator interface and never uses anything on the IOrderShipper interface.

He suggests creating a factory that will call a static method to get the delegate of the interface. He is creating a new object in his refactored code which seems unnecessary.

I guess the crux of the issue is we are using IoC to build all our objects regardless if they're being used or not. If you instantiate an object with 2 interfaces and have code that could branch to not use one of them, how do you handle it?

In this example, we assume _validator.Validate(order) always returns false and the IOrderShipper.Ship() method is never called.

Original Code:

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
  }
}

Refactored Code

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
    }
}

And here is the method that configures this factory at start-up time (global.asax for ASP.NET):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

聆听风音 2024-08-25 08:38:08

我刚刚发布了对 Jeffrey Palermos 帖子的反驳

总之,我们不应该让具体的实现细节影响我们的设计。这将违反建筑规模上的里氏替换原则。

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

I just posted a rebuttal of Jeffrey Palermos post.

In short, we should not let concrete implementation details influence our design. That would be violating the Liskov Substitution Principle on the architectural scale.

A more elegant solution lets us keep the design by introducing a Lazy-loading OrderShipper.

梓梦 2024-08-25 08:38:08

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

坚持仅使用一个依赖项的代码分支,有两个分支可供建议:

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

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

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

I'm running late for a meeting, but a few quick points...

Sticking to the code branching of using only one dependency, there are two branches to propose:

  • Applying DDD practices, you would not have an OrderProcessor with a dependency on IOrderValidator. Instead, you'd make the Order() entity be responsible for its own validation. Or, stick to your IOrderValidator, but have its dependency within the OrderShipper() that is implemented - since it will return any error codes.
  • Ensure the dependencies being injected are constructed using a Singleton approach (and configured as Singleton in the IoC container being used). This resolves any memory concerns.

Like someone else that mentioned here, the point is to break the dependency on concrete classes and loosely couple the dependencies using Dependency Injection with some IoC container in use.

This makes future refactoring and swapping out legacy code far easier on a grand scale by limiting the technical debt incurred in the future. Once you have a project near 500,000 lines of code, with 3000 unit tests, you'll know first hand why IoC is so important.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文