“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做的事)。
无论如何,看看迪,你可能真的很高兴。
不,因为你在这里所做的就是创造全球状态。全球国家元首有各种各样的问题,其中一个职能以相当不可见的方式依赖于其他职能的行为。如果一个函数调用了另一个函数,而该函数在结束前忘记存储和恢复
将工厂类型存储为成员,并将其传递给需要它的新对象的构造函数(或者根据需要创建一个新对象,等等)可能是最有意义的。它还提供了更好的控制——您可以保证由对象A构造的所有对象都经过同一个工厂,或者您可以提供交换工厂的方法。
我建议不要这样做,因为这样很难为调用这些工厂的代码编写合适的单元测试。
Misko Hevery在他的博客上有一篇关于这个的好文章。
Is this considered best practice?
我认为没问题:你说"我的程序不需要更多",因此你实现任何更灵活/抽象的东西可能是你不需要的。
singleton的错误形式是实现工厂方法的等效形式,其中包括:
1 | return new CalculationFactory (); |
这是很难根除,模仿或包装。
返回在其他地方创建的对象的版本要好得多,尽管与大多数东西一样,也可能被误用或过度使用。