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

Are there any viable alternatives to the GOF Singleton Pattern?

让我们面对现实吧。单例模式是一个备受争议的话题,程序员们站在围栏的两边。有一些人觉得单件只是一个光荣的全球变量,还有一些人对模式发誓并不断地使用它。不过,我不想让关于单件事的争论成为我问题的核心。每个人都可以拔河决战,看看谁能赢得我所关心的一切。我想说的是,我不相信有一个正确的答案,我也不是故意煽动党派间的争吵。当我问以下问题时,我只是对单件可选方案感兴趣:

他们是否有任何特定的选择,以戈夫单体模式?

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

你还有什么别的主意?

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


要理解解决单件事的正确方法,您需要了解单件事(以及总体上的全球状况)有什么问题:

单件隐藏依赖项。

为什么这很重要?

因为如果隐藏依赖关系,您可能会失去对耦合量的跟踪。

你可能会说

1
2
3
4
void purchaseLaptop(String creditCardNumber, int price){
  CreditCardProcessor.getInstance().debit(creditCardNumber, amount);
  Cart.getInstance().addLaptop();
}

比简单

1
2
3
4
5
void purchaseLaptop(CreditCardProcessor creditCardProcessor, Cart cart,
                    String creditCardNumber, int price){
  creditCardProcessor.debit(creditCardNumber, amount);
  cart.addLaptop();
}

但至少第二个API清楚地说明了方法的合作者是什么。

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


亚历克斯·米勒在《我讨厌的模式》中引用了以下几句话:

"当一个单身汉似乎是答案时,我发现通常更明智的做法是:

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

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

    我虽然管理起来很复杂,但在读了这篇博文"单身汉都去哪儿了?"似乎很自然。另外,它在隔离单元测试方面有很大帮助。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class NeedyClass {

        private ExSingletonClass exSingleton;

        public NeedyClass(ExSingletonClass exSingleton){
            this.exSingleton = exSingleton;
        }

        // Here goes some code that uses the exSingleton object
    }

    然后是工厂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class FactoryOfNeedy {

        private ExSingletonClass exSingleton;

        public FactoryOfNeedy() {
            this.exSingleton = new ExSingletonClass();
        }

        public NeedyClass buildNeedy() {
            return new NeedyClass(this.exSingleton);
        }
    }

    因为您将只实例化一次工厂,所以将只实例化一次exsingleton。每次调用buildNeedy时,needyclass的新实例都将与exsingleton捆绑在一起。

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


    Spring或任何其他IOC容器在这方面做得相当好。由于类是在应用程序本身之外创建和管理的,容器可以创建简单的类,并在需要时注入它们。


    你不应该为了避免任何模式而走投无路。模式的使用要么是一个设计决策,要么是一个自然的适合(它只是到位的)。在设计系统时,您可以选择使用模式或不使用模式。然而,你不应该为了避免任何最终是设计选择的事情而不择手段。

    我不回避单件模式。要么它是合适的,我使用它,要么它不合适,我不使用它。我相信这很简单。

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


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

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

    因此,我建议不要使用经典的单例模式,而是将服务"like"模式与服务容器一起使用,在这种情况下,您不必通过静态字段使用单例,而是通过请求所需服务类型的方法获取对它的引用。

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

    而不是单个全局

    1
    *pseudocode* singletonType.Instance

    这样,当您想将对象的类型从单例更改为其他类型时,您将有足够的时间轻松地进行更改。另外,作为一个附加的好处,您不必将对象实例的分配传递给每个方法。

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

    我的观点是尽可能地隐藏使用单例模式,因为不可能总是避免它,或是希望它。


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

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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这个事实。


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

    (尽管如此,我认为这是一个错误的方式使用一个单一的一开始)


    如果你的问题是你想保持状态,你需要一个孟买管理类。在您开始使用系统之前,您的客户机会创建一个MumbleManager,其中Mumble是系统的名称。国家通过这种方式得以保留。很可能你的孟买经理会拿一个你所在州的财产袋。

    这种类型的风格让人感觉非常C-like,而不是像object-like——您会发现定义您的系统的对象都引用了同一个mumblemanager。


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


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

    你能给出一个详细的例子,你在哪里使用一个单例,或者一个单例当前被使用,而你试图避免使用它?这将有助于人们找到一个更为奇特的解决方案,即如何在完全没有单件的情况下处理这种情况。

    顺便说一句,我个人对单身没有任何问题,我不理解别人对单身有什么问题。我看他们没什么不好的。也就是说,如果你没有虐待他们。每一种有用的技术都可能被滥用,如果被滥用,将导致负面结果。另一种常用的技术是继承。然而,没有人会说遗产是坏东西,仅仅是因为有些人滥用它。


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


    我认为最好的地方来管理单身汉是在班级设计水平。在这个阶段,您应该能够映射出类之间的交互,并查看是否有绝对的东西,明确地要求在应用程序生命的任何时候都只有这个类的一个实例存在。

    如果是这样的话,那么你就有一个单身汉。如果您在编码过程中为了方便起见而加入了单例,那么您应该重新审视您的设计,并且停止编码,比如singletons:)

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


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


    由于没有在面向对象的环境(例如Java)中编程,所以我还没有完全理解讨论的复杂性。但我在PHP4中实现了一个单例。我这样做是为了创建一个"黑匣子"数据库处理程序,它可以自动初始化,并且不必在一个不完整且有点破损的框架中上下传递函数调用。

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

    像大多数模式和算法一样,使用单例"仅仅因为它很酷"是错误的。我需要一个真正的"黑匣子"电话,它看起来很像一个单身汉。而在我看来,这就是解决这个问题的方法:意识到这个模式,同时也要考虑到它的更广泛的范围,以及它的实例需要在什么程度上是独特的。


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

    为了"避免"这种情况,这意味着我遇到了许多情况,在这种情况下,单例模式是一种自然良好的适合,因此我必须采取一些措施来消除这些情况。

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