进程视角看常用的几种设计模式
——Design Patterns
in Process View
田海立,系统分析师
2005年11月7日
摘要
设计模式是软件设计智慧的结晶,但是它们也有应用前提和使用限制。本文从运行时的视角,分析了多进程环境下使用几种常用设计模式的注意点。
关键词:设计模式,运行时,单例模式,侦听者模式
Keyword: Design Patterns, Run-time, Singleton Pattern, Listener Pattern
目 录
?TOC \o "1-3" \h \z
进程视角看常用的几种设计模式
——Design Patterns in Process View
田海立,系统分析师
2005年11月7日
一、设计模式
设计模式是什么?对不起,我也不记得它的确切定义,也许本来就没打算记住。即便有它“官方的”定义,看那定义半天也不一定能准确理解它的含义,过后不记得它也就很正常了。不过《Software Architect Bootcamp》上对关于它的“rule of three”描述我却记得非常清楚:解决某个特定问题所采取的设计,‘一次出现是偶然现象,两次是巧合,三次出现就是一个模式了’。所以用我的话说,就可以这么理解设计模式了,设计模式就是前人(当然是一头牛或者是一群牛了,比如GOF)对某个特定问题所经常采用的解决方案的总结,而这个设计是被实践反复证明合理的。因为设计模式是由权威的大师提出来,并被业界普遍接受,所以它也就成了交流的手段,在双方都很好地理解设计模式的情况下,对设计的交流就只需只言片语,而不是长篇大论还可能有交流的误区。
参考资料中GOF的著作是设计模式领域当之无愧的“圣经”,经过十年岁月的洗礼,现在仍然是亚马逊网上书店销量榜上的亚军(资料来源:《程序员》2005年11期,笔者并未进行证实)。设计模式在现在的软件系统中被普遍采用,你要看最近出现的软件的框架之类的东西,如果不懂设计模式恐怕是进行不下去的。我这里不赘述那些设计模式,而从进程视角(Process View是RUP中的概念,不过一般的软件架构模型中也都有类似的概念,可能的名字有Siemens常用架构的PIPE View或者CMU/SEI的Component-Connector View),探讨一下常用设计模式应用时应该注意的地方,这些模式在嵌入式系统多进程环境中被普遍采用,但应该注意这种环境下的特殊性。
二、运行时看常用设计模式
2.1Singleton(单例)模式
单例模式设计的意图(Intent)是“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”为了保证Singleton实例的唯一性,Singleton里定义一个类成员(有些实现语言不是这个概念)uniqueInstance,而把构造函数的修饰符声明成private或者protected(为了派生),使得别人无法通过它构造出一个新实例,而要得到一个Singleton的实例的话,必须通过Singleton暴露的getInstance()方法。
上面简要介绍了Singleton模式的设计思想。如果Singleton只被一个进程调用,它能够很好地工作,但是因为uniqueInstance是类的成员,并不是getInstance()里的局部变量,多个进程同时调用Singleton.getInstance()时,会出现竞争资源问题。考虑下面情况,初始状态uniqueInstance是null;进程P1执行了语句1;此时调度程序允许进程P2执行,进程2进入getInstance(),并能够执行1、2、3,所以创建了一个Singleton的实例;现在进程1重新被调度为执行,P1会从语句2开始执行,这样就又实例化了一个对象,跟我们设计的意图相违背。
要解决这个问题,就要保证getInstance()的任一时刻的单一进程可入。在用Java语言做实现时,只需加入synchronized关键字,就可以保证对同一个Singleton对象的互斥访问,如下图所示。在其他语言做实现时,只需要加入保证多个进程对getInstance()互斥访问的语句即可。
2.2 Listener/Observer模式
Listener模式的意图(Intent)是“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。”
Listener模式是Client把自己实现的Listener接口通过Subject的addListener()
/ removeListener()方法注册/注销感兴趣的事件,当所注册的事件发生时,相应的通知机制会通过notify()来调用之前所注册的Listener中的方法——这里是handleEvent(),注意这个方法是由Listener所实现的。这个模式被广泛应用于各种异步事件处理机制中,如果你认为有些没有采用,那极有可能只是它没有显式的提出是这个模式而已。注册事件的时机有可能是隐式的,可能在创建实例的时候,也可能在编译阶段就已经注册了。而Listener的形式也可能是Observer,Subscriber,callback,所以这个模式也就有了Observer,Subscriber模式的名字,等。
Listener是由异步事件通知系统之外的程序来实现的,这一般是应用程序,从runtime来看,有一个Client的进程。要实现异步通知Client为感兴趣的事件,一般异步事件通知系统会有一个Service进程,负责事件的分发和处理,事件处理过程是通过系统内维护的Listeners列表,找到应用程序实现的Listener接口,然后调用Listener的相应方法实现。可能你已经注意到我所说的重点了——Listener中的方法虽然实现在系统之外,但是它们却运行在系统Service进程的进程上下文中,如下图所示。
Listener接口中的方法是由系统调用运行在系统的进程上下文中的,你即不知道它们什么时候会被调用,也不知道它们会被谁(局部性原理,假设与不是你实现的东西都是通过协商一致并明确定义的接口来交互的)调用到,所以在写它的实现时要特别注意:1. 方法中用到Listener实现类的成员或者全局变量时,要注意对它们的临界访问问题;2. 方法中做到尽量快地完成返回,并且注意千万不要再在这些方法内调用可能引起系统内进程被重新调度的语句。
To be continued
三、总结
设计模式是软件设计中不断沉积演化的智慧的结晶,它是不断发展的。掌握现有的设计模式是我们设计出架构优美,结构清晰,表达明确的软件制品,以及保持与主流技术一致发展的前提。不过如果对它们一知半解,而没有全面理解其应用场景和范围以及设计时的限制和注意点,倒是还不如不知道这个概念,在没有设计模式概念之前,已经有实际采用了设计模式的优秀的软件作品。所以要用它学习消化它,就要尽可能全面掌握它,即便不能全面把握,限制和可能的不好的后果是首先要了解的。
参考资料
1.
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.
Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley
/ 机械工业,1995/2002.3
2.
Raphael Malveau, Thomas J.
Mowbray, Ph.D. Software Architect Bootcamp, Second Edition. Prentice Hall PTR, 2003-10-10
3.
Michael L. Scott著/裘宗燕译. Programming Language Pragmatics. Elsevier/电子工业出版社, 2005.3
关于作者
田海立,系统分析师。主要兴趣方向:嵌入式软件架构,Java/Eclipse,Linux技术。您可以通过 haili.tian@gmail.com 或 tianhaili@nju.org.cn 与他联系,到 http://blog.csdn.net/thl789/ or http://spaces.msn.com/members/thl789/
看他的文章。