GOF Singleton模式是否有可行的替代方案?


91

面对现实吧。Singleton模式是高度争议与成群程序员话题围栏边。有些人觉得Singleton只是荣耀的全局变量,而其他人则按模式发誓并不断使用它。但是,我不想让“ 单例论战 ”成为我问题的核心。 每个人都可以进行拔河比赛并与之抗衡,看看谁在我所关心的范围内获胜。我要说的是,我不相信有一个正确的答案,而且我并不是故意在煽动党派争吵。当我问这个问题时,我只是对单例替代方案感兴趣:

它们是GOF单例模式的任何特定替代方案吗?

例如,过去很多次使用单例模式时,我只是对保留一个或几个变量的状态/值感兴趣。但是,可以在类的每个实例之间使用静态变量而不是使用单例模式来保留变量的状态/值。

您还有什么其他想法?

编辑: 我真的不希望这是关于“如何正确使用单例”的另一篇文章。同样,我正在寻找避免这种情况的方法。好玩吗 我想我是在用最好的电影预告片声音问一个纯粹的学术问题:“在没有单例的平行宇宙中,我们能做什么?”


什么?它既不是好事也不是坏事,但是我该如何替换呢?对于所有说这很好的人-不要参加。你们所有人都说这很糟糕,请向我展示如何在没有它的情况下证明自己。听起来对我有争议。
S.Lott,

@CodingWithoutComents:阅读了整个帖子。这就是我的感觉:“如果您认为Singletons没问题,请不要回复”。
S.Lott

2
好吧,如果真是这样,我深表歉意。我以为我采取了重要措施来避免两极分化。我以为我问的问题的方式,单身的既爱好者和仇敌能都来走与作为一个程序员,我们都有选择-他们从来都不是只有一种正确的方式
CodingWithoutComments

如果使用Singletons,那么如何解决它们将毫无帮助。听起来让我感到两极分化。
S.Lott

9
我每天都使用Singletons,但这是否使我无法想到可能会有更好的做事方法?设计模式只存在了14年。我会将它们视为圣经的真理吗?我们是否停止尝试跳出框框思考?我们是否不尝试推进CS学科?
CodingWithoutComments

Answers:


76

Alex Miller在“ 我讨厌的模式 ”中引用了以下内容:

“当单身人士似乎是答案时,我发现这样做通常更明智:

  1. 创建您的单例的接口和默认实现
  2. 在系统的“顶部”构造默认实现的单个实例。这可能在Spring配置中,也可能在代码中,或者以各种方式定义,具体取决于您的系统。
  3. 将单个实例传递到需要它的每个组件中(依赖注入)

2
我知道这已经快5岁了,但是您能对此进行扩展吗?我认为我被教导,创建单例的正确方法是使用接口。我很想知道我在做什么是“确定”,而不是像我之前所说的那样糟糕。
Pureferret

97

要了解解决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清楚地表明了该方法的协作者是什么。

因此,解决Singleton的方法不是使用静态变量或服务定位器,而是将Singleton类更改为实例,这些实例在有意义的范围内实例化,并注入到需要它们的组件和方法中。您可以使用IoC框架来处理此问题,也可以手动进行,但是重要的是摆脱全局状态并使相关性和协作明确。


+1用于讨论问题的根源,而不仅仅是解决问题的方法。
Phil

9
在成熟的多层应用程序中,第二种方法通常会创建大量不必要的代码。通过使用逻辑来污染中间层的代码以传递到较低层,这些对象在该级别上是不必要的,我们不是在创建不需要的依赖项吗?说导航堆栈中的第4层会启动后台操作,例如文件上传。现在说您想在完成时提醒用户,但是到那时,用户可能位于应用程序的完全不同的部分中,该部分仅与初始视图共享第1层。大量不需要的代码...
Hari Karam Singh 2012年

1
@RasmusFaber Singleton模式不会隐藏依赖项。您抱怨隐藏依赖项是与模式无关的问题。模式的确很容易犯此错误,但是很有可能同时执行IoC和Singleton模式。例如,我可以创建一个需要对其实例进行特殊生命周期管理的第3方库,并且使用我的库的任何人仍应使用经典的Dependency Injection将“我的单例”实例“传递”到其类中,而不是“悬吊”我的单例从每个切入点。
马丁·布利斯2013年

@HariKaramSingh,您可以使用订阅/发布模式或系统范围的事件总线(必须在所有相关各方之间共享,而不是单例),以解决此问题。
Victor Sorokin

4
同样,发布/订阅或观察者模式也可能像“失去耦合之路”一样糟糕,就像不得不逐步调试严重依赖它们的框架的任何人所证明的那样。至少对于单例而言,被调用的方法名称与调用代码位于同一位置:)就我个人而言,我认为所有这些策略都有其位置,没有一个可以盲目实现。lop草的编码人员将使意大利面条无法用任何样式制作,负责任的编码人员不必在意识形态上过度负担自己来创建优雅的代码。
哈里·卡拉姆·辛格

14

我遇到的最好的解决方案是使用工厂模式构造类的实例。使用该模式,可以确保只有一个类的实例在使用它的对象之间共享。

我虽然管理起来很复杂,但是在阅读了这篇博客文章“所有单身人士都去了哪里?”之后,,似乎很自然。顺便说一句,隔离单元测试很有帮助。

总之,您需要做什么?每当一个对象依赖于另一个对象时,它将仅通过其构造函数接收该对象的实例(您的类中没有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捆绑在一起。

我希望这有帮助。请指出任何错误。


5
以确保仅在需要私有构造函数并将实例定义buildNeedy方法为静态方法后才实例化Factory。
朱利安·格里尼尔

16
Julien是正确的,此修复程序有一个基本缺陷,那就是您隐式地说只能实例化一个工厂。如果采取必要的预防措施以确保仅实例化一个工厂(就像朱利安所说的那样),最终将导致……单身!实际上,这种方法只是在单例之上添加了不必要的抽象层。
鲍勃·萨默斯

还有另一种方法,只有一个实例。您只能实例化工厂一次。无需编译器强制执行。这对于需要其他实例的单元测试也有帮助。
2011年

这是我见过的针对单例错误解决的问题的最佳解决方案-添加相关性而不会使每个方法调用混乱(并因此重新设计接口并重构其相关性)。进行轻微的修改非常适合我的情况,在这种情况下,仅存在一个“ singleton”实例并不重要,但默认情况下每个人都将共享同一实例非常重要(修改为:代替使用工厂,而使用static外部设置公共实例并在构造过程中使用该实例的方法)。
meustrus

因此,这种方法的基本优点是,您只需要在一个对象实例(工厂)周围改组,而不是可能整堆对象(您选择不使用Singletons)?
哈里·卡拉姆·辛格

9

Spring或任何其他IoC容器在这方面做得相当不错。由于这些类是在应用本身之外创建和管理的,因此容器可以使简单的类成为单例,并在需要时将其注入。


是否有关于上述主题的链接?
CodingWithoutComments

这些框架似乎特定于Java-是否还有其他特定于语言的选项?还是与语言无关?
CodingWithoutComments

对于.NET:Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/zh-cn/library/cc468366.aspx Unity实际上是一个依赖项注入框架,但可能适用于该主题。
Erik van Brakel

1
为什么没有太多的选票?IOC是避免Singleton的完美解决方案。
Sandeep Jindal 2013年

@CodingWithoutComments我知道PHP具有Symfony服务容器。尽管红宝石花花公子还可以通过其他方式注入依赖关系。您是否对特定语言感兴趣?
Bevelopper

9

您不必全力以赴避免任何模式。模式的使用既可以是设计决策,也可以是自然的选择(恰好就位)。设计系统时,可以选择使用模式还是不使用模式。但是,您不应该全力以赴避免任何最终成为设计选择的事情。

我不会避免使用Singleton模式。要么合适,我就用它,要么不合适,我就不使用它。我相信就这么简单。

Singleton是否合适(取决于情况)。这是必须做出的设计决策,并且必须理解(并记录)该决策的结果。


我本打算说同样的话,但并不能完全回答他的问题:)
Swati,

2
请在适当或不适当的答案中讨论。好的,请。
CodingWithoutComments

它确实回答了这个问题。我没有避免使用Singleton Pattern的技术,因为我没有竭尽全力避免使用Singleton Pattern,而且我认为任何开发人员都不应该这样做。
Thomas Owens

我认为您何时可以将其归纳为恰当的方法还是不合适的方法。这些都是特定于项目的,并且在很大程度上取决于相关系统的设计。我不会在此类决策中使用“经验法则”。
Thomas Owens

嗯 我不明白为什么这被否决了。我回答了问题-您避免使用Singleton模式的技巧是什么?-通过说我没有技巧,因为我不会竭尽全力避免这种模式。反对者可以详细说明他们的推理吗?
Thomas Owens

5

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的事实。


这值得更多的选票;我从Google来到这里,正在寻找MonoState复习课程!
mahemoff

@Mark在我看来,就线程安全而言,很难锁定这种设计。我要为MonoStateExample中的每个静态变量创建一个实例锁,还是要创建一个所有属性都可以利用的锁?两者都有严重的后果,很容易以Singleton模式解决。
马丁·布利斯2013年

本示例是Singleton模式的替代方案,但不能解决Singleton模式的问题。
Sandeep Jindal 2013年

4

之所以存在单例模式,是因为在某些情况下需要单个对象来提供一组服务

即使是这种情况,我仍然认为通过使用表示实例的全局静态字段/属性来创建单例的方法是不合适的。这是不合适的,因为它在代码中创建了静态字段和对象之间的依赖关系,而不是对象提供的服务。

因此,我建议不要将经典的单例模式与服务式容器一起使用服务的“喜欢”模式,在这种情况下,您可以通过请求所需服务类型的方法而不是通过静态字段使用单例。

*pseudocode* currentContainer.GetServiceByObjectType(singletonType)
//Under the covers the object might be a singleton, but this is hidden to the consumer.

而不是单一的全局

*pseudocode* singletonType.Instance

这样,当您想将对象的类型从单例更改为其他类型时,就可以轻松地做到这一点。另外,作为一项附加好处,您不必将对象实例的分配传递给每个方法。

另请参见控制反转,其思想是通过将单例直接暴露给使用者,可以在使用者和对象实例之间创建依赖关系,而不是由对象提供的对象服务。

我的意见是躲起来尽可能避免单例模式的使用,因为并非总有可能避免或希望这样做。


我不太了解您使用服务容器的示例。您是否可以链接到在线资源?
CodingWithoutComments

看看“城堡项目-温莎集装箱” castleproject.org/container/index.html。不幸的是,很难找到有关该主题的抽象出版物。
Pop Catalin'Oct

2

如果使用Singleton表示单个数据对象,则可以改为传递数据对象作为方法参数。

(尽管,我认为这首先是使用Singleton的错误方法)


1

如果您的问题是要保持状态,则需要MumbleManager类。在开始使用系统之前,客户端会创建一个MumbleManager,其中Mumble是系统的名称。通过它,国家得以保留。您的MumbleManager可能会包含一个保存您的状态的属性包。

这种类型的感觉非常像C,而不像对象-您会发现定义系统的对象都将引用同一个MumbleManager。


在不主张或反对辛格尔顿的情况下,我必须说这种解决方案是一种反模式。MumbleManager成为包含各种不同知识的“上帝班”,这违反了单一责任制。对于所有应用程序,它也可能是Singleton。
马丁·布利斯2013年

0

使用普通对象和工厂对象。工厂仅负责管理实例和纯对象详细信息,仅包含配置信息(例如包含)和行为。


1
工厂不是经常单身吗?这就是我通常看到实现的Factory模式的方式。
Thomas Owens

0

实际上,如果您从头开始设计避免Singeltons,则可能不必通过使用静态变量来解决不使用Singletons的问题。使用静态变量时,您或多或少都在创建一个Singleton,唯一的区别是您正在创建不同的对象实例,但是在内部它们的行为都好像它们在使用Singleton。

您能否提供一个详细的示例,说明您使用Singleton还是当前使用Singleton并试图避免使用它?这可以帮助人们找到更理想的解决方案,而根本不用Singleton来处理情况。

顺便说一句,我个人对Singletons没问题,我也不明白其他人对Singletons的问题。我看不出他们有什么不好。也就是说,如果您不滥用它们。任何有用的技术都可能被滥用,如果被滥用,将会导致负面结果。通常被滥用的另一种技术是继承。仍然没有人会说继承是不好的,仅仅是因为有人可怕地滥用了继承。


0

就我个人而言,一种实现类似单例行为的更明智的方法是使用完全静态的类(静态成员,静态方法,静态属性)。大多数时候,我都是以这种方式实现的(从用户的角度来看,我无法想到任何行为差异)


0

我认为管理单例的最佳地点是在班级设计水平。在这个阶段,您应该能够绘制出类之间的交互关系,并确定是否绝对需要在应用程序生命周期的任何时候都只存在该类的1个实例。

如果是这种情况,那么您就有一个单身人士。如果在编码过程中为了方便起见扔了单例,那么您应该真正地重新设计,并停止对所述单例进行编码:)

是的,“警察”是我在这里的意思,而不是“避免”。单例是不可避免的(就像goto和global变量不可避免的方式一样)。相反,您应该监视它的使用,并确保它是有效完成所需工作的最佳方法。


1
我完全理解您在说什么。我完全同意。但是,您的答案仍然没有具体回答我的问题。我不希望这是“何时适合使用单例?”的另一个问题。我只是对单例的可行替代方案感兴趣。
CodingWithoutComments

0

我主要将单例用作“方法容器”,根本没有任何状态。如果我需要与许多类共享这些方法,并且希望避免实例化和初始化的负担,我可以创建一个上下文/会话并在那里初始化所有类;涉及会话的所有内容也都可以访问其中包含的“单个”。


0

由于没有在高度面向对象的环境(例如Java)中编程,因此我对讨论的复杂性并不完全了解。但是我已经在PHP 4中实现了单例。我这样做是为了创建一个“黑匣子”数据库处理程序,该处理程序将自动初始化,并且不必在不完整且有些破损的框架中上下传递函数调用。

阅读了单例模式的一些链接后,我不确定要再次以完全相同的方式实现它。真正需要的是具有共享存储的多个对象(例如,实际的数据库句柄),这几乎就是我所说的。

像大多数模式和算法一样,使用单例“只是因为它很酷”是“做错事”。我需要一个真正的“黑匣子”电话,它看起来很像单身汉。而IMO就是解决这个问题的方法:要了解这种模式,但还要注意它的范围更广,并且它的实例在什么层次上必须是唯一的。


-1

您是什么意思,我有什么避免方法?

要“避免”它,这意味着我遇到了许多情况,其中单例模式自然很适合,因此我必须采取一些措施来化解这些情况。

但是没有。我不必避免使用单例模式。它根本不会出现。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.