不使用单例模式时出了什么问题

时间:2015-06-06 15:30:28

标签: java design-patterns singleton

我正在阅读我的电子书(Head First Design Patterns)中的单例模式,我知道使用这种模式是合适的,以防你只需要一个类的一个实例。
但是我对这本电子书中的问题介绍有点麻烦。
(是的,我想我可以在这里引用部分内容!?)

  

巧克力工厂
每个人都知道所有现代巧克力工厂都有计算机控制的巧克力锅炉。的工作   锅炉是要加入巧克力和牛奶,煮沸,然后煮沸   然后将它们传递给下一个制作巧克力棒的阶段。   
这是   Choc-O-Holic,Inc。的工业实力的控制器类   巧克力锅炉。查看代码;你会注意到他们已经尝试过   要非常小心,确保不会发生坏事,比如排水   500加仑未煮沸的混合物,或者当锅炉充满时   已经满了,或煮沸空锅!

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
            // fi ll the boiler with a milk/chocolate mixture
        }
    }

    public void drain() {
        if (!isEmpty() && isBoiled()) {
            // drain the boiled milk and chocolate
            empty = true;
        }
    }

    public void boil() {
        if (!isEmpty() && !isBoiled()) {
            // bring the contents to a boil
            boiled = true;
        }
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

是的,这是他们的问题:

  

Choc-O-Holic在确保坏事方面做得不错   发生了,不是吗?然后,你可能会怀疑如果两个   ChocolateBoiler实例变得松散,可能会发生一些非常糟糕的事情   如果不止一个ChocolateBoiler实例会出现问题   是在应用程序中创建的?

因此,当我们这样做时,问题就会“发生”:

ChocolateBoiler boiler1 = new ChocolateBoiler(),
 boiler2 = new ChocolateBoiler();
//...

但是我看到这两个实例控制着自己的行为,并且它们独立运行(因为这里没有静态字段)。
所以它们分开运行而不影响其他实例。
我不知道这个问题是关于非法状态或当一个实例运行并影响其他实例时可能发生的事情(“错误的程序行为,过度使用资源,或者 不一致的结果“,来自电子书),但它不在这里
那么,How might things go wrong here?,它只是浪费实例吗?

  

如果两个ChocolateBoiler实例松动,一些非常糟糕的事情可以   发生。

我想看看bad things是如何发生的?

#Edit 1: 感谢大家帮助我。我弄清楚我的问题是什么,
当我调用boiler2 = new ChocolateBoiler()时,boiler2实例仍然引用与bolder1相同的boilder,是吗?
我第一次认为new ChocolateBoiler()类似于买一个新的锅炉:)这是关于概念,我是这里的新手

3 个答案:

答案 0 :(得分:4)

您似乎不理解此示例试图解释的概念。 ChocolateBoiler不是真正的锅炉,它是一个java类。

但是,此类可用于指示一块硬件(一个真正的锅炉控制器)执行某些操作。如果您错误地有两个ChocolateBoiler实例并且您错误地使用它们来指示同一个锅炉控制器,那么显然您遇到了麻烦。

在我上一段中有两个“错误地”,你可能会说如果你一般“错误地”做事,那么无论如何你都会遇到麻烦。但是如果设计糟糕的单身人士,错误可能不会那么明显。如果序列化和反序列化不处理序列化问题的单例以保持唯一性,然后尝试使用该实例来加热锅炉,则可以烧毁锅炉。

答案 1 :(得分:3)

您应该注意单身模式的几个问题。 让我们考虑两个不同的单例例子:

1)无国籍单身人士

这个单身人士没有外地成员,只会将方法作为服务提供给外界。

public class StatelessSingleton {

    private static final StatelessSingleton INSTANCE = new StatelessSingleton();

    private StatelessSingleton() {

        // exists to defeat instantiation
    }


    public void service() {
        //...
    }

    public void anotherService() {
        //..
    }

    public StatelessSingleton getInstance() {
        return INSTANCE;
    }
}

出于性能原因和可读性,这种类型的单例通常更好地用仅具有静态方法的类替换。实现模式(例如策略)时可能会出现异常,您需要将某些接口实现为无状态算法,因为缓存此实现是有意义的。要实现接口,您显然需要一个实例。

2)StatefullSingletion

public class StatefullSingleton {

    private int a = 3;

    private static final StatefullSingleton INSTANCE = new StatefullSingleton();

    private StatefullSingleton() {

        // exists to defeat instantiation
    }


    public void service() {
        // do some write operation on a
    }

    public void anotherService() {
        // do some read operation on a
    }

    public StatefullSingleton getInstance() {
        return INSTANCE;
    }

}

现在,关于单身人士的问题:

这两个单例如果实现不当,可能会产生多个实例。如果您在java中使用双重检查锁定以确保只存在一个Singleton实例,那么如何发生这种情况的示例如下:

class Foo {
    private Helper helper;
    public Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

    // other functions and members...
}

有很多资源可以讨论为什么这在java中不起作用,所以这里不需要重复。

避免双重检查锁定问题的一种方法如上所示,私有构造函数和对实例+ getter的静态引用。

第二个问题,特定于StatefullSingelton是如果你的服务方法不同步,多个线程可以搞乱这种Singleton的状态。在您的示例中,如果不同的工人同时填充和排放锅炉,可能会出现问题。

第三个问题是序列化。鉴于Singleton实现了java.io.Serializable接口,这可能导致在反序列化期间有多个Singleton。为避免在反序列化时创建新对象,必须实现readResolve

答案 2 :(得分:2)

使用 Singleton模式的重点与Single Source of Truth principle和冲突管理有关(或可以视为其变体)。 单一的事实来源当然也是一种冲突管理。

单例模式的另一个方面是出于效率原因需要最小化不必要的重复和/或(重新)初始化。

例如,两个单独的实例会在同一个资源上发生冲突,这取决于所使用的应用程序和平台(例如多线程)可能导致各种问题,如死锁,无效状态等。

资源是一个(单例),因此该资源的管理者或驱动程序需要考虑到这一点,以避免在同一资源上发生潜在冲突(见上文)。