为什么无参数工厂方法是 Leaky Abstraction?

Why Parameterless factory methods are Leaky Abstraction?

我正在读一本书,上面写着:

// code smell
public interface IProductRepositoryFactory {
   IProductRepository Create();
}

抽象工厂创建的依赖项在概念上应该需要一个运行时值,并且从运行时值到抽象的转换应该是有意义的。 通过指定具有无参数的 IProductRepositoryFactory 抽象 创建方法,让消费者知道给定的实例有更多 服务,它必须处理这个问题。因为 IProductRepository 的另一个实现可能根本不需要多个实例或确定性处置, 因此,你通过抽象工厂泄露了实现细节 无参数创建方法。

我有点疑惑,“给定服务的更多实例”是什么意思,是否意味着多次调用具体工厂的Create方法?这有什么问题吗?即使您的工厂方法确实具有以下参数:

public interface IProductRepositoryFactory {
   IProductRepository Create(string type);
}

如果多次调用具体工厂的 Create 方法,也会有多个实例。那么无参数工厂方法有什么问题呢?它泄漏了什么?

does it mean that you call a concrete Factory's Create method multiple times? what's wrong with that?

好吧,他们的意思是您不知道如何处理产品存储库。是单例吗?那么它应该是一个实际的单例实例,而不是来自工厂。是一次性的吗?好吧,您返回的是 IProductRepository,而不是 IDisposable,因此没有任何迹象表明您应该处理它。

even if you have factory methods that does have parameters [...] if you call a concrete Factory's Create method multiple times there will be multiple instances too

我相信他们的想法是,您将根据在运行之间缓存的参数获取已经构建的实例,因此不涉及处置。

我不确定我是否完全同意他们的想法,但我要说的是,在我看来,您永远不会将这种图案卖给我。使用单例或依赖注入(也取代单例)。

当抽象未能隐藏它应该隐藏的底层实现的细节时,它就是“有漏洞的”。

无参数工厂方法 总是 泄漏是不正确的,因为这当然取决于它 returns 是什么抽象 应该 隐藏,而您引用的作者从未真正指定暴露了哪些应该隐藏的信息细节。

但他说的往往是对的。如果您提供此 IProductRepositoryFactory 方法,那么接收方可以根据需要创建任意数量的 IProductRepository 实例...但是 为什么 接收方 想要制作一个、两个或一堆?如果你通过这个工厂接口,那么选择可能很重要。接收者 必须知道 制作一个实例与制作多个实例所涉及的各种权衡。可能跟缓存、线程池等有关

通常,这接收者不必知道的那种实现细节。

但是,你知道...

在很多情况下,注入看起来很像这种情况的接口实际上是很常见的,而且非常好,这会引起人们对单词定义的争论。

例如,您可以传入接收方将使用的工厂,如下所示:

factory.createDocument().setTitle(title).setContent(content).save();

非常好。有什么不同?好吧,在这种情况下,我们正在创建的文档不是“依赖项”。工厂本身就是依赖项。它提供的服务是创建文档的能力,然后调用者将拥有。这些文件显然是有状态的并且具有身份。这根本不是 Document 抽象应该隐藏的东西,因此这不是一个有漏洞的抽象。

在使用多线程代码时,类似的模式发生了很多。您通常会有一个线程安全的工厂服务,该服务创建 线程安全的对象。

要正确解决这个问题确实需要阅读 full article 以让阅读此处答案的任何读者了解必要的上下文。根据您的特定问题的具体情况,这里对所提出的问题进行一些澄清:

  1. 注入工厂而不是服务本身,将依赖项 (IProductRepository) 的生命周期管理责任推给了消费者 (HomeController)。如果注入依赖项而不是工厂,代理 class 或 IoC 框架可以负责生命周期管理,从而使消费者能够专注于处理依赖项的 API 表面。
  2. 使用代理存储库 class 可以进一步限制泄漏,因为 IDisposable 将不再由 IProductRepository 实施,也不会暴露给消费者,因为代理将管理生命周期。
  3. 工厂的Create 方法意味着可以创建正在交回的任何实现的多个实例。同样,这给消费者带来了额外的和不必要的责任来管理——或者至少在可以在其他地方处理时关心这一点。正如 Matt 的回答中所指出的那样,在某些情况下,即使不是预期的那样,能够从工厂生成多个实例也完全没问题。在所讨论的文章中,实际上是存储库模式的问题以及随之而来的约定使设计变得尴尬;通常,拥有给定类型的存储库的多个实例是没有意义的,但是通过注入工厂而不是工厂实例,代码允许这样做,从而造成泄漏。

总的来说,这里的大部分问题都围绕着这样一个事实,即工厂被作为依赖项而不是依赖项本身注入,这最终需要比消费者端需要更多的依赖项知识。返回抽象的无参数工厂方法进一步加剧了这一点。如果需要提供一些运行时信息以便该工厂决定实例化和返回的具体类型作为抽象,注入工厂会更有意义。就目前而言,它并不是很好的设计,糟糕的设计会导致额外的精神负担。 Create 方法在不接受任何参数的情况下交回接口实例而不是具体实例的事实可能 a) 提出关于为什么工厂会交回一种或另一种类型的实例的问题,因此需要了解如何工厂做出决定,或者 b) 需要知道 IProductRepository 只有一个实现这一事实。这些都不是消费者或利用依赖关系的开发人员真正应该关心的事情。适当的抽象与适当的 IoC 相结合可以减轻这些担忧。