GOF 单例模式是不是有任何可行的替代方案?

Posted

技术标签:

【中文标题】GOF 单例模式是不是有任何可行的替代方案?【英文标题】:Are there any viable alternatives to the GOF Singleton Pattern?GOF 单例模式是否有任何可行的替代方案? 【发布时间】:2010-09-14 19:00:22 【问题描述】:

让我们面对现实吧。单例模式是highly controversial 主题,在围栏的both 两侧都有成群的程序员。有些人觉得 Singleton 只不过是一个美化的全局变量,而另一些人则对模式发誓并不断地使用它。但是,我不希望Singleton Controversy 成为我问题的核心。 每个人都可以来一场拔河比赛,然后一决胜负,看看谁会在我所关心的范围内获胜。我想说的是,我不相信有一个正确的答案,我也不是故意煽动党派争吵。当我问这个问题时,我只是对 singleton-alternatives 感兴趣:

他们是否有任何特定的 GOF 单例模式替代方案?

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

你还有什么想法?

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

【问题讨论】:

什么?它既不好也不坏,但是我该如何替换它呢?对于所有说这很好的人——不要参与。所有说它不好的人,通过向我展示没有它我将如何生活来证明这一点。对我来说听起来很争论。 @CodingWithoutComments:确实阅读了整篇文章。这就是我得到“如果你认为单身人士还可以就不要回复”的感觉。 好吧,如果是这样的话,我很抱歉。我认为我采取了重大措施来避免两极分化。我想我问这个问题的方式是单身人士的爱好者和憎恨者都可以接受,作为一名程序员,我们都有选择——他们永远不是唯一一种正确的方式 如果我使用单例,我对如何解决它们没有可能的贡献。听起来对我来说是两极分化。 我每天都在使用单例,但这是否会阻止我认为可能有更好的方法来做事?设计模式才出现了 14 年。我认为它们是圣经真理吗?我们是否停止尝试跳出框框思考?我们不努力推进 CS 学科吗? 【参考方案1】:

要了解解决单例的正确方法,您需要了解单例的问题(以及一般的全局状态):

单例隐藏依赖项。

为什么这很重要?

因为如果你隐藏依赖项,你往往会忘记耦合量。

你可能会争辩

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

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

【讨论】:

+1 讨论问题的根源,而不仅仅是如何解决它。 在成熟的多层应用程序中,第二种方法通常会创建大量不必要的代码。通过用逻辑污染中间层的代码以传递到较低层,否则在该级别是不必要的对象,我们不是在创建不需要的依赖项吗?假设导航堆栈中的第 4 层启动后台操作,例如文件上传。现在假设您想在完成时提醒用户,但到那时,用户可能处于应用程序的完全不同部分,该部分仅与初始视图共享第 1 层。大量不需要的代码... @RasmusFaber 单例模式不会隐藏依赖关系。您抱怨隐藏依赖项是与模式不同的问题。确实,模式很容易犯这个错误,但是非常有可能和谐地执行 IoC 和 Singleton 模式。例如,我可以创建一个需要对其实例进行特殊生命周期管理的第 3 方库,并且任何使用我的库的人仍应使用经典的依赖注入而不是“天钩”我的单例将我的单例实例“传递”给他们的类从每个切入点。 @HariKaramSingh 您可以使用订阅/发布模式或系统范围的事件总线(必须在所有相关方之间共享,而不是单例)来解决这个问题。 此外,pub/sub 或观察者模式也可能与任何不得不通过严重依赖它们的框架进行单步调试的人所证明的“失去耦合”一样糟糕。至少对于单例,被调用的方法名称与调用代码在同一个地方:) 就个人而言,我认为所有这些策略都有它们的位置,没有一个可以盲目地实现。邋遢的编码员会做出任何模式的意大利面,负责任的编码员不必为自己的意识形态限制过多地负担,以创建优雅的代码。【参考方案2】:

Alex Miller 在“Patterns I Hate”中引用了以下内容:

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

    创建单例的接口和默认实现 在系统“顶部”构建默认实现的单个实例。这可能在 Spring 配置中,或者在代码中,或者根据您的系统以各种方式定义。 将单个实例传递给需要它的每个组件(依赖注入)

【讨论】:

我知道这已经快 5 年了,但你能详细说明一下吗?我想我被教导创建单例的正确方法是使用接口。如果我正在做的事情是“好的”,我会很感兴趣,并且不像我被告知的那么糟糕。【参考方案3】:

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

我虽然管理起来会很复杂,但是看了这篇博文"Where Have All the Singletons Gone?"之后,感觉很自然。顺便说一句,它对隔离单元测试很有帮助。

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

我希望这会有所帮助。如有错误请指出。

【讨论】:

确保您的工厂仅在您需要私有构造函数并将 buildNeedy 方法定义为静态方法时才被实例化。 Julien 是对的,这个修复有一个根本性的缺陷,那就是你隐含地说你只能实例化一个工厂。如果您采取必要的预防措施以确保仅实例化一个工厂(就像 Julien 所说的那样),您最终会得到......一个单例!实际上,这种方法只是在单例之上添加了一个不必要的抽象层。 只有一个实例还有另一种方法。您只能实例化您的工厂一次。编译器无需强制执行此操作。这也有助于您想要不同实例的单元测试。 对于单例错误解决的问题,这是我见过的最好的解决方案 - 添加依赖项而不会弄乱每个方法调用(因此重新设计接口并重构其依赖项)。稍微修改一下就很适合我的情况了方法来外部设置公共实例,并在构造期间使用该实例)。 所以基本上这种方法的优点是你只需要围绕 一个 对象实例(工厂)而不是潜在的一大堆对象(你是选择不使用单例)?【参考方案4】:

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

【讨论】:

有关于上述主题的链接吗? 这些框架似乎是特定于 java 的——还有其他特定于语言的选项吗?还是与语言无关? for .NET: Castle Windsor castleproject.org/container/index.html Microsoft Unity msdn.microsoft.com/en-us/library/cc468366.aspx Unity 实际上是一个依赖注入框架,但它可能适用于这个主题。 为什么这个投票没有太多? IOC 是避免 Singleton 的完美解决方案。 @CodingWithoutComments 我知道 php 有 Symfony 服务容器。 ruby dudes 还有其他注入依赖项的方法。您是否对某种特定的语言感兴趣?【参考方案5】:

您不必特意避开任何模式。模式的使用要么是设计决策,要么是自然契合(它就在适当的位置)。在设计系统时,您可以选择使用模式或不使用模式。但是,您不应该竭尽全力避免最终成为设计选择的任何事情。

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

Singleton 的适当性(或缺乏)取决于具体情况。这是一个必须做出的设计决策,并且必须理解(并记录)该决策的后果。

【讨论】:

我想说同样的话,但它并没有完全回答他的问题:) 请在您的回答中讨论合适或不合适。漂亮请。 它确实回答了这个问题。我没有避免使用单例模式的技术,因为我不会竭尽全力避免它,而且我认为任何开发人员都不应该这样做。 我认为您无法一概而论何时合适或不合适。这都是特定于项目的,并且很大程度上取决于相关系统的设计。对于这样的决定,我不使用“经验法则”。 嗯。我不明白为什么这会被否决。我回答了这个问题 - 你有什么技巧可以避免使用单例模式? - 说我没有技术,因为我不会竭尽全力避免这种模式。否决者能否详细说明他们的推理?【参考方案6】:

Monostate(在 Robert C. Martin 的敏捷软件开发中描述)是单例的替代方案。在这种模式中,类的数据都是静态的,但 getter/setter 是非静态的。

例如:

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 具有与单例类似的行为,但这样做的方式是程序员不一定知道正在使用单例这一事实。

【讨论】:

这值得更多投票;我从 Google 来到这里是为了寻找 MonoState 复习! @Mark 在我看来,这种设计在线程安全方面很难锁定。我是为 MonoStateExample 中的每个静态变量创建一个实例锁,还是创建一个所有属性都利用的锁?两者都有严重的后果,很容易在单例模式中解决。 这个例子是单例模式的替代,但不能解决单例模式的问题。【参考方案7】:

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

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

因此,我建议不要使用经典的单例模式,而是将 service 'like' 模式与 serviced containers 一起使用,而不是通过静态字段使用单例,而是获取对它的引用通过请求所需服务类型的方法。

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

而不是单个全局

*pseudocode* singletonType.Instance

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

另请参阅Inversion of Control,其想法是通过将单例直接暴露给消费者,您在消费者和对象实例之间创建了依赖关系,而不是对象提供的对象服务。

我的意见是尽可能隐藏单例模式的使用,因为它并不总是可以避免或可取的。

【讨论】:

我不太喜欢您的服务容器示例。您有可以链接到的在线资源吗? 看看“城堡项目 - 温莎集装箱”castleproject.org/container/index.html。不幸的是,很难找到关于这个主题的抽象出版物。【参考方案8】:

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

(虽然,我认为这首先是使用单例的错误方式)

【讨论】:

【参考方案9】:

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

这种风格感觉很像 C 而不是很像对象 - 你会发现定义你的系统的对象都会引用同一个 MumbleManager。

【讨论】:

不支持或反对Singleton,我必须说这个解决方案是一种反模式。 MumbleManager 成为一个包含各种不同知识的“神级”,违反了 Single Responsibility。它也可能是所有应用程序关心的单例。【参考方案10】:

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

【讨论】:

工厂不是经常单身吗?这就是我通常看到实现工厂模式的方式。【参考方案11】:

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

您能否提供一个详细的示例,您使用单例或当前使用单例并且您试图避免使用它?这可以帮助人们找到一个更奇特的解决方案,如何在没有 Singleton 的情况下处理这种情况。

顺便说一句,我个人对单身人士没有任何问题,我无法理解其他人对单身人士的问题。我看不出他们有什么不好。也就是说,如果您没有滥用它们。每一种有用的技术都可能被滥用,如果被滥用,将导致负面结果。另一种经常被滥用的技术是继承。仍然没有人会因为某些人滥用它而说继承是不好的。

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

我认为监管单身人士的最佳场所是在班级设计层面。在这个阶段,您应该能够绘制出类之间的交互,看看是否有什么东西绝对需要在应用程序生命的任何时候都只存在这个类的 1 个实例。

如果是这样的话,那么你有一个单身人士。如果你在编码过程中为了方便而扔单例,那么你真的应该重新审视你的设计并停止编码所说的单例:)

是的,“警察”是我在这里的意思,而不是“避免”。单例不是要避免的(就像 goto 和全局变量不是要避免的一样)。相反,您应该监控它的使用情况,并确保它是有效完成您想做的事情的最佳方法。

【讨论】:

我完全理解你所说的工作狂。我完全同意。但是,您的回答仍然没有具体回答我的问题。我不希望这是“什么时候适合使用单例?”的另一个问题。我只是对单例的可行替代方案感兴趣。【参考方案14】:

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

【讨论】:

【参考方案15】:

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

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

与大多数模式和算法一样,“仅仅因为它很酷”而使用单例是错误的做法。我需要一个看起来很像单身人士的真正“黑盒”电话。而 IMO 这就是解决这个问题的方法:注意模式,但也要看看它的更广泛的范围以及它的实例需要在什么级别是唯一的。

【讨论】:

【参考方案16】:

你是什么意思,我有什么技巧可以避免它?

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

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

【讨论】:

以上是关于GOF 单例模式是不是有任何可行的替代方案?的主要内容,如果未能解决你的问题,请参考以下文章

死磕GOF23之单例模式

第一:单例模式

设计模式之五:单例模式(Singleton Pattern)

软件设计模式之单例模式

GOF23设计模式--单例模式

应用程序设置管理器的单例模式的替代方案