设计模式(四)单例模式
发布日期:2021-09-17 01:32:52 浏览次数:9 分类:技术文章

本文共 6103 字,大约阅读时间需要 20 分钟。

写在前边:辛辛苦苦写了好几天终于能有一篇发到首页上了,其中的艰辛就不必多说了,我不是专家不发能首页,好多文章博乐也都不看,比起首页上那些空洞无味的文章,我觉得我的博客对一部分人能起到帮助的作用,如果您觉得我写的还可以就顶一下吧,您的支持是我最大的动力!

一、模式定义

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它

提供全局访问的方法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式

是一种对象创建型模式。单例模式又名单件模式或单态模式。

二、模式结构

这里写图片描述

我们可以看到单例模式与我们之前所讲的工厂模式不同,它只有一个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
(); /** * 首先将本类的实例加入到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());    }}

我们看到多例模式的多例类中有多于一个对象实例,究竟返回哪一个对象取决于程序的业务逻辑。

源码下载:

转载地址:https://blog.csdn.net/xingjiarong/article/details/50040939 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:设计模式(六)原型模式
下一篇:设计模式(五)创建者模式(Builder)

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月27日 04时03分31秒