设计模式一点通之单态模式
 

2009-03-10 作者:陈兵 来源:网络

 

程序设计是思维具体化的一种方式,是思考如何解决问题的过程,设计模式是在解决问题的过程中,一些良好思路的经验集成,最早讲设计模式,人们总会提到 Gof 的著作,它最早将经典的 23 种模式集合在一起说明,对后期学习程序设计,尤其是对从事面向对象程序设计的人们起了莫大的影响。

设计模式并不直接用来完成程序的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。算法不能算是一种设计模式,因为算法主要是用来解决计算上的问题,而非设计上的问题。设计模式主要是使不稳定的依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。

后来设计模式一词被广泛的应用到各种经验集成,甚至还有反模式(AntiPattern),反模式教导您如何避开一些常犯且似是而非的程序设计思维。

发展历史

建筑师克里斯托佛?亚历山大在1977/79年编制了一本汇集设计模式的书。但是这种设计模式的思想在建筑设计领域里的影响远没有后来在软件开发领域里传播的广泛。

肯特·贝克和沃德?坎宁安在1987年,利用克里斯托佛?亚历山大在建筑设计领域里的思想开发了设计模式并把此思想应用在Smalltalk中的图形用户接口的生成中。一年后Erich Gamma在他的苏黎世大学博士毕业论文中开始尝试把这种思想改写为适用于软件开发。于此同时James Coplien 在1989年至1991 年也在利用相同的思想致力于C++的开发,而后于1991年发表了他的著作Advanced C++ Idioms。就在这一年Erich Gamma 得到了博士学位,然后去了美国,在那与Richard Helm, Ralph Johnson ,John Vlissides 合作出版了Design Patterns - Elements of Reusable Object-Oriented Software一书,在此书中共收录了23个设计模式。

这四位作者在软件开发领域里也以他们的匿名著称Gang of Four(四人帮,简称GoF),并且是他们在此书中的协作导致了软件设计模式的突破。有时这个匿名GoF也会用于指代前面提到的那本书。

表述格式

表述一个软件设计模式的格式根据作者的不同,划分和名称等都会有所不同。常用的GoF描述模式的格式大致分为以下这些部分:

  • 模式名:每一个模式都有自己的名字,模式的名字使得我们可以讨论我们的设计。
  • 问题:在面向对象的系统设计过程中反复出现的特定场合,它导致我们采用某个模式。
  • 解决方案:上述问题的解决方案,其内容给出了设计的各个组成部分,它们之间的关系、职责划分和协作方式。
  • 别名:一个模式可以有超过一个以上的名称。这些名称应该要在这一节注明。
  • 动机:该模式应该利用在哪种情况下是本节提供的方案(包括问题与来龙去脉)的责任。
  • 适用性:模式适用于哪些情况、模式的背景等等。
  • 结构:这部分常用类图与互动图阐述此模式。
  • 参与者:这部分提供一份本模式用到的类与物件清单,与它们在设计下扮演的脚色。
  • 合作:描述在此模式下,类与物件间的互动。
  • 影响:采用该模式对软件系统其他部分的影响,比如对系统的扩充性、可移植性的影响。影响也包括负面的影响。这部分应描述使用本模式后的结果、副作用、与权衡(trade-off)
  • 实作:这部分应描述实现该模式、该模式的部分方案、实现该模式的可能技术、或者建议实现模式的方法。
  • 示例:简略描绘出如何以编程语言来使用模式。
  • 已知应用:业界已知的实作范例。
  • 相关模式:这部分包括其他相关模式,以及与其他类似模式的不同。

分类

设计模式分为创建型模式,结构型模式,行为型模式.把它们通过授权,聚合,诊断的概念来描述,并增加几个导入或衍生的简单模式。

Creational(创建型) 模式

对象的产生需要消耗系统资源,所以如何有效率的产生、管理与操作对象,一直都是值得讨论的课题,Creational 模式即与对象的建立相关,在这个分类下的模式给出了一些指导原则及设计的方向。
  o Simple Factory 模式
  o Abstract Factory 模式
  o Builder 模式
  o Factory Method 模式
  o Prototype 模式
  o Singleton 模式
  o Registry of Singleton 模式

Structural(结构型) 模式

如何设计对象之间的静态结构,如何完成对象之间的继承、实现与依赖关系,这关乎着系统设计出来是否健壮(robust):像是易懂、易维护、易修改、耦合度低等等议题。Structural 模式正如其名,其分类下的模式给出了在不同场合下所适用的各种对象关系结构。
  o Default Adapter 模式
  o Adapter 模式 - Object Adapter
  o Adapter 模式 - Class Adapter
  o Bridge 模式
  o Composite 模式
  o Decorator 模式
  o Facade 模式
  o Flyweight 模式
  o Proxy 模式(一)
  o Proxy 模式(二)

Behavioral(行为型)模式

对象之间的合作行为构成了程序最终的行为,对象之间若有设计良好的行为互动,不仅使得程序执行时更有效率,更可以让对象的职责更为清晰、整个程序的动态结构(像是对象调度)更有弹性。
  o Chain of Responsibility 模式
  o Command 模式
  o Interpreter 模式
  o Iterator 模式
  o Mediator 模式
  o Memento 模式
  o Observer 模式
  o State 模式
  o Strategy 模式
  o Template Method 模式
  o Visitor 模式

● 多执行绪模式

在很多应用中都会使用多执行绪,尤其是在Web应用中,多执行绪以 Gof 整理的模式为基础,考虑多执行绪环境中,如何组合这些基本模式来完成多执行绪安全要求。
  · Guarded Suspension 模式
  · Producer Consumer 模式
  · Worker Thread 模式
  · Thread-Per-Message 模式
  · Future 模式
  · Read-Write-Lock 模式
  · Two-phase Termination 模式
  · Thread-Specific Storage 模式

这里我将整理一些常用设计模式学习心得,以比较易懂的方式进行讲解,便以读者理解。实现的部份是使用Java,因而您会看到一些与 Gof 模式不同的图及实现方式,这是为了善用一些Java本身的特性,至于C++的实现方面,Gof 的书已经给了不少的例子。

单态模式(Singleton)

Singleton模式主要作用是保证在应用程序中,一个类Class只有一个实例存在。很多时候,您会需要Singleton模式,例如打印机管理,您希望程序中只能有一个Print Spooler,以避免两个打印动作同时输入至打印机中;例如数据库管理,因为建立连接(Connection)对象会耗用资源,您希望程序中只能有一个连接对象,所有其它的程序都透过这个对象来连接数据库,以避免连接对象的重复开启造成资源的耗用;例如系统程序属性文件的读取,您使用单一个对象来读取属性内容,而程序的其它部份都向这个对象要求属性数据,而不是自行读取属性数据。

使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。

要理解单态模式,首先看看我们通常(不考虑“单态”是的做法)是怎样对类进行操作的:

例如,我们有一个类,这个类只有一个方法,就是说一句话“Hello 这是一个singleton的例子”。我们用eclipse等开发工具生成相应的程序框架并编写类Single:

运行程序,输出结果是:

但是现在我有一个要求,就是说在这个程序中,只能够产生唯一的一个Single对象,哪么这应该怎么办呢?

好,大家知道,所有对象在它进行其实例化时都要调用构造方法,那么我们就在构造方法这个地方着手,我们把构造方法卡死,让public的构造方法改成私有的,我们来看一下

这个时候我们来看一下运行程序,出现错误,说Single是私有的(见下图):

因为构造函数已经变成“私有的”,当然也就不能够在外部去new了,这个时候private Single()只对Single类内部可见,外部是不可见的。

那么我们现在想一下,既然我们不能在外面new,那么我们能不能在外面声明一下呢?我们只声明,不new,看会不会出现错误呢?看下面的程序改动:

再运行程序,可以发现没有任何错误,这说明什么?

既然private Single()只能够在Single类内可见,那么我们就想能不能就在Single类内部实例化一个Single对象看行不行:

运行程序,可以发现没有任何错误。说明虽然构造函数是私有的,但是还是可以在Single类内部去实例化一个Single对象的。那么这个时候,我希望在外部也能够得到这个s1对象,那么该怎么办?因为本类的对象没有办法在外部实例化了,那么我们就想到只能够通过类的名称去取得这个属性,因此我们在声明s1对象的时候,前面加一个static(静态的)注1,现在我们就能够通过static方法、通过类名Single访问这个Single实例:

再运行一次,没有任何问题,成功调用到了这个方法(参见下图)。

注1:有时你希望定义一个类成员,使它的使用完全独立于该类的任何对象。通常情况下,类成员必须通过它的类的对象访问,但是可以创建这样一个成员,它能够被它自己使用,而不必引用特定的实例。在成员的声明前面加上关键字static(静态的)就能创建这样的成员。如果一个成员被声明为static,它就能够在它的类的任何对象创建之前被访问,而不必引用任何对象。你可以将方法和变量都声明为static。static 成员的最常见的例子是main( ) 。因为在程序开始执行时必须调用main() ,所以它被声明为static。

但是这个时候我们可能想到另外一个问题:在类中,一切属性都必须去封装(用private去封装它),也就是在static前面再加上private看看会出现什么情况:运行程序,依然报错误。为什么呢?还是刚才呢个问题,这个时候我们怎么办呢?--它被封装之后,又调用不到了?这时候我们就想,既然这个实例(对象)不能在外面调用了,哪么我们再写一个方法,我们让这个方法向外部返回一个实例化对象,这样应该没有问题,那我们就来写一个这样的方法,这个方法给外界返回一个对象:

运行程序,没有任何问题.

但是,虽然static Single s1 = new Single()声明为static了,而实际上依然可以重复声明s1这个Single对象,哪么我们就应该再加上一个关键字:final注2,这样以来就只能声明一次了。现在在整个程序中就只有一个Single对象s1。这就是一个简单的单态设计模式。完整的程序如下:

注2:Java多线程对每个线程都有自己的栈。因此对于在线程控制流中的调用函数,它的内部变量不会相互影响。但是,对于多个线程可以同时访问到的对象,Java为了安全,必须强制程序员将其声明为final,以避免可能出现的竞争问题。

上面我们从问题、解决方案、动机、适用行扥方面着手,一步一步地给大家讲解了singleton模式的来龙去脉,这样会给容易去理解。在学习别的设计模式的时候,大家可以采取类似的方法,就可以比较容易地理解了。

当然,在实际使用singleton模式的时候,还要根据实际情况(编程语言、架构等)去考虑可能存在的问题并加以解决。

使用Singleton注意事项:

一个单例类可以是有状态的(stateful),一个有状态的单例对象一般也是可变(mutable) 单例对象。有状态的可变的单例对象常常当做状态库(repositary)使用。比如一个单例对象可以持有一个int 类型的属性,用来给一个系统提供一个数值惟一的序列号码,作为某个贩卖系统的账单号码。当然,一个单例类可以持有一个聚集,从而允许存储多个状态。

另一方面,单例类也可以是没有状态的(stateless), 仅用做提供工具性函数的对象。既然是为了提供工具性函数,也就没有必要创建多个实例,因此使用单例模式很合适。一个没有状态的单例类也就是不变(Immutable) 单例类。

有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的EJB 容器有能力将一个EJB 的实例跨过几个JVM 调用。由于单例对象不是EJB,因此,单例类局限于某一个JVM 中。换言之,如果EJB 在跨过JVM 后仍然需要引用同一个单例类的话,这个单例类就会在数个JVM 中被实例化,造成多个单例对象的实例出现。一个J2EE应用系统可能分布在数个JVM 中,这时候不一定需要EJB 就能造成多个单例类的实例出现在不同JVM 中的情况。

如果这个单例类是没有状态的,那么就没有问题。因为没有状态的对象是没有区别的。但是如果这个单例类是有状态的,那么问题就来了。举例来说,如果一个单例对象可以持有一个int 类型的属性,用来给一个系统提供一个数值惟一的序列号码,作为某个贩卖系统的账单号码的话,用户会看到同一个号码出现好几次。

因此在任何使用了EJB、RMI 和JINI 技术的分散式系统中,应当避免使用有状态的单例模式。

另外,同一个JVM 中会有多个类加载器,当两个类加载器同时加载同一个类时,会出现两个实例。在很多J2EE 服务器允许同一个服务器内有几个Servlet 引擎时,每一个引擎都有独立的类加载器,经有不同的类加载器加载的对象之间是绝缘的。

比如一个J2EE 系统所在的J2EE 服务器中有两个Servlet 引擎:一个作为内网给公司的网站管理人员使用;另一个给公司的外部客户使用。两者共享同一个数据库,两个系统都需要调用同一个单例类。如果这个单例类是有状态的单例类的话,那么内网和外网用户看到的单例对象的状态就会不同。除非系统有协调机制,不然在这种情况下应当尽量避免使用有状态的单例类。

Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类、线程 、内存等概念有相当的了解。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织