GOF Singleton模式是否有可行的替代方案?
面对现实吧。 Singleton模式是一个备受争议的话题,围栏两侧都有成群的程序员。有些人觉得Singleton只不过是一个荣耀的全局变量,而其他人则是按模式发誓并不断使用它。但是,我不想让"单例论战"成为我问题的核心。每个人都可以进行拔河比赛并与之抗衡,看看谁在我所关心的比赛中获胜。我要说的是,我不相信有一个正确的答案,而且我并不是故意在煽动党派争吵。当我问这个问题时,我只对单例替代方案感兴趣:
它们是GOF单例模式的任何特定替代方法吗?
例如,过去很多次使用单例模式时,我只是对保留一个或者几个变量的状态/值感兴趣。但是,可以在类的每个实例之间使用静态变量而不是使用单例模式来保留变量的状态/值。
我们还有什么其他想法?
编辑:我真的不希望这是关于"如何正确使用单例"的另一篇文章。同样,我正在寻找避免这种情况的方法。好玩吗我想我是在用最好的电影预告片声音问一个纯粹的学术问题,"在没有单例的平行宇宙中,我们能做什么?"
解决方案
我们不必全力以赴避免任何模式。模式的使用既可以是设计决策,也可以是自然的选择(恰好就位)。设计系统时,可以选择使用模式还是不使用模式。但是,我们不应该全力以赴避免任何最终成为设计选择的事情。
我不会避免使用Singleton模式。或者合适,我就用它,或者不合适,我就不使用它。我相信就这么简单。
Singleton的适当性(或者缺乏适当性)取决于具体情况。这是必须做出的设计决策,并且必须理解(并记录)该决策的结果。
使用普通对象和工厂对象。工厂仅负责使用配置信息(例如,包含配置信息)和行为来监管实例和普通对象的详细信息。
Spring或者其他任何IoC容器在此方面都做得相当不错。由于这些类是在应用本身之外创建和管理的,因此容器可以使简单的类成为单例,并在需要时将其注入。
如果使用Singleton表示单个数据对象,则可以改为传递数据对象作为方法参数。
(尽管,我认为这首先是使用Singleton的错误方法)
如果问题是要保持状态,则需要MumbleManager类。在开始使用系统之前,客户端会创建一个MumbleManager,其中Mumble是系统的名称。通过这种方式,国家得以保留。MumbleManager可能会包含一个保存状态的属性包。
这种类型的感觉非常像C,而不是对象,就像我们会发现定义系统的对象都将引用同一个MumbleManager。
实际上,如果我们从头开始设计避免Singeltons,则可能不必通过使用静态变量来解决不使用Singletons的问题。使用静态变量时,我们或者多或者少都在创建一个Singleton,唯一的区别是我们正在创建不同的对象实例,但是在内部它们的行为都好像它们在使用Singleton。
我们能否提供一个详细的示例,说明我们使用Singleton还是当前使用Singleton并试图避免使用它?这可以帮助人们找到更理想的解决方案,而根本不用Singleton来处理情况。
顺便说一句,我个人对Singletons没问题,我也无法理解其他人对Singletons的问题。我看不出他们有什么不好。也就是说,如果我们不滥用它们。任何有用的技术都可能被滥用,如果被滥用,将会导致负面结果。通常被滥用的另一种技术是继承。仍然没有人会说继承是不好的,只是因为有人可怕地滥用了继承。
Alex Miller在"我讨厌的模式"中引用了以下内容:
"当单身人士似乎是答案时,我发现这样做通常更明智:
- 创建单例的接口和默认实现
- 在系统顶部构造默认实现的单个实例。这可能在Spring配置中,也可能在代码中,或者以多种方式定义,具体取决于系统。
- 将单个实例传递到需要它的每个组件中(依赖注入)
就我个人而言,实现类似单例行为的更明智的方法是使用完全静态的类(静态成员,静态方法,静态属性)。
大多数时候,我都是以这种方式实现的(从用户的角度来看,我无法想到任何行为差异)
我们是什么意思,我有什么避免方法?
要"避免"它,这意味着我遇到了许多情况,其中单例模式自然很适合,因此我必须采取一些措施来化解这些情况。
但是没有。我不必避免使用单例模式。它根本不会出现。
我认为管理单例的最佳地点是在班级设计水平。在此阶段,我们应该能够绘制出类之间的交互关系,并确定是否绝对需要在应用程序生命周期的任何时候都只存在该类的1个实例。
如果是这种情况,那么我们就有一个单身人士。如果在编码过程中为了方便起见扔了单例,那么我们应该真正地重新设计,并停止对所说的单例进行编码:)
是的,"警察"是我在这里的意思,而不是"避免"。单例是不可避免的(就像goto和global变量不可避免的相同方式)。相反,我们应该监视它的使用,并确保它是有效完成所需工作的最佳方法。
我主要将单例用作"方法容器",根本没有任何状态。如果我需要与许多类共享这些方法,并且希望避免实例化和初始化的负担,我可以创建一个上下文/会话并在那里初始化所有类;涉及会话的所有内容也都可以访问其中包含的"单个"。
存在单例模式是因为在某些情况下需要单个对象来提供一组服务。
即使是这种情况,我仍然认为通过使用表示实例的全局静态字段/属性来创建单例的方法是不合适的。这是不合适的,因为它在静态字段和对象之间(而不是对象提供的服务)在代码中创建了依赖关系。
因此,我建议不要将经典的单例模式与服务式容器一起使用"喜欢"模式,在这种情况下,我们可以通过请求所需服务类型的方法来获得对它的引用,而不是通过静态字段使用单例。
*pseudocode* currentContainer.GetServiceByObjectType(singletonType) //Under the covers the object might be a singleton, but this is hidden to the consumer.
而不是单一的全局
*pseudocode* singletonType.Instance
这样,当我们想要将对象的类型从单例更改为其他类型时,就可以轻松地做到这一点。另外,作为一项添加好处,我们不必将对象实例的分配传递给每个方法。
另请参见控制反转,其思想是通过将单例直接暴露给使用者,可以在使用者和对象实例之间创建依赖关系,而不是由对象提供的对象服务。
我的观点是,尽可能避免隐藏单例模式的使用,因为并非总有可能避免或者希望这样做。
我遇到的最好的解决方案是使用工厂模式来构造类的实例。使用该模式,可以确保只有一个实例的类在使用它的对象之间共享。
我虽然管理起来很复杂,但是在阅读了这篇博客文章"所有的单身人士都去了哪里?"之后,这似乎很自然。顺便说一句,它对隔离单元测试很有帮助。
总之,我们需要做什么?每当一个对象依赖于另一个对象时,它将仅通过其构造函数接收该对象的实例(类中没有new关键字)。
class NeedyClass { private ExSingletonClass exSingleton; public NeedyClass(ExSingletonClass exSingleton){ this.exSingleton = exSingleton; } // Here goes some code that uses the exSingleton object }
然后是工厂。
class FactoryOfNeedy { private ExSingletonClass exSingleton; public FactoryOfNeedy() { this.exSingleton = new ExSingletonClass(); } public NeedyClass buildNeedy() { return new NeedyClass(this.exSingleton); } }
由于只实例化工厂一次,因此将有一个exSingleton实例化。每次我们调用buildNeedy时,NeedyClass的新实例将与exSingleton捆绑在一起。
我希望这有帮助。请指出任何错误。
要了解解决Singletons的正确方法,我们需要了解Singletons(通常是全局状态)有什么问题:
单例隐藏依赖项。
为什么这么重要?
因为如果隐藏依赖项,我们往往会失去对耦合量的跟踪。
我们可能会争辩说
void purchaseLaptop(String creditCardNumber, int price){ CreditCardProcessor.getInstance().debit(creditCardNumber, amount); Cart.getInstance().addLaptop(); }
比简单
void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart, String creditCardNumber, int price){ creditCardProcessor.debit(creditCardNumber, amount); cart.addLaptop(); }
但是至少第二个API清楚地表明了该方法的协作者是什么。
因此,解决Singletons的方法不是使用静态变量或者服务定位器,而是将Singleton类更改为实例,这些实例在有意义的范围内实例化并注入到需要它们的组件和方法中。我们可以使用IoC框架来处理此问题,也可以手动执行,但是重要的是摆脱全局状态并使相关性和协作明确。
由于没有在高度面向对象的环境(例如Java)中进行编程,因此我对讨论的复杂性并不完全了解。但是我已经在PHP 4中实现了单例。我这样做是为了创建一个"黑匣子"数据库处理程序,该处理程序将自动初始化,并且不必在不完整且有些破损的框架中上下传递函数调用。
阅读了单例模式的一些链接后,我不确定是否会再次以完全相同的方式实现它。真正需要的是多个具有共享存储的对象(例如,实际的数据库句柄),这几乎就是我所说的。
像大多数模式和算法一样,使用单例"只是因为它很酷"是"做错事"。我需要一个真正的"黑匣子"电话,碰巧看起来很像单身汉。而IMO就是解决这个问题的方法:要了解这种模式,但还要注意它的范围更广,并且它的实例在什么层次上需要唯一。
Monostate(在Robert C. Martin的敏捷软件开发中进行了描述)是单例的替代方案。在这种模式下,类的数据都是静态的,但获取器/设置器是非静态的。
例如:
public class MonoStateExample { private static int x; public int getX() { return x; } public void setX(int xVal) { x = xVal; } } public class MonoDriver { public static void main(String args[]) { MonoStateExample m1 = new MonoStateExample(); m1.setX(10); MonoStateExample m2 = new MonoStateExample(); if(m1.getX() == m2.getX()) { //singleton behavior } } }
Monostate与Singleton具有相似的行为,但是这样做的方式是程序员不必了解正在使用Singleton的事实。