关于工厂:“Singleton”工厂,好还是坏?

“Singleton” factories, ok or bad?

我有很多(抽象的)工厂,它们通常是作为单例实现的。

通常是为了方便不必通过那些与使用或了解这些工厂毫无关系的层。

大多数情况下,我只需要在启动时决定哪个工厂实现代码程序的其余部分,可能通过一些配置

它看起来像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class ColumnCalculationFactory {
  private static ColumnCalculationFactory factory;

 public static void SetFactory(ColumnCalculationFactory f) {
         factory = f;
  }

  public static void Factory() {
         return factory;
  }

 public IPercentCalculation CreatePercentCalculation();
 public IAverageCalculation CreateAverageCalculation();
    ....

}

有些人确实嗅到了这一点,我只是不确定是什么——它可能更像是一个废弃的全球,而不是一个单一的。虽然我的程序不需要更多的计算,但实际上不需要只有一个工厂来创建列计算。

这被认为是最好的做法吗?我应该把这些东西放在某个(半)全局AppContext类中吗?还有什么(我还没准备好换一个更大的ioc容器,或者spring.net,顺便问一下)?


它实际上取决于您正在做什么和应用程序的范围。如果它只是一个相当小的应用程序,而且永远不会超出这个范围,那么您当前的方法可能会很好。对于这些事情,没有普遍的"最佳"实践。虽然我不建议对除无状态叶方法和/或单向调用(如日志记录)以外的任何其他方法使用单例,但将其排除在外"仅仅因为"它是单例并不一定是正确的操作。

对于除琐碎代码或原型代码以外的任何代码,我个人喜欢在构造函数注入中显式地使用控制反转,因为这意味着所有依赖项都被考虑在内,并且您不会得到任何"惊喜"。编译器不会让您实例化没有b的a和没有c的b。singletons会立即隐藏这些关系——您可以实例化没有b的a和没有c的b。当从a到b的调用发生时,您将得到一个空引用异常。

这在测试时尤其烦人,因为您必须通过运行时失败来迭代地向后工作。当您测试代码时,您使用API的方式和其他程序员一样,因此它表明了这种方法的设计问题。构造函数注入确保这永远不会发生——所有依赖项都是预先声明的。构造函数注入的缺点是对象图的配置更复杂。这可以通过使用IOC容器来减轻。

我想我想说的是,如果你考虑使用某种上下文对象和注册表模式,你也可以看看IOC容器。如果你能使用像autofac这样的成熟的免费产品,那么努力推出自己的mutt版本可能是浪费时间。


拥有一些单例代码是非常典型的,通常不会有问题——很多单例代码会导致一些令人恼火的代码。

我们刚刚经历了一种情况,我们必须测试我们的重量级单件课程。问题是,当您测试类B时,它得到类C(单例),您没有办法模拟类C(至少easymock不允许我们替换单例类的静态工厂方法)。

一个简单的解决方法是为所有的单例设置"setter",用于测试目的。不太推荐。

我们尝试的另一件事是让一个类保存所有的单例——一个注册表。这样做非常接近依赖注入,这几乎是您应该使用的。

除了测试之外,我很久以前就知道,当一个给定对象的实例永远都不会超过一个时,在下一个版本中,它们通常需要两个实例,这使得singleton比静态类要好得多——至少您可以向singleton的getter添加一个参数,并返回第二个,而不需要进行太多的重构(即又一次做了DI做的事)。

无论如何,看看迪,你可能真的很高兴。


不,因为你在这里所做的就是创造全球状态。全球国家元首有各种各样的问题,其中一个职能以相当不可见的方式依赖于其他职能的行为。如果一个函数调用了另一个函数,而该函数在结束前忘记存储和恢复factory,那么您就遇到了一个问题,因为如果不将旧值存储在某个地方,您甚至无法取回旧值。你必须添加代码才能做到这一点(从你的代码来看,我猜你使用的是finally语言,这会给你留下更多的错误空间)。更重要的是,如果您最终得到的代码需要在两个工厂之间快速切换两个子对象,那么您必须在每个点编写一个方法调用-您不能在每个子对象中存储状态(好吧,您可以,但是这样您就击败了全局状态的目的[当然,这并不多])。

将工厂类型存储为成员,并将其传递给需要它的新对象的构造函数(或者根据需要创建一个新对象,等等)可能是最有意义的。它还提供了更好的控制——您可以保证由对象A构造的所有对象都经过同一个工厂,或者您可以提供交换工厂的方法。


我建议不要这样做,因为这样很难为调用这些工厂的代码编写合适的单元测试。

Misko Hevery在他的博客上有一篇关于这个的好文章。


Is this considered best practice?

我认为没问题:你说"我的程序不需要更多",因此你实现任何更灵活/抽象的东西可能是你不需要的。


singleton的错误形式是实现工厂方法的等效形式,其中包括:

1
return new CalculationFactory ();

这是很难根除,模仿或包装。

返回在其他地方创建的对象的版本要好得多,尽管与大多数东西一样,也可能被误用或过度使用。