【问题标题】:Why Parameterless factory methods are Leaky Abstraction? [closed]为什么无参数工厂方法是泄漏抽象? [关闭]
【发布时间】:2021-08-24 01:40:18
【问题描述】:

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

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

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

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

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

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

【问题讨论】:

  • 如果您对此进行更多研究,我认为您将从这个过程中获得更多信息和更有价值的信息。是的,我们可以回答一个问题,并可能给您一个很好的答案,但是这个过程对您来说很慢,而且您在这个过程中丢失了所有其他相关信息。其次,我们无法告诉你作者在极端情况下的想法。此外,这些都是时尚问题,stackoverflow 更多的是关于具体的答案和解决方案,这既不适合也可能更适合软件工程
  • 我认为在评估这段摘录时有两点需要注意。首先是建议特定于给定的示例。这不是关于模式的一般建议,而是关于特定上下文中特定实现的建议。换句话说,重点不在于无参数的Create 方法是all 泄漏抽象。这个例子恰好是一个。其次,这个示例不是 GoF 模式。这种抽象工厂的退化形式很常见,但不满足书中定义的标准。

标签: c# design-patterns factory


【解决方案1】:

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

无参数的工厂方法总是会泄漏是不正确的,因为这当然取决于它返回的抽象应该隐藏什么,以及作者您引用的内容从未真正指定公开哪些信息细节应该保持隐藏。

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

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

但是,你知道...

在许多情况下,注入看起来很像这种情况的接口实际上是很常见且完全没问题的,这会导致对单词的定义大惊小怪。

例如,您可能会传入一个接收器将使用的工厂,如下所示:

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

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

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

【讨论】:

    【解决方案2】:

    这是否意味着您多次调用具体工厂的 Create 方法?这有什么问题?

    嗯,他们的意思是您不知道如何处理产品存储库。是单例吗?那么它应该是一个实际的单例实例,而不是来自工厂。是一次性的吗?好吧,你返回的是 IProductRepository,而不是 IDisposable,所以没有什么建议你应该处理它。

    即使您的工厂方法确实有参数 [...] 如果您多次调用具体工厂的 Create 方法,也会有多个实例

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

    我不确定我是否完全同意他们的想法,但我会说,在我看来,你永远不会把这种模式卖给我。要么使用单例,要么使用依赖注入(也可以取代单例)。

    【讨论】:

    • 文章指出 IProductRepository 实现了 IDisposable。他们应该在文章中有 IProductRepository 的代码进行澄清,但文章中已说明。
    【解决方案3】:

    要正确解决此问题,确实需要阅读full article,以便此处答案的任何读者了解必要的上下文。就您的特定问题的具体情况而言,以下是对提出的问题的一些说明:

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

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

    【讨论】:

    • 非常感谢您的回答。对于第三点,为什么除了对象生命周期管理之外,任何实现的多个实例都是消费者关心的问题?消费者只是调用 Create 并获取一个实例,然后消费者再次调用 Create 以获取第二个实例,这有什么问题?
    • @sydevloper 我根据您的评论详细阐述了第 3 点。请查看更新后的答案。
    猜你喜欢
    • 1970-01-01
    • 2019-05-07
    • 2011-01-05
    • 2014-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-21
    相关资源
    最近更新 更多