Way*_*ina 8 .net dependency-injection inversion-of-control solid-principles
我目前正在尝试学习使用IoC容器的好处并熟悉DI.我已经开始使用StructureMap,因为它看似相当简单但功能强大.
我想验证我对这些概念的理解是否正确.让我们假设一个应用程序中的以下基本类(为简洁起见,省略了详细信息):
public class OrderService : IOrderService
{
private IOrderRepository _repository;
private ITaxCalculator _taxCalculator;
private IShippingCalculator _shippingCalculator;
public OrderService(IOrderRepository repository,
ITaxCalculator taxCalculator,
IShippingCalculator shippingCalculator)
{
this._repository = repository;
this._shippingCalculator = shippingCalculator;
this._taxCalculator = taxCalculator;
}
public IOrder Find(int id) { return this._repository.Find(id); }
}
public class OrderRepository : IOrderRepository
{
public IOrder Find(int id) { // ... }
}
public class StandardShippingCalculator : IShippingCalculator
{
// ...
}
public class StandardTaxCalculator : ITaxCalculator
{
private ITaxSpecification _specification;
public StandardTaxCalculator(ITaxSpecification specification)
{
this._specification = specification;
}
}
Run Code Online (Sandbox Code Playgroud)
首先,依赖倒置原则指出,由于OrderService是一个"高级"模块,它不应该依赖于任何较低级别的实现细节,它应该只引用这些类并且能够要求它们在不知道它在做什么的情况下做他们的事情,消费代码应该负责创建和处理这些预先配置的模块.是对的吗?因此,DI保持这些类松散耦合,使他们不必知道确切的方式对依赖的方法被调用,简单地认为它会被调用,做任何需要做的事情-在OrderService不介意Repository正在查询XML,或使用NHibernate或EF甚至是原始DataSet; 它只知道它可以调用存储库,告诉它找到ID为42的订单,存储库将知道该怎么做.
我的理解是,在这种情况下,IoC容器(StructureMap)通过不强制我们确保手动创建所有这些依赖项并将它们传入来提供一个好处.例如,一个简单应用程序的Main方法使用上面的代码可能有:
static void Main(string[] args)
{
IOrderService service = new OrderService(
new OrderRepository(), new StandardShippingService(),
new StandardTaxService(new StandardTaxSpecification()));
IOrder order = service.Find(42);
// Do something with order...
}
Run Code Online (Sandbox Code Playgroud)
所有的消息都是令人反感的丑陋; 即使我创建了变量,它仍然很难看.使用IoC容器可以避免所有这些,在StructureMap的情况下,它将成为:
static void Main(string[] args)
{
ObjectFactory.Initialize(x =>
{
x.For<IOrderRepository>().Use<OrderRepository>();
x.For<IOrderService>().Use<OrderService>();
x.For<IOrder>().Use<Order>();
x.For<IShippingCalculator>()
.Use<StandardShippingCalculator>();
x.For<ITaxCalculator>().Use<StandardTaxCalculator>();
x.For<ITaxSpecification>()
.Use<StandardTaxSpecification>();
});
IOrderService service =
ObjectFactory.GetInstance<IOrderService>();
IOrder order = service.Find(42);
// do stuff with order...
}
Run Code Online (Sandbox Code Playgroud)
哪个更清洁,更易于维护,如果我正在编写单元测试,那么让我简单地说出一个Mock的具体类.简而言之,它的好处是它可以将所有内容更多地分离到我甚至不必关心的地方(在调用代码中,即)特定类所依赖的内容,我可以使用容器创建一个并让它做这是事情,并确保消费代码只知道它需要什么 - 例如在真实的应用程序中,如果Controller正在调用服务,它不需要知道存储库或计算器或规范,它需要知道的只是使用OrderService对订单执行某些操作.
这个理解是否正确?在那个问题上,有一些我还不确定的事情:
如果您决定使用IoC容器,它是否意味着在应用程序的任何地方使用,只有在您有许多反向依赖关系要解决的地方,或者仅在消费者中?例如,在OrderRepository中,如果我正在使用具体实现并新建订单; 这个类是否也会使用StructureMap获取订单?这可能是一个有点愚蠢的问题,但我看到的所有DI/IoC示例都只关注在消费客户端(例如网页)中使用它,而从不在其他地方使用它.看起来这是一种全有或全无的方法:如果你打算使用IoC容器,那么它随处可见; new SomeObject();在这种情况下,你基本上是替换任何来电ObjectFactory.GetInstance<ISomeObject>();
是否认为每个类(当然可能)都来自一个接口,无论是否需要使用DI/IoC或类似的东西,它被认为是好还是坏?我已经看过许多代码示例,其中每个不是内置类的类都有一个接口,虽然我可以看到这样做的好处和可能的未来证明,但我认为跟随TDD或BDD可能是一个因为使用这些方法通常会告诉你是否需要一个类的接口,但我已经看到并且与许多人交谈,无论是否TDD,他们都认为你永远不应该将对象的类型定义为具体类; 它应该始终是一个接口或抽象类作为底层类型.这似乎是"不必要的复杂性"代码气味的情况,更不用说违反了YAGNI.
你的两个问题都涉及有争议的话题,但我会站在辩论的一边。
如果您决定使用 IoC 容器,它是否意味着在应用程序中的任何地方使用,仅在您有许多反向依赖需要解决的地方使用,还是仅在消费者中使用?
您的顶级应用程序(消费者)是唯一应该了解您的依赖注入框架的组件。您不需要在new整个代码库中进行替换,因为每个对象都应该拥有完成其工作所需的所有依赖项实例(Miško Hevery 的“依赖注入神话:引用传递”最终让我找到了这个家)。
无论是否需要使用 DI/IoC 或类似模拟它的东西,让每个类(当然,在可能的情况下)都从接口派生是好还是坏?
这个问题的其余部分表明您已经知道答案:只构建接口来创建更合适的抽象(比有问题的具体类)或提供一些其他值。