"辛格尔顿"工厂,好还是坏?

lee*_*roy 31 singleton factory

我有很多(抽象的)工厂,他们通常被实施为单身人士.

通常是为了方便不必将它们传递给真正与使用或了解这些工厂无关的层.

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

它看起来像是

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

}
Run Code Online (Sandbox Code Playgroud)

有些人闻到了这一点,我只是不确定是什么 - 它可能更像是一个被解雇的全球而不是单身人士.这并不是真的必须只有一个工厂创建ColumnCalculations - 虽然我的程序不需要更多.

这被认为是最佳实践吗?我应该在一些(半)全局AppContext类中填充它们吗?还有别的东西(我还没准备好切换到一些更大的IoC容器,还是转向一下spring.net)?

Mar*_*son 14

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

对于除了普通代码或原型代码之外的任何其他内容,我个人喜欢使用构造函数注入显式地使用控制反转,因为这意味着所有依赖项都被考虑在内并且您没有得到任何"惊喜".编译器不会让你在没有B的情况下实例化A而没有C.单身人士会立即埋葬这些关系 - 你可以实例化没有B的B和没有C的B.当从A到B的调用发生时,你将得到一个空参考例外.

这在测试时尤其令人讨厌,因为您必须通过运行时故障迭代地向后工作.当您测试代码时,您正在使用API​​,就像编码器一样,因此它表明了这种方法的设计问题.构造函数注入确保永远不会发生这种情况 - 所有依赖项都是预先声明的.构造函数注入的缺点是对象图的配置更复杂.通过使用IoC容器可以减轻这种影响.

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


Bil*_*l K 11

拥有一些单身是非常典型的,通常不会有问题 - 许多单身人士会导致一些恼人的代码.

我们刚刚经历了一个情况,我们不得不测试我们重度单身人士的课程.问题是,当你测试类b,它得到类c(单例)时,你无法模拟类c(至少EasyMock不允许我们替换单例类的静态工厂方法.

一个简单的解决方法是为所有单身人士设置"Setters"以进行测试.不是真的推荐.

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

除了测试之外,我很久以前就学会了,当时永远不会有一个给定对象的多个实例; 在下一个版本中,他们经常需要两个,这使得单例比静态类更好 - 至少你可以在单例的getter中添加一个参数并返回第二个而不需要太多的重构(这也是DI做的事情) ).

无论如何,看看DI,你可能真的很开心.


cop*_*pro 6

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

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