设计模式之单例模式

一、引例

单例模式是GoF23种设计模式的一种,也是最简单一种,用来解决对象创建的问题。我们在使用window操作系统的时候,按下ctrl+alt+del键,可以打开任务管理器,但是大家可以试试多次按下这三个键,能不能打开多个任务管理器,答案当然是否定的,电脑上很多软件,比如一些常见的音乐软件,视频软件都不能同时打开多个,你重复打开的话,显示的是已经在运行的那个程序,为什么要这样做呢?任务管理器就是用来管理操作系统正在运行的进程的,我们有一个就够了,多了无益,还浪费系统资源。读到这里我们明白了,我们通过控制实例的个数来实现控制并节约系统资源
现在我们国家的政策也是一夫一妻制,你同时只能有一个女朋友,不能同时有多个,当然渣男除外,小姐姐们一定要擦亮双眼,远离渣男。在谈恋爱这个类中,GirlFriend这个对象是单例的,你只能创建一个。

愿得一人心,白首不分离

二、定义

为了能够准确的描述一个设计模式,我们必须先知道它的定义。
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
来划重点了,从这段话中,我们能读出单例模式有三个要点,一是某个类只能有一个实例,二是它必须自行创建这个实例,三是它必须自行向整个系统提供这个实例。

Ensure a class has only one instance and provide a global point of access to it

我们之前都是通过new操作符来进行手动的对象创建的,它没有实现对象的创建和使用分离不说,还可以随意的创建,不能控制实例的个数,所以在实现单例模式的时候,首先要确保不能够使用new操作符来创建对象,回忆一下,使用new操作符创建对象的时候,会调用这个对象的构造方法,那么我们就屏蔽掉这个方法,至少不能让外部使用,构造函数私有化可以很好解决这个问题,不让new了,那么我们应该怎样创建对象呢,记不记得我们之前学过静态工厂方法模式,在类里面写一个静态方法来返回对象,外部可以直接通过类名+方法名的方式来获取对象,那怎么能保证只有一个呢,我们在静态方法里面加个判断不就行了,如果存在这个类的对象就直接返回,不存在,就new了之后再返回,所以我们还需要在类中定义一个静态的属性来存储这个类的对象。

三、结构

单例模式只包含一个Singleton(单例角色)类:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
下面看一下实现代码

public class Singleton {
	private static Singleton instance = null;
	private Singleton() {
	}
	public static getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

为了测试单例类所创建对象的唯一性,可以编写如下客户端测试代码

public class Client {
	public static void main(String[] args) {
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1 == s2);
	}
}

编译代码并运行,输出结果为

true

说明两次调用getInstance()时所获取的对象时同一个实例对象,且无法在外部对Singleton进行实例化,因而确保系统中只有唯一的一个Singleton对象。

四、效果与应用

优缺点

优点
1.提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制,客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
2.由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
3.允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
缺点
1.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
2.单例类的职责过重,在一定程度上违背了“单一职责原则”。
3.滥用单例将带来一些负面问题。

适用环境

在生活中,我们选择一种事物的原因一般有两个,一是,我需要这个东西,这是主观上的,另一种是,我只能选择这个东西,这是被动的,表示没有别的选择,是否选择单例模式也是这样。
1.系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而之只允许创建一个对象。
2.客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

应用

1.Java语言类库JDK中就有很多单例模式的应用实例,如java.lang.Runtime类。在每一个Java应用程序里面,都有唯一的一个Runtime对象,通过这个Runtime对象,应用程序可以与其运行环境发生相互作用。

public class Runtime {
	private static Runtime currentRuntime = new Runtime();
	public static Runtime getRuntime() {
		return currentRuntime;
	}
	private Runtime() {}
}

咦,这个单例模式和我们刚才写的好像优点不一样,别着急,我们下面接着聊。
2.在流行的Java EE框架Spring中,当我们试图要从Spring容器中获取某个类的实例时,默认情况下,Spring会通过单例模式进行创建,也就是在Spring的bean工厂中这个bean的实例只有一个,代码如下

<bean id="date" class="java.util.Date" scope="singleton"/>

scope属性来控制对象创建的个数,默认值是singleton,单例创建。

五、扩展

饿汉式

饿汉式单例类是在Java语言中实现起来最为方便的单例类

public class EagerSingleton {
	private static final EagerSingleton instance = new EagerSingleton();
	private EagerSingleton() {
	}
	public static EagerSingleton getInstance() {
		return instance;
	}
}

在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
开始我还在思考,为什么这样设计叫做饿汉式,想了很久,没有很好的解释,后来查了一下,原来是这样。

饿汉式是由the singleton instance is early created at complie time中的early音译过来的
懒汉式是由the singleton instance is lazily created at runtime中的lazily意译过来的

懒汉式

与饿汉式单例类相同之处是,懒汉式单例类的构造函数也是私有的。
与饿汉式单例类不同的是,懒汉式单例类在第一次被引用时将自己实例化,在懒汉式单例类被加载时不会将自己实例化。

public class LazySingleton {
	private static LazySingleton instance = null;
	private LazySingleton() {
	}
	synchronized public static LazySingleton getInstance() {
		if (instance == null)
			instance = new LazySingleton();
		return instance;
	}
}

懒汉式是在第一次调用这个方法的时候才创建,像一个懒人,你不催他他就不干,要催一下才能干活,方法调用就相当于催他的过程。
该懒汉式单例类实现静态工厂方法时使用了同步化机制,以处理多线程环境。
饿汉式单例类在自己被加载时就将自己实例化。单从资源利用效率角度来讲,这个比懒汉式稍差些;从反应速度和反应时间角度来讲,则比懒汉式单例类稍好些。然而,懒汉式单例类在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别时当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味这多个线程同时以用此类的几率变得较大,需要通过同步化机制进行控制。

你可能感兴趣的:(Java,设计模式)