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

时间:2008-10-02 12:38:46

标签: design-patterns singleton

让我们面对现实吧。单例模式是highly controversial主题,成员编程人员在两个两侧。有些人认为,辛格尔顿只不过是一个美化的全球变量,而其他人则以模式发誓并不断使用它。但是,我不希望Singleton Controversy成为我问题的核心。 每个人都可以进行一场拔河比赛并与之斗争,看看谁为我所关心的人赢得胜利。我想说的是,我不相信有一个正确的答案,我不是故意尝试激烈的党派争吵。当我问这个问题时,我只对 singleton-alternatives 感兴趣:

他们是GOF Singleton模式的任何特定替代品吗?

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

你有什么其他想法?

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

16 个答案:

答案 0 :(得分:92)

要了解解决单身人士的正确方法,你需要了解单身人士(以及一般的全球状态)有什么问题:

单身人士隐藏依赖关系。

为什么这很重要?

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

你可能会认为

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框架来处理此问题,或者您可以手动执行此操作,但重要的是摆脱全局状态并使依赖关系和协作显式化。

答案 1 :(得分:72)

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

“当单身人士看起来像答案时,我发现它通常更明智:

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

答案 2 :(得分:14)

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

我虽然管理起来会很复杂,但在阅读了这篇博文"Where Have All the Singletons Gone?"之后,它似乎很自然。另外,它可以帮助您隔离单元测试。

总之,您需要做什么?每当一个对象依赖于另一个对象时,它只会通过它的构造函数接收它的一个实例(你的类中没有新的关键字)。

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捆绑在一起。

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

答案 3 :(得分:8)

你不应该为了避免任何模式而竭尽所能。模式的使用既可以是设计决策,也可以是自然拟合(它只是落实到位)。在设计系统时,您可以选择使用模式或不使用模式。但是,你不应该试图避免任何最终是设计选择的东西。

我没有避免使用Singleton模式。它是适当的,我使用它或它是不合适的,我不使用它。我相信它就是那么简单。

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

答案 4 :(得分:8)

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

答案 5 :(得分:4)

存在单件模式,因为有些情况需要单个对象来提供一组服务

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

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

*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 ,其想法是通过直接向消费​​者展示单身人士,在消费者和对象实例之间创建依赖关系,而不是对象提供的对象服务

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

答案 6 :(得分:4)

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

答案 7 :(得分:2)

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

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

答案 8 :(得分:1)

如果您的问题是要保持状态,则需要MumbleManager类。在开始使用系统之前,您的客户端会创建一个MumbleManager,其中Mumble是系统的名称。通过这种方式保留国家。有可能你的MumbleManager将包含一个保存你的州的财产包。

这种类型的风格感觉非常像C样,并且不是很像对象 - 你会发现定义你的系统的对象都会引用同一个MumbleManager。

答案 9 :(得分:0)

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

答案 10 :(得分:0)

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

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

顺便说一句,我个人对单身人士没有任何问题,我无法理解其他人对单身人士的问题。我没有看到他们的坏事。也就是说,如果你没有滥用它们。每种有用的技术都可能被滥用,如果被滥用,将导致负面结果。另一种常被滥用的技术是继承。仍然没有人会说继承是坏事只是因为有些人可怕地滥用它。

答案 11 :(得分:0)

就我个人而言,实现像singleton这样行为的更明智的方法是使用完全静态类(静态成员,静态方法,静态属性)。 大多数时候我以这种方式实现它(我不能想到任何与用户观点的行为差异)

答案 12 :(得分:0)

我认为监督单身人士的最佳地点是在班级设计层面。在这个阶段,你应该能够映射出类之间的交互,看看是否绝对,绝对要求在应用程序生命的任何时候只有这个类的1个实例存在。

如果是这种情况,那么你有一个单身人士。如果你在编码过程中为了方便而投掷单身,那么你应该重新审视你的设计并停止对单身人士进行编码:)

是的,“警察”是我在这里所说的而不是“避免”。单例不是要避免的东西(就像goto和全局变量不是要避免的那样)。相反,您应该监控它的使用并确保它是获得有效工作的最佳方法。

答案 13 :(得分:0)

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

答案 14 :(得分:0)

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

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

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

答案 15 :(得分:-1)

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

为了“避免”它,这意味着我遇到的情况很多,其中单身人士模式天生就是合适的,因此我必须采取一些措施来化解这些情况。

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