设计模式(四)单例模式
最后更新于:2022-04-01 11:05:11
**写在前边:**辛辛苦苦写了好几天终于能有一篇发到首页上了,其中的艰辛就不必多说了,我不是专家不发能首页,好多文章博乐也都不看,比起首页上那些空洞无味的文章,我觉得我的博客对一部分人能起到帮助的作用,如果您觉得我写的还可以就顶一下吧,您的支持是我最大的动力!
### 一、模式定义
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它
提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式
是一种对象创建型模式。单例模式又名单件模式或单态模式。
### 二、模式结构
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-04-20_57175852e1d94.jpg "")
我们可以看到单例模式与我们之前所讲的工厂模式不同,它只有一个Singleton角色,并且这个类自行创建自己的实例,并向系统提供这个实
例,可以注意到我没有在图中吧实例的建造个数限制为1个,这一点我们后边会讲到多例模式。
单例模式分为懒汉式的和饿汉式,有的地方也会讲登记式的单例模式,下面我们就一起来学习一下这三种单例模式。
### 三、饿汉式的单例模式
饿汉式单例模式是Java语言中实现起来最为简单的单例模式,我们通过代码来看一下。
~~~
package com.designpattern.singleton;
public class EagerSingleton {
/**
* 直接创建一个本类的对象
*/
private static final EagerSingleton eagerSingleton = new EagerSingleton();
/**
* 覆盖默认的构造方法,将默认的构造方法声明为私有的,防止其他类直接创建对象
*/
private EagerSingleton(){}
/**
* 提供一个工厂方法来返回本类的唯一对象
* @return
*/
public static EagerSingleton getInstance(){
return eagerSingleton;
}
}
~~~
饿汉式单例模式就是一开始就自己创建一个私有的静态的本类对象,当这个类被加载时,静态变量eagerSingleton就会被初始化,这时这个
类的私有构造方法就会被调用。这个时候,单例类的唯一实例就被创建出来了。但需要获得单例类的对象是就调用getInstance方法。
需要注意的一点就是单例类的构造方法一定要声明为私有的,否则其他类就可以利用构造方法直接创建对象,使单例类不再是只有一个唯一
的实例。
另外,值得一提的是,由于构造方法是私有的,因此此类不能被继承。
饿汉式单例模式在自己加载时就将自己实例化,所以从资源利用效率的角度来讲,饿汉式单例模式不如懒汉式单例模式节省资源,但是饿汉
式单例模式的速度更快一些。
### 四、懒汉式的单例模式
懒汉式单例模式与饿汉式稍有不同,下面看一下代码:
~~~
package com.designpattern.singleton;
public class LazySingleton {
/**
* 单例类的唯一实例,但是不是加载时初始化
*/
private static LazySingleton lazySingleton = null;
/**
* 覆盖原有的默认构造方法,声明为私有的,防止其他类直接使用构造方法创建单例类对象,同时也使子类无法继承
*/
private LazySingleton() {
}
/**
* 线程互斥的获取实例
* @return
*/
public static synchronized LazySingleton getInstance() {
/**
* 如果实例为空就创建实例,创建的语句只会执行一次
*/
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
~~~
从代码我们可以看出,懒汉式单例模式的创建对象是在第一次企图获得单例类的对象时。另外我们再getInstance方法中使用了
synchronization关键字,这是防止出现race condition,使两个线程new出来两个单例类的实例。构造方法同样是私有的,这也决定了子类
不能继承自这个类。
懒汉式单例模式与饿汉式单例模式相比,更节省资源,但是必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作
为资源控制器在实例化时必然涉及资源初始化,而资源初始化很有可能耗费时间,这就意味着出现多个线程同时首次引用此类的几率变得较
大。
### 五、登记式的单例模式
为了克服饿汉式单例模式以及懒汉式单例模式类均不可继承的缺点,产生了一种新的模式——登记式单例模式。
登记式的单例模式中父类中有一个集合,用来存储所有的子类的实例,当一个子类创建时,必须在父类的中登记,也就是把自己的实例加入
到父类的集合中,当其他类想要获取子类的实例时,就到父类的集合中查找,找到了就返回,如果找不到就创建这个子类的唯一实例。
这是父类的代码。
~~~
package com.designpattern.singleton;
import java.util.HashMap;
public class RegSingleton {
/**
* 建立一个HashMap来存储子类的完整类名和子类的实例
*/
private static HashMap registry = new HashMap<String, RegSingleton>();
/**
* 首先将本类的实例加入到HashMap中
*/
static{
RegSingleton x = new RegSingleton();
registry.put(x.getClass().getName(), x);
}
/**
* 构造方法不再是private的了,所以子类可以继承了
*/
protected RegSingleton(){
}
/**
* 根据子类传来的类名返回相应的实例
* @param name 想要获得的类的完整类名
* @return
*/
public static RegSingleton getInstance(String name){
/**
* 提供默认的类
*/
if(name == null){
name = "com.designpattern.singleton.RegSingleton";
}
/**
* 第一次引用这个类时创建类的实例,利用了反射机制
*/
if(registry.get(name) == null){
try{
registry.put(name, Class.forName(name).newInstance());
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 返回子类想要的类的实例
*/
return (RegSingleton)registry.get(name);
}
}
~~~
这是子类的代码。
~~~
package com.designpattern.singleton;
public class RegSingletonChild extends RegSingleton{
/**
* 构造方法必须是公有的,否则父类无法产生子类的对象
*/
public RegSingletonChild(){}
/**
* 工厂方法,获取本类的唯一实例,实际上是借助了父类的getInstance方法
* @return
*/
public static RegSingletonChild getInstance(){
return (RegSingletonChild)RegSingleton.getInstance("com.designpattern.singleton.RegSingletonChild");
}
}
~~~
登记式的单例模式解决了懒汉式和饿汉式不能继承的缺点,但是子类中的构造方法变为了public的,所以其他类可以直接通过构造方法创建
类的实例而不用向父类中登记,这是登记式单例模式最大的缺点。
### 六、什么情况下使用单例模式
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,
使之成为多例模式
可能有人会将一个系统中需要的“全局”变量放在一个单例类中,这样做是不对的。首先,单例模式针对的是只能有一个实例的类而不是变
量,其次一个设计得当的系统就不应该有所谓的“全局”变量,这些变量应该放到他们所描述的实体所对应的类中去。将这些变量从他们的
实体类中抽出来,放到一个不相干的单例类中去,使得这些变量产生错误的依赖关系和耦合关系。
在资源方面管理方面,单例模式用的更多,比如一台计算机连接着一台打印机,那么这个打印机就只有一台,也只应该有一个实例,试想如
果有两个打印机的实例,这两个实例同时操控打印机会出现什么情况,打出来的东西将是乱七八糟的。
所以应该在合适的情况下合理的使用设计模式,而不是一味的追求一个系统利用了多少模式。
### 七、单例模式的优点和缺点
**单例模式的优点:**
- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提
供了共享的概念。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
**单例模式的缺点:**
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,
包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池
溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认
为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失。
### 八、模式在JDK中的应用
Java中的Runtime对象就是一个使用单例模式的例子。在每一个Java应用程序里面,都有唯一的一个Runtime对象,通过这个对象应用程序可
以与其运行环境发生相互作用。
Runtime类提供一个静态工厂方法getRuntime():
`public static Runtime getRuntime();`
通过调用次方法,可以获得Runtime类唯一的一个实例:
`Runtime rt = Runtime.getRuntime();`
### 九、扩展:多例模式
单例是只有一个类实例,自然多例模式就是有多个类的实例了。下面就以有两个实例为例讲一下。
~~~
package com.designpattern.singleton;
import java.util.Random;
public class Die {
/**
* 两个类实例
*/
private static Die die1 = new Die();
private static Die die2 = new Die();
/**
* 私有的构造方法
*/
private Die(){}
/**
* 根据用户使用的标号来决定返回哪一个对象
* @param witchOne
* @return
*/
public static Die getInstance(int witchOne){
if(witchOne == 1)
return die1;
else
return die2;
}
/**
* 使用对象产生随机数
* @return
*/
public synchronized int dice(){
Random rand = new Random();
return rand.nextInt(6)+1;
}
}
~~~
~~~
package com.designpattern.singleton;
public class Client {
public static void main(String[] args){
Die die1 = Die.getInstance(1);
Die die2 = Die.getInstance(2);
System.out.println(die1.dice());
System.out.println(die2.dice());
}
}
~~~
我们看到多例模式的多例类中有多于一个对象实例,究竟返回哪一个对象取决于程序的业务逻辑。
源码下载:[http://download.csdn.net/detail/xingjiarong/9297315](http://download.csdn.net/detail/xingjiarong/9297315)