IoC 和构造函数过度注入反模式解析
这个问题是 Jeffery Palermo 关于如何绕过分支代码和依赖注入的帖子的结果 http://jeffreypalermo.com/blog/constructor-over-injection-anti-pattern/
在他的帖子中,Jeffery 有一个类(public class OrderProcessor : IOrderProcessor
),在构造函数上采用 2 个接口。一种是验证器 IOrderValidator
和 IOrderShipper
接口。他的方法代码仅在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我刚刚发布了对 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.
我开会迟到了,但有几点要点...
坚持仅使用一个依赖项的代码分支,有两个分支可供建议:
就像这里提到的其他人一样,重点是打破对具体类的依赖,并使用依赖注入将依赖项与正在使用的某些 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:
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.