37

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

通常是为了方便,不必让他们通过那些真正与使用或了解这些工厂无关的层。

大多数时候我只需要在启动时决定其余代码程序的工厂实现,也许通过一些配置

它看起来像

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();
    ....

}

有些东西确实闻到了这个味道,我只是不确定是什么——它可能更像是一个不合时宜的全球而不是一个单身人士。并不是真的只有一个工厂可以创建 ColumnCalculations——尽管我的程序不需要更多。

这被认为是最佳做法吗?我是否应该将这些东西放在一些(半)全局 AppContext 类中?别的东西(我还没有准备好切换到一些更大的 IoC 容器,或者 spring.net 还顺便说一句)?

4

6 回答 6

17

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

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

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

我想我想说的是,如果你已经到了考虑使用某种上下文对象和注册表模式的地步,你不妨看看 IoC 容器。当您可以使用像 Autofac 这样成熟的免费产品时,努力推出自己的 mutt 版本可能是浪费时间。

于 2009-08-16T20:18:25.617 回答
12

有几个单例是很典型的,通常不会有问题——很多单例会导致一些烦人的代码。

我们刚刚经历了一种情况,我们必须测试我们大量单例负载的类。问题是当你在测试 b 类时,它得到了 c 类(单例),你无法模拟出 c 类(至少 EasyMock 不允许我们替换单例类的静态工厂方法。

一个简单的解决方法是为所有单例设置“Setters”以进行测试。不是很推荐。

我们尝试的另一件事是创建一个包含所有单例的类——一个注册表。这样做非常接近依赖注入,这几乎肯定是你应该使用的。

除了测试之外,我很久以前就了解到,给定对象的实例永远不会超过一个;在下一个版本中,他们通常需要两个,这使得单例比静态类好得多——至少你可以在单例的 getter 中添加一个参数并返回第二个而不需要太多重构(这再次只是做 DI 所做的事情)。

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

于 2009-08-16T19:42:18.420 回答
6

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

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

于 2009-08-16T19:29:09.697 回答
4

这被认为是最佳实践吗?

我认为没有问题:您说“我的程序不需要更多”,因此您实现任何更灵活/抽象的东西可能是You Ain't Gonna Need It的情况。

于 2009-08-16T19:26:57.270 回答
4

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

Misko Hevery在他的博客上有一篇很好的文章。

于 2009-08-16T19:33:20.173 回答
1

单例的不良形式是实现与您的工厂方法等效的形式:

return new CalculationFactory ();

这更难存根、模拟或包装。

返回在别处创建的对象的版本要好得多,尽管像大多数东西一样,可能会被误用或过度使用。

于 2009-08-16T22:46:46.540 回答