【问题标题】:How does proper use of IoC containers help you to avoid using factories?正确使用 IoC 容器如何帮助您避免使用工厂?
【发布时间】:2016-02-10 20:50:42
【问题描述】:

许多 IoC 容器都具有“自动工厂”的功能,可根据其接口生成抽象工厂的实现。然而,它通常是二等公民:StructureMap declares only a basic support 用于该功能,而 Simple Injector 的作者故意从库中省略自动工厂,声称 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;
    }
}

在这种情况下,每次我创建一个Order,比如说,在 MVC 控制器中,我都需要使用一个工厂。最终,这很可能对任何域对象都是正确的。即使它们现在可能没有任何依赖关系,也很可能有人在某些时候会要求某些功能根据配置值表现不同,或者某些代码将被重构为单独的类,然后可能会被解耦使用 DI 可在分离中进行测试。

所以我几乎愿意推出工厂来控制每个域对象的初始化。但比我更有经验的开发人员说我不应该经常将工厂与 IoC 容器一起使用。我应该对我的代码设计进行哪些更改以使工厂变得不必要?

【问题讨论】:

  • 要了解 Steven 将工厂排除在 Simple Injector 之外的原因,您应该从头开始:here
  • 该参数反对返回其他组件的工厂,而不反对任何类型的工厂,例如返回域对象或其他值类型的工厂。退回组件的工厂尤其是容器的“自动工厂”功能提供的工厂类型。
  • 那么您的 TaxProvider 就是这里的工厂?从这个意义上说,存储库也是工厂。他们没有任何问题。
  • @Steven 不,我没有展示工厂,工厂将用于创建Order 的实例。 ITaxProvider 是由 IoC 容器提供的依赖项。这就是需要工厂的原因。
  • 允许创建实体的抽象不称为工厂;这是一个存储库。但是,您犯的错误是您在实体构造函数中注入了组件。这被认为是不好的。您的 IoC 容器不应负责创建实体;它的工作是构建组件。

标签: design-patterns dependency-injection inversion-of-control ioc-container simple-injector


【解决方案1】:

在我看来,域模型永远不应该包含依赖项。它们应该尽可能接近POCO。我的系统中只有消息可以使用方法注入轻松地从一个类传递到另一个类。

正如 cmets 已经建议的那样,这些模式被描述为 herehere

如果我们将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 也可能是单例。订单只是一个普通的对象:

class Order
{
    public decimal Quantity { get; set; }
    public decimal PricePerUnit { get; set; }
    public decimal Cost { get; set; }
}

这将完全消除对工厂的需求。使用参考博文中描述的基于消息的模式将使您的生活更轻松。

更新:

正如 Steven 评论所暗示的,这不是唯一的解决方案。除了创建计算器类之外,您还可以定义一个将依赖项作为参数的方法。我更喜欢将这样的方法实现为这样的扩展方法:

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

【讨论】:

  • 域对象依赖组件是可以的,但不是在构造函数中注入它们,这些依赖关系应该简单地作为方法参数传递,如:GetProfit(ITaxProvider provider)。但是,当使用建议的模式时,真正的域对象将是您的命令对象。
猜你喜欢
  • 1970-01-01
  • 2011-08-12
  • 1970-01-01
  • 2019-04-24
  • 1970-01-01
  • 2011-03-15
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
相关资源
最近更新 更多