天天看点

Singleton(单例)模式Singleton(单例)模式

文章目录

  • Singleton(单例)模式
    • 1. 意图
    • 2. 别名
    • 3. 动机
    • 4. 适用性
    • 5. 结构
    • 6. 参与者
    • 7. 协作
    • 8. 效果
    • 9. 实现
    • 10.代码示例
    • 11. 已知应用
    • 12. 相关模式
    • 13. 设计原则口袋
    • 14. 参考文献

Singleton(单例)模式

隶属类别——对象创建型模式

1. 意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2. 别名

NO

3. 动机

对一些类来说,只有一个实例也是很重要的。虽然系统中可以有许多打印机,但却只应该有一个打印假脱机(printer spooler——一个管理多用户多打印机工作的程序),只应该有一个文件系统和一个窗口管理器。一个数字滤波器只能有一个A/D

转化器。一个会计系统只能用于一家公司,有些对象我们只需要一个,例如线程池(threadpool),缓存(cache),对话框,处理偏好设置和注册表(registry)的对象,日志对象。

我们怎么样才能保证一个类只有一个实例并且这个实例易于访问呢,但它能防止你实例化多个对象。

一个更好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建(通过截取创建的请求),并且它可以提供一个访问该实例的方法,现在Java一般是采用枚举创建实例的办法保证单例的安全性与高效性。这就是Singleton模式.

4. 适用性

在下面的情况下可以使用Singleton模式:

  • 当类只能有一个实例并且客户可以从一个众所周知的访问点访问它。
  • 当这个唯一实例应该是通过子类化可扩展,并且客户应该无需更改代码就能使用一个扩展的实例时。
    • Q:在使用enum版本单例设计模式时,鉴于enum无法被扩展,修改单例类代码是扩展单例功能的唯一方法吗?
    • A:
    • A:

5. 结构

传统的:

Singleton(单例)模式Singleton(单例)模式

枚举版本:

Singleton(单例)模式Singleton(单例)模式

6. 参与者

  • Singleton

    ——定义一个getInstance操作,允许客户访问它的唯一实例.getInstance()是一个类操作,即使静态的而且同步的

    ——可能负责创建它自己的唯一实例

7. 协作

  • 客户只能通过Singleton的Instance操作访问一个Singleton的实例

8. 效果

Singleton模式有许多优点:

  • 1.对唯一实例的受控方法 因为Singleton类封装了它的唯一实例,所以它可以严格的控制客户怎么以及何时访问它。
  • 2.缩小名空间 Singleton模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染命名空间?
  • 3.允许对操作和表示的精化 Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用很容易的。你可以用你所需要的类的实例在运行时刻配置应用
  • 4.允许可变数目的实例 这个模式使得你易于改变你的想法,并允许Singleton类的多个实例。此外,你可以用相同的方法来控制应用所使用的实例的数目。只有允许访问的Singleton实例的操作需要改变。
  • 5.比类操作更灵活 另一种封装单件功能的方式是使用类操作(即C++中的静态成员函数,Java中静态方法,或者是Smalltalk中的类方法)。但是C++和Smalltalk都难以改变设计以允许一个类有多个实例。enum版本的Singleton好像可以确保?对!可以确保,枚举方法在功能上与公有域方法相似但更加简洁,无偿提供了序列化机制,即使在面对复杂的序列化或者反射攻击的时候,绝对防止多次实例化。但是当Singleton必须扩展一个超类的时候,而不是扩展Enum的时候,则不宜使用这个方法

9. 实现

下面是使用Singleton模式时所要考虑的实现问题:

  • 1.保证一个唯一的实例 Singleton模式使得这个唯一实例是类的一般实例,但该类被写成只有一个实例能够被创建。做到这一点的一个常用方法是将创建这个实例的操作隐藏在一个类操作(即一个静态成员函数或者是一个类方法)后面,由它保证只有一个实例被创建。这个操作可以访问保存唯一实例的变量。而它可以保证这个变量和返回值之前用这个唯一实例化。这种方法保证了单件在它的首次使用前被创建和使用。

    在Java中可以使用enum确保

    public enum Singleton {
        INSTANCE;
    }
               
  • 创建Singleton类的子类 主要问题与其说是定义子类不如说是建立它的唯一实例。这样客户就可以使用它。事实上,指向单件实例的变量必须用子类的实例进行初始化。
    • 最简单的技术是在Singleton的getInstance中决定你想使用哪一个单件。
    • 另一个选择的Singleton的子类的方法是将Instance的实现从父类(即MazeFactory)中分离出来并将它放入子类。
    • 一种更灵活的方法是使用单件注册表(registry of singleton).可能的Singleton类的集合不是由Instance定义的,Singleton类可以根据名字在一个众所周知的注册表中注册它们的单件实例。这个注册表在字符串名字和单件之间建立映射。因为要映射,所有定义映射的时候要声明类型,所以需要一个同一个的接口,单件需要一个统一的父类或者接口,但Instance需要一个单件是,它参考注册表,根据名字请求单件。
    Singleton类不再负责创建单件。它的主要职责是使得你选择的单件对象在系统中可以被访问。静态对象方法还有一个潜在的缺点——也就是所有可能的Singleton子类的实例都必须被创建,否则他们他们不会被注册。

10.代码示例

单例的实现由几种方式——使用双重检查锁:

Singleton.java

public class Singleton {
	private volatile static Singleton uniqueInstance;
	
	private Singleton() {}
	
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			synchronized (Singleton.class) {
				if (uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
	
	public String getDescription() {
		return "I am a Singleton class";
	}
}
           

使用关键字synchronized:

Singleton.java

public class Singleton {

	private static Singleton uniqueInstance;
		
	private Singleton() {}
	
	public static synchronized Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}	
	
	public String getDescription() {
		return "I'm a thread safe Singleton!";
	}
}
           

《Effective Java》中推荐的第一种方法:公有域方法

// Singleton with public final field
public class Elvis {
	public static final Elvis INSTANCE = new Elvis();
	private Elvis() {
		// ...
	}
	
}
           

私有构造仅被调用一次,用来实例化公有的静态final域Elvis.instance.由于缺少public或者protect的构造器,所以保证了Elvis的全局唯一性;一旦Elvis类被实例化,将智慧存在一个Elvis实例,不多也不少。客户端的任何行为都不会改变这一点,但要提醒一点,享有特权的客户端可以借助AccessibleObject.setAccessible静态方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例时抛出异常

《Effective Java》中推荐的第二种方法:使用静态工厂方法

// Singleton with static factory
public class Elvis {
	private static final Elvis INSTANCE = new Elvis();
	private Elvis() {
		// ...
	}
	public static Elvis getInstance() {
		return INSTANCE;
	}
	
	
}

           
  • 静态工厂的方法优势在于,它提供了灵活性,在不改变其API的前提下,我们可以改变该类是否为Singleton的想法。工厂方法返回该类的唯一实例,但是,它很容易被修改,比如改为每个调用该方法的县城返回一个唯一的实例。
  • 第二个优势是,如果应用程序需要,可以编写一个泛型工厂(genenric singleton factory).
  • 可以通过方法引用(method reference)作为提供者,比如Elvis::instance就是一个Supplier<Elvis>。

除非满足以上任意一种优势,否则还是优先考虑公有域(public-field)的方法。

StackOverflow上推荐给我的,也是《Effective Java》的作者Joshua Bloch推荐的方法——使用枚举方法

Singleton.java

public enum Singleton {
	INSTANCE;
	
	@Override
	public String toString() {
		return "enum Singleton";
	}
}

           

枚举方法在功能上与公有域方法相似但更加简洁,无偿提供了序列化机制,即使在面对复杂的序列化或者反射攻击的时候,绝对防止多次实例化。但是当Singleton必须扩展一个超类的时候,而不是扩展Enum的时候,则不宜使用这个方法

UML图偏简单这里就不放

11. 已知应用

  • java.lang.Runtime.getRuntime(): This method gives Runtime class that has only one instance in a JVM.
  • java.lang.System.getSecurityManager(): This method returns a SecurityManager for the current platform. java.awt.Desktop.getDesktop()

12. 相关模式

  • Abstract Factory : 抽象工厂中ConcreteFactory可以考虑用单例。
  • Prototype:里面怎么使用单例模式了?
  • Builder:生成器里面怎么使用单例模式给外部的public类吗(看到Builder时验证)

13. 设计原则口袋

  • 封装变化
  • 针对接口编程,不要针对实现编程
  • 多用组合少用继承
  • 为交互对象的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类

14. 参考文献

《HeadFirst设计模式》

《设计模式:可复用面向对象软件的基础》