94

面对现实吧。单例模式是一个备受争议的话题,在围墙的两边都有成群的程序员。有些人觉得 Singleton 只不过是一个美化的全局变量,而另一些人则对模式发誓并不断地使用它。但是,我不希望Singleton Controversy成为我问题的核心。 每个人都可以进行一场拔河比赛,然后一决胜负,看看谁会赢。我想说的是,我不相信有一个正确的答案,我也不是故意煽动党派争吵。当我问这个问题时,我只是对单例替代品感兴趣:

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

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

你还有什么别的想法?

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

4

16 回答 16

97

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

单例隐藏依赖项。

为什么这很重要?

因为如果您隐藏依赖项,您往往会失去对耦合量的跟踪。

你可能会争辩说

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

于 2008-10-02T14:04:04.720 回答
77

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

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

  1. 创建单例的接口和默认实现
  2. 在系统的“顶部”构建默认实现的单个实例。这可能在 Spring 配置中,或者在代码中,或者根据您的系统以各种方式定义。
  3. 将单个实例传递给需要它的每个组件(依赖注入)
于 2008-10-02T12:50:04.180 回答
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 捆绑在一起。

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

于 2008-10-02T13:38:16.233 回答
9

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

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

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

于 2008-10-02T12:40:45.547 回答
9

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

于 2008-10-02T12:43:08.473 回答
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 与单例具有相似的行为,但这样做的方式是程序员不一定知道正在使用单例这一事实。

于 2010-03-17T17:46:10.633 回答
4

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

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

因此,我建议不要使用经典的单例模式,而是将 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,其想法是通过将单例直接暴露给消费者,您可以在消费者和对象实例之间创建依赖关系,而不是对象提供的对象服务。

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

于 2008-10-02T13:10:58.187 回答
2

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

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

于 2008-10-02T12:44:33.650 回答
1

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

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

于 2008-10-02T12:44:49.780 回答
0

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

于 2008-10-02T12:43:04.090 回答
0

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

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

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

于 2008-10-02T12:45:23.320 回答
0

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

于 2008-10-02T12:55:47.630 回答
0

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

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

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

于 2008-10-02T13:05:48.543 回答
0

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

于 2008-10-02T13:06:09.993 回答
0

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

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

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

于 2008-11-11T22:50:46.430 回答
-1

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

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

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

于 2008-10-02T12:57:07.037 回答