正确使用 IoC 容器如何帮助您避免使用工厂?
How does proper use of IoC containers help you to avoid using factories?
很多IoC容器都有'auto factories'的特性,即根据其接口生成抽象工厂的实现。但是,它通常是第二个 class 公民:StructureMap declares only a basic support for the feature, while Simple Injector's author has deliberately omitted auto factories from the library claiming that when applying Dependency Injection correctly, the need for using factories is minimized。这不是我遇到类似观点的唯一地方。
然而,对我来说,工厂似乎是使用 IoC 和编写领域模型时不可或缺的设计元素。比如说,一个简单的虚构示例可以是:
class Order
{
private ITaxProvider _taxProvider;
public decimal Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Cost { get; set; }
public Order(ITaxProvider taxProvider)
{
_taxProvider = taxProvider;
}
public decimal GetProfit()
{
// Don't nitpick on this example, please, it's just to show some
// kind of domain object that depends on some kind of service,
// but also has some logic of its own.
decimal grossProfit = Quantity * PricePerUnit - Cost;
decimal netProfit = grossProfit * (1.0m - _taxProvider.GetTaxRate());
return netProfit;
}
}
在这种情况下,每次我在 MVC 控制器内创建 Order
时,我都需要使用工厂。最终,这很可能适用于任何域对象。即使他们现在可能没有任何依赖关系,也很可能有人会在某个时候请求某些功能根据配置值表现不同,或者某些代码将被重构以分离 classes,然后可以使用 DI 将其解耦,以便在分离中进行测试。
所以我几乎愿意推出工厂来控制每个域对象的初始化。但是比我更有经验的开发人员说我不需要经常使用带有 IoC 容器的工厂。我应该在我的代码设计中更改什么以使工厂变得不必要?
在我看来,域模型永远不应该包含依赖项。它们应该尽可能接近 POCO。我的系统中只有消息可以使用方法注入轻松地从 class 传送到 class。
如评论中所建议的那样,here and here 描述了这些模式。
如果我们让 Order
成为 POCO,我们将如何计算利润?通过提供仅计算利润的服务:
public class ProfitCalculator
{
private readonly ITaxProvider taxProvider;
public ProfitCalculator(ITaxProvider taxProvider)
{
this.taxProvider = taxProvider;
}
public decimal GetProfit(Order order)
{
decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
decimal netProfit = grossProfit * (1.0m - this.taxProvider.GetTaxRate());
return netProfit;
}
}
假设ITaxProvider
可能是单例,ProfitCalculator
也可能是单例。 order 只是一个普通的对象:
class Order
{
public decimal Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Cost { get; set; }
}
这将完全消除对工厂的需求。使用参考博文中描述的基于消息的模式将使您的生活更加轻松。
更新:
正如史蒂文的评论所暗示的那样,这不是唯一的解决方案。除了创建计算器 class,您还可以定义一个将依赖项作为参数的方法。我更喜欢将这样的方法实现为这样的扩展方法:
public static class DomainExtensions
{
public static decimal GetProfit(this Order order, ITaxProvider taxProvider)
{
decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
decimal netProfit = grossProfit * (1.0m - taxProvider.GetTaxRate());
return netProfit;
}
}
很多IoC容器都有'auto factories'的特性,即根据其接口生成抽象工厂的实现。但是,它通常是第二个 class 公民:StructureMap declares only a basic support for the feature, while Simple Injector's author has deliberately omitted auto factories from the library claiming that when applying Dependency Injection correctly, the need for using factories is minimized。这不是我遇到类似观点的唯一地方。
然而,对我来说,工厂似乎是使用 IoC 和编写领域模型时不可或缺的设计元素。比如说,一个简单的虚构示例可以是:
class Order
{
private ITaxProvider _taxProvider;
public decimal Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Cost { get; set; }
public Order(ITaxProvider taxProvider)
{
_taxProvider = taxProvider;
}
public decimal GetProfit()
{
// Don't nitpick on this example, please, it's just to show some
// kind of domain object that depends on some kind of service,
// but also has some logic of its own.
decimal grossProfit = Quantity * PricePerUnit - Cost;
decimal netProfit = grossProfit * (1.0m - _taxProvider.GetTaxRate());
return netProfit;
}
}
在这种情况下,每次我在 MVC 控制器内创建 Order
时,我都需要使用工厂。最终,这很可能适用于任何域对象。即使他们现在可能没有任何依赖关系,也很可能有人会在某个时候请求某些功能根据配置值表现不同,或者某些代码将被重构以分离 classes,然后可以使用 DI 将其解耦,以便在分离中进行测试。
所以我几乎愿意推出工厂来控制每个域对象的初始化。但是比我更有经验的开发人员说我不需要经常使用带有 IoC 容器的工厂。我应该在我的代码设计中更改什么以使工厂变得不必要?
在我看来,域模型永远不应该包含依赖项。它们应该尽可能接近 POCO。我的系统中只有消息可以使用方法注入轻松地从 class 传送到 class。
如评论中所建议的那样,here and here 描述了这些模式。
如果我们让 Order
成为 POCO,我们将如何计算利润?通过提供仅计算利润的服务:
public class ProfitCalculator
{
private readonly ITaxProvider taxProvider;
public ProfitCalculator(ITaxProvider taxProvider)
{
this.taxProvider = taxProvider;
}
public decimal GetProfit(Order order)
{
decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
decimal netProfit = grossProfit * (1.0m - this.taxProvider.GetTaxRate());
return netProfit;
}
}
假设ITaxProvider
可能是单例,ProfitCalculator
也可能是单例。 order 只是一个普通的对象:
class Order
{
public decimal Quantity { get; set; }
public decimal PricePerUnit { get; set; }
public decimal Cost { get; set; }
}
这将完全消除对工厂的需求。使用参考博文中描述的基于消息的模式将使您的生活更加轻松。
更新:
正如史蒂文的评论所暗示的那样,这不是唯一的解决方案。除了创建计算器 class,您还可以定义一个将依赖项作为参数的方法。我更喜欢将这样的方法实现为这样的扩展方法:
public static class DomainExtensions
{
public static decimal GetProfit(this Order order, ITaxProvider taxProvider)
{
decimal grossProfit = order.Quantity * order.PricePerUnit - order.Cost;
decimal netProfit = grossProfit * (1.0m - taxProvider.GetTaxRate());
return netProfit;
}
}