第十五章 单例模式
单例模式的要点
显然单例模式的要点有三个:一个某个类只能有一个实例;二是它必须自行创建这个事例;三是它必须自行向整个系统提供这个事例。
单例模式的结构
单例类只能有一个实例。
单例类必须自己创建自己的唯一的实例。
单例类必须给所有其他对象提供这一实例。
由于java语言的特点,使得单例模式在java语言的实现上有自己的特点。这些特点主要表现在单例类如何将自己实例化上。
饿汉式单例类
饿汉式单例类是在java语言里实现起来最为简便的单例类,实现代码如下:
package com.javapatterns.singleton.demos; public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return m_instance; } }
可以看出,在这个类被夹在时,静态变量m_instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的唯一实例就被创建出来了。
java语言中单例类的一个最重要的特点是类的构造子是私有的,从而避免外界利用构造子直接创建出任意多的实例。值得指出的是,由于构造子是私有的,因此此类不能被继承。
懒汉式单例类
与饿汉式单例类相同指出是,类的构造子是私有的。与饿汉式单例类不同的是,懒汉式单例类在第一次被引用时将自己实例化。如果加载器是静态的,那么在懒汉式单例类被加载时不会讲自己实例化。
懒汉式单例类的源代码如下:
package com.javapatterns.singleton.demos; public class LazySingleton { private static LazySingleton m_instance = null; private LazySingleton() { } synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance; } }
饿汉式和懒汉式的对比
饿汉式单例类在自己被加载时就将自己实例化。即便加载器是静态的,在饿汉式单例类被加载时仍会讲自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。从速度和反应时间角度来讲,则比懒汉式单例类稍好些。然而,懒汉式单例类在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器在实例化时必然涉及资源初始化,而资源初始化很有可能消耗时间。这意味着出现多线程同时首次引用此类的几率变得较大。
实际上,java与模式这本书认为饿汉式单例类更符合java语言本身的特点。
登记式单例类
代码如下:
package com.javapatterns.singleton.demos; import java.util.HashMap; public class RegSingleton { protected RegSingleton() {} static public RegSingleton getInstance(String name) { if (name == null) { name = "com.javapatterns.singleton.demos.RegSingleton"; } System.out.println("From RegSingleton: requesting for " + name ); if (m_registry.get(name) == null) { try { m_registry.put( name, Class.forName(name).newInstance() ) ; } catch(ClassNotFoundException e) { System.out.println("Class " + name + " is not found."); } catch(InstantiationException e) { System.out.println("Class " + name + " can not be instantiated."); } catch(IllegalAccessException e) { System.out.println("Class " + name + " can not be accessed."); } } return (RegSingleton) (m_registry.get(name) ); } static private HashMap m_registry = new HashMap(); static { RegSingleton x = new RegSingleton(); m_registry.put( x.getClass().getName() , x); } public String about() { return "Hello, I am RegSingleton."; } }
上面类的子类RegSingletonChild 需要父类的帮组才能实例化。
package com.javapatterns.singleton.demos; import java.util.HashMap; public class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} static public RegSingletonChild getInstance() { return (RegSingletonChild) RegSingleton.getInstance( "com.javapatterns.singleton.demos.RegSingletonChild" ); } public String about() { return "Hello, I am RegSingletonChild."; } }
package com.javapatterns.singleton.demos; public class RegSingletonTest { public static void main(String[] args) { System.out.println( RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingleton").about() ) ; System.out.println( RegSingleton.getInstance(null).about() ) ; System.out.println( RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingletonChild").about() ) ; System.out.println( RegSingletonChild.getInstance().about()) ; } }
结果是:
From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingleton Hello, I am RegSingleton. From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingleton Hello, I am RegSingleton. From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonChild Hello, I am RegSingletonChild. From RegSingleton: requesting for com.javapatterns.singleton.demos.RegSingletonChild Hello, I am RegSingletonChild.
在GoF原始的例子中并没有getInstance()方法,这一得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。本章在登记式单例类子类的例子里,加入了getInstance()方法,这一座的好处是RegSingletonChild可以通过这个方法返还自己的实例。而这一座的缺点是,由于数据类型不同,无法再RegSingleton提供这样一个方法。
由于子类必须允许父类以构造子调用生产例子,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式生产实例而不再父类的登记中。这是登记式单例类的一个缺点。
GoF曾指出,由于父类的实例必须存在才能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。
使用单例模式的条件
使用单例模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。
多个JVM系统的分散式系统
EJB容器有能力将一个EJB的实例跨国几个JVM调用。由于单例对象不是EJB因此,单例类局限于某一个JVM中。换言之,如果EJB在跨国JVM后仍然需要引用同一个单例类的话,这个单例类就会在数个JVM中被实例化,造成多个单例对象的实例出现。一个J2EE应用系统可能分布在数个JVM中,这时候不一定需要EJB就能造成多个单例类的实例出现在不同JVM中的情况。
如果这个单例类是没有状态的,那么就没有问题。但是如果这个单例类是有状态的,那么问题就来了。举例来说,如果一个单例对象可以持有一个int类型的属性没用来给一个系统提供一个数值唯一的序列号码没座位某个贩卖系统的账号号码的话,用户会看到同一个号码出现好几次。
在任何使用了EJB RMI和JINI技术的分散式系统中,应当避免使用有状态的单例模式。
java语言中的单例模式
java的Runtime对象
在java语言内部mjava.lang.Runtime对象就是一个使用单例模式的例子。在每一个java应用程序里面,都有唯一的一个Runtime对象。通过这个Runtime对象,应用程序可以与其运行环境发生相互作用。
代码如下:
import java.io.*; public class CmdTest { public static void main(String[] args) { Process proc = Runtime.getRuntime().exec("notepad.exe"); } }
不完全的单例类
程序如下:
package com.javapatterns.singleton.demos; public class LazySingleton { public LazySingleton() { } synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance; } private static LazySingleton m_instance = null; }
上面的代码看起来是一个懒汉式单例类,但是它有一个公开的构造子。由于外界可以使用构造子创建出任意多个此类的实例,这违背了单例类只能有一个实例的特征,因此这个类是不完全的单例类。
造成这种情况出现的原因有以下几种可能:
1.初学者的错误。
2.当初出于考虑不周,讲一个类设计成为单例类,后来发现此类应当有多于一个实例。为了弥补错误,干脆将构造子改为公开的,以便在需要多于一个的实例时,可以随时调用构造子创建实例。
3.设计师的java知识很好,知道单例如何用,但是还是这样,因为他意在使用一个改良的单例模式。这时候,除去共有的构造子不符合单例模式的要求之外,这个类必须是很好的单例模式。
默认实例模式
有些设计师将这种不完全的单例模式叫做“默认实例模式”。这样做的唯一好处是。这种模式允许客户端选择如何将类实例化:创建新的自己独有的实例,或者使用共享的实例。
java与模式这本书建议读者不要这样做。。。。
双重检查成立的研究
成例是一种代码层次上的模式,有很多人认为双重检查成例可以使用在懒汉单例模式里面。
线程安全的版本
class Foo { private Helper helper = null; public synchronized Helper getHelper() { if(helper == null) { helper = new Heper(); return helper; } } }
画蛇添足的双重检查
使用双重检查成例的懒汉式单例模式代码:
class Foo { private Helper helper = null; public Helper getHelper() { if(helper == null) { //第一次检查 //这里会有多于一个的线程同时到达 synchronized (this) { //这里在每个时刻只能有一个线程 if(helper == null) { //第二次检查 helper = new Helper(); } } } return helper; } }
在java编译器中,Foo类的初始化与helper变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取helper引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。
文献[BLOCH01]指出:一般而言,双重检查成例对java语言来说是不成立的。
在一般情况下,使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。
第十七章 多例模式
作为对象的创建模式,多例模式中的多例类可以有多个实例,而且多例类必须自己创建、管理自己的实例,并向外界提供自己的实例。
有上限的多例类
package com.javapatterns.multilingual.dice; import java.util.Random; import java.util.Date; public class Die { private static Die die1 = new Die(); private static Die die2 = new Die(); private Die() { } public static Die getInstance(int whichOne) { if (whichOne == 1) { return die1; } else { return die2; } } public synchronized int dice() { Date d = new Date(); Random r = new Random( d.getTime() ); int value = r.nextInt(); value = Math.abs(value); value = value % 6; value += 1; System.out.println(value); return value; } }
在多例类Die中,使用了饿汉方式创建了两个Die的实例。根据静态工厂方法的参数,工厂方法返还两个实例中的一个。Die对象的die方法代表掷骰子,这个方法会返还一个1到6之间的随机数,测试代码如下:
package com.javapatterns.multilingual.dice; public class Client { private static Die die1;private static Die die2; public static void main(String[] args) { die1 = Die.getInstance(1); die2 = Die.getInstance(2); die1.dice(); die2.dice(); } }
无上限多例模式
多例类的实例数目并不需要有上限,实例数目没有上限的多例模式就叫做无上限多例模式,可以用一个容器来储存。
自我理解多例模式就是单例的推广,自不过数目不同而已。
发表评论
-
设计模式记录(3.2)
2010-09-08 21:36 758第二十五章 合成模式 合成模型模式属于对象的结构模式, ... -
设计模式记录(3.1)
2010-09-08 13:16 716第四部分 结构模式 第二十二章 适配器模式 适配器模式 ... -
设计模式记录(2.4)
2010-09-06 13:35 786第十二章 原始模型模式 原始模型模式属于对象的创建模式 ... -
设计模式记录(2.3)
2010-09-03 23:20 767第十九章 建造模式 建造模式似乎对象的创建模式。建造模 ... -
设计模式记录(2.1)
2010-09-01 20:04 884第十二章 简单工厂模式 ... -
设计模式记录(1)
2010-08-31 19:05 903第四章 开闭原则 开闭原则讲的是:一个软件实体应该对扩展开放, ...
相关推荐
书名: 设计模式可复用面向对象软件的基础 英文原书名: Design Patterns:Elements of Reusable Object-Oriented software 作者: Erich Gamma 等 译者: 李英军 马晓星 蔡敏 刘建中 书号: 7-111-07575-7 页码: 254 定价...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 引言 设计模式是软件工程领域中一个极为重要的概念,它代表着一系列被广泛接受的解决特定问题的方法。GoF(Gang of Four)所提出的23种设计模式更是...
我们并不认为这组设计模式是完整的和一成不变的,它只是我们目前对设计的思考的记录。因此我们欢迎广大读者的批评与指正,无论从书中采用的实例、参考,还是我们遗漏的已知应用,或应该包含的设计模式等方面。你...
设计模式的学习和应用分为几个阶段:首先自己学会设计模式,然后将其转化为自己的语言表达出来,接着是教授他人并最终记录下来。这种学习路径要求学习者不仅要理解设计模式本身,还要具备清晰的表达能力和深刻的理解...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 0. 引言 设计模式是软件工程领域的一个重要概念,它为解决特定问题提供了一套标准的解决方案。《设计模式精解——GoF 23种设计模式解析及C++实现源码...
### Java设计模式的应用 #### 一、引言 在当今快速发展的软件开发领域,Java作为一门功能强大且灵活的语言,不仅拥有丰富的API资源,还能与强大的数据库系统无缝对接。这使得许多开发人员能够以模块化的形式构建...
### 设计模式精解——GoF 23种设计模式解析及C++实现源码 #### 0. 引言 ##### 0.1 设计模式解析(总序) 设计模式是面向对象编程中用于解决常见问题的一系列模板。它们为软件设计提供了标准化的解决方案,帮助...
本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 ...
### 设计模式精解—GoF 23种设计模式解析及C++实现源码 #### 0. 引言 设计模式作为一种重要的面向对象设计工具,在软件开发中扮演着至关重要的角色。本文旨在深入解析GoF(Gang of Four,四人组)提出的23种设计...
最出名的设计模式,语言诙谐明了。 目 录 序言 前言 读者指南 第1章 引言 1 1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决...
根据给定的信息,本文将深入探讨GoF23种设计模式的核心概念及其应用场景,并通过具体的实例来解析每一种设计模式的实现原理和技术要点。 ### 0. 引言 设计模式是一系列被广泛接受的解决方案,用于解决软件设计中...
1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...