申明:本系列文章中的大多数代码和部分文字来源于网络,版权归其所有人所有。
13.decorator
14.facade
15.iterator
16.observer
17.template method
13.Decorator 说明:我开始看这个模式的时候,看了好几遍C#代码描述的装饰模式,也许是我功力不行也许是代码过于简单(不是看不懂代码),我怎么也想不通它的好处。直到看了下面这个JAVA的例子才转过弯来。这是一种对子类化(这里指派生子类)的补充功能,在C#中用的是接口实现的,简言之,就是在不合适子类化的时候用下面的这种方法实现子类功能的扩展。C#的例子我就不给出了,大家仔细看这个JAVA的代码应该更容易理解。
实例: Java程序员知道可以通过扩展一个类来改变类的行为和扩展一个类的功能。这个行为被称为继承,它是面向对象编程的一个重要的特性.
举例来说,如果你想得到一个带有边框的Swing类型标签,你可以子类化javax.swing.JLabel类。然而,子类化并不总是有效。当继承不能解决问题的时候,你不得不求助与其它的方式。比如,使用Decorator模式。
这篇文章解释了Decorator模式是什么,并说明什么时候应该子类化,什么时候应该采用Decorate模式。
在Java语言中关键字extends被提供来子类化(扩展)一个类。具有丰富的面向对象编程经验的程序员知道子类化的威力。通过扩展一个类,我们能够改变这个类的行为。以列表1所讲的JBorderLabel类为例,它扩展了javax.swing.JLabel类,除了多了一个边框,它和JLabel类具有相同的外观和行为。
列表 1 -- the JBorderLabel class, an example of subclassing
package decorator;
import java.awt.Graphics; import javax.swing.JLabel; import javax.swing.Icon; public class JBorderLabel extends JLabel { public JBorderLabel() { super(); } public JBorderLabel(String text) { super(text); } public JBorderLabel(Icon image) { super(image); } public JBorderLabel(String text, Icon image, int horizontalAlignment) { super(text, image, horizontalAlignment); } public JBorderLabel(String text, int horizontalAlignment) { super(text, horizontalAlignment); } protected void paintComponent(Graphics g) { super.paintComponent(g); int height = this.getHeight(); int width = this.getWidth(); g.drawRect(0, 0, width - 1, height - 1); } }
要理解JBorderLabel如何工作,我们首先要了解Swing绘它的组件的原理。 JLabel类同其它的Swing组件一样,继承至javax.swing.Jcomponent.Swing。它们都是通过调用JComponent组件的paint方法来画界面。我们可以通过重载JComponent的公开方法paint来修改一个组件画界面的行为。下面是一个JComponent的paint方法的定义。
public void paint(Graphicsg)
作为paint方法的参数传进来的对象Graphics是一个绘图面板。为了优化绘图这个操作,paint方法被分割成三个具有保护(protected)属性的方法:paintComponent, paintBorder, paintChildren。paint方法调用这三个方法同时将它接受到的Graphics实例传递给这三个方法。下面是这三个函数的一个声明:
protected void paintComponent(Graphics g) protected void paintBorder(Graphics g) protected void paintChildren(Graphics g)
你可以通过重载这些方法来定制你自己的绘制组件的方式。
JBorderLabel类重载了javax.swing.JComponent的paintComponent方法。类JborderLabel的paintComponent方法首先调用父类的paintComponent得到一个Jlabel.它保持了自己的长和宽,通过java.awt.Graphics实例的drawRect方法画一个矩形。JBorderLabel类的一个实例除了多了一个边框外,它和JLabel外观是一样的。
这个例子中子类化工作得相当好。我们来看看子类化不合适的案例。如果你打算让其它的组件都具有同一行为(比如:画一个边框),那么你必须做很多的子类化操作。在列表1中,子类化看起来很简单是因为例子中你仅仅需要重载一个方法。当你有太多的子类需要创建时你的代码将变得很复杂,出错的机会也增大了。(你必需要复制(reproduce)你的子类需要支持的父类的构造函数,就像JBorderLabel类一样)。在这个时候,最好的方式是使用Decorator模式。
Decorator模式
在Erich Gamma等编写的《Design Patterns : Elements of Reusable Object-Oriented Software》一书中,Decorator模式被归类为结构模式。Decorator模式提供了子类化的一个替代方案。子类化和Decorator模式的主要区别是:采用子类化,你同一个类打交道;使用Decorator模式,你可以动态的修改多个对象。当你扩展(Extend)一个类的时候,你对儿子类的改变将会影响到这个儿子类所有的实例。采用Decorator模式,你所作的改变只会影响到你打算改变的那个对象。
理解JComponent类对于书写装饰者类很重要,我们通过这个装饰者类来改变Swing组件的用户界面。在前面部分我解释了JComponent是如何画它的用户界面的,我们可以通过文档查找来了解这个类的所有的成员。我们要意识到JComponent有子组件,当JComponent被画的时候,这些子组件也将被画。
创建一个从JComponent扩展过来的Swing装饰者。这个装饰者的构造函数接受一个类型为JComponent的参数。可以传递任一一个需要改变行为的Swing对象给装饰者。这个装饰者将传进来的这个组件作为自己的子组件。并不是直接将Swing组件增加到JFrame或JPannel或其它容器,而是先将Swing组件添加到修饰者,再把修饰者增加给容器类。因为一个修饰者也是一个JComponent类型的对象,容器不能将他们区分开来。这个装饰者是这个容器的一个子组件。当容器让装饰者重画的时候,这个装饰者paint方法将被调用。
举例来说,假设你有一个JLabel类,你打算把它传给一个称之为frame1的JFrame类。使用如下相似的代码:
frame.getContentPane().add(new JLabel("a label"));
用MyDecorator来修饰JLabel的代码和它很相似,如下:(记住,MyDecorator类的构造函数应该接受一个JComponent类的输入参数)
frame.getContentPane().add(new MyDecorator(new JLabel("a label")));
这篇文章示例了两个Decorator模式的例子。第一个例子是BorderDecorator.这个类被用来修饰JComponent,以便让JComponent具有一个边框。当把一个由BorderDecorator修饰的JLabel增加到JFrame,这个JLabel看起来就像JBorderLabel的一个实例。这说明,子类化不是必须的。更好的是,你能够传递任何一个Swing组件给BorderDecorator,这些被传递的组件都会给予一个边框。在这个例子中,通过创建了一个类BorderDecorator来改变不同类型的实例的行为。
第二个例子是ResizableDecorator。这个装饰着为每一个传给它的Swing组件增加一个小按钮到左上角。当用户点击这个按钮的时候,这个组件将会最小化为这个按钮。
BorderDecorator类
我们以BorderDecorator开始。这个类表示的装饰者会为Swing组件增加一个边框。示例代码如列表2
列表2 -- the BorderDecorator class
package decorator; import javax.swing.JComponent; import java.awt.Graphics; import java.awt.Color; import java.awt.BorderLayout; public class BorderDecorator extends JComponent { // decorated component protected JComponent child;
public BorderDecorator(JComponent component) { child = component; this.setLayout(new BorderLayout()); this.add(child); }
public void paint(Graphics g) { super.paint(g); int height = this.getHeight(); int width = this.getWidth(); g.drawRect(0, 0, width - 1, height - 1); } } 注意,这个BorderDecorator扩展了JComponent,它的构造函数接受一个JComponet类型的参数。这个BorderDecorator类有一个类型为JComponent的属性child,它是传进来的Jcomponent对象的一个引用。
14.facade 说明:facade是门面的意思,顾名思义,为子系统中的一组接口提供一个一致的界面。这个模式大家在不知不觉中都在使用,打个比方,如果有100行代码,包含了5种功能,你自然会将它写成5个函数,然后用一个主函数安排它们的逻辑关系。每个函数的参数列表都是一个接口,你在使用函数的时候不用关心它的内部流程,只关心接口。同样下面的JAVA例子用大家都很熟悉的数据库操作阐述了这种模式。 实例: Facade一个典型应用就是数据库JDBC的应用,如下例对数据库的操作: public class DBCompare { Connection conn = null; PreparedStatement prep = null; ResultSet rset = null; try { Class.forName( "<;driver>;" ).newInstance(); conn = DriverManager.getConnection( "<;database>;" ); String sql = "SELECT * FROM <;table>; WHERE <;column name>; = ?"; prep = conn.prepareStatement( sql ); prep.setString( 1, "<;column value>;" ); rset = prep.executeQuery(); if( rset.next() ) { System.out.println( rset.getString( "<;column name" ) ); } } catch( SException e ) { e.printStackTrace(); } finally { rset.close(); prep.close(); conn.close(); } } 上例是Jsp中最通常的对数据库操作办法。 在应用中,经常需要对数据库操作,每次都写上述一段代码肯定比较麻烦,需要将其中不变的部分提炼出来,做成一个接口,这就引入了facade外观对象。如果以后我们更换Class.forName中的<;driver>;也非常方便,比如从Mysql数据库换到Oracle数据库,只要更换facade接口中的driver就可以。 我们做成了一个Facade接口,使用该接口,上例中的程序就可以更改如下: public class DBCompare { String sql = "SELECT * FROM <;table>; WHERE <;column name>; = ?"; try { Mysql msql=new mysql(sql); prep.setString( 1, "<;column value>;" ); rset = prep.executeQuery(); if( rset.next() ) { System.out.println( rset.getString( "<;column name" ) ); } } catch( SException e ) { e.printStackTrace(); } finally { mysql.close(); mysql=null; } } 可见非常简单,所有程序对数据库访问都是使用该接口,降低系统的复杂性,增加了灵活性。 如果我们要使用连接池,也只要针对facade接口修改就可以。

由上图可以看出,facade实际上是个理顺系统间关系,降低系统间耦合度的一个常用的办法,也许你已经不知不觉在使用,尽管不知道它就是facade。
15.iterator 说明:这是使用最容易的模式,因为不管是在JAVA还是C#中它都已经被整合到了类库的集合类中,在JAVA中这个模式已经被整合入Java的Collection.在大多数场合下无需自己制造一个Iterator,只要将对象装入Collection中,直接使用Iterator进行对象遍历。在C#中只要实现IEnumerable 与 IEnumerator接口即可。实现起来也非常简单,如下: class List: IEnumerable { private T[] elements; public IEnumerator GetEnumerator() { foreach (T element in elements) { yield element; } } }
16.Observer 说明:看了这个模式我不禁微笑起来,是不是很容易让人想到WINDOWS的消息机制呢,不同的是WINDOWS的消息由系统的消息队站负责分发。我想起以前自己在多个对象之间相互交换信息的时候总是向windows消息模式靠,导致构造一个类似Windows消息分发程序很困难,(不仅仅是用一个线程+列表那么简单,对象之间的参数和线程对象的访问都会带来问题),虽然以前也很自然的想到过这种模式,但是总认为很麻烦所以就没有深想,现在看到下面这个例子,感觉要比构造一个消息分发器要容易的多,参数传递也容易了,唯一不足的是不具备通用性(想具备通用性估计要提升到类库的支持)。用C#的Delegate实现这种模式很方便,看下面的例子。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
实例: 在编写多层应用程序时,我们通常将表示层和数据逻辑层分隔开,比如很常见的View/Document结构,这种设计方式的好处带来的结果通常是使用多视图同时表示单一数据源,比如一个Web网站可以方便的同时拥有针对电脑的Html页面和针对手机的WAP页面。使用这种结构时,为了保持数据显示的一致性,必须要求数据源在数据发生改变时能及时的逐一通知每一个和它绑定的表示层进行更新。但是问题在于数据层本身并不知道到底有多少个不同的表示层正在反映着它的数据内容。因此需要设计一套有效的机制来完成这个目标。 模式实现 我们先看看来自《设计模式迷你手册》的常规的C#实现代码。 Subject(抽象目标): 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
//实现代码 class Subject { //由于不知道有多少个观察者,所以建立了一个观察者链表 private ArrayList list = new ArrayList(); private string strImportantSubjectData = "Initial"; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Attach(Observer o) { list.Add(o); o.ObservedSubject = this; } public void Detach(Observer o) { } public void Notify() { //在数据发生改变后遍历列表通知观察者 foreach (Observer o in list) { o.Update(); } } }
Observer(抽象观察者): 为那些在目标发生改变时需要获得通知的对象定义一个更新接口。 abstract class Observer { //内置一个需要观察的对象 protected Subject s; public Subject ObservedSubject { get { return s; } set { s = value; } } abstract public void Update(); }
ConcreteSubject(实体目标,在这里相当于数据逻辑层): 将有关状态存入各ConcreteSubject对象。 当它的状态发生改变时,向它的各个观察者发出通知。 //在这里基本上什么都没有做,数据的获取可以放到GetState()里面 class ConcreteSubject : Subject { public void GetState() { } public void SetState() { } }
ConcreteObserver(实体观察者,在这里就相当于表示层): 维护一个指向ConcreteSubject的引用。 储存有关状态,这些状态应与目标的状态保持一致。 实现Observer的更新接口以使自身状态与目标状态保持一致。 class ConcreteObserver : Observer { private string observerName; public ConcreteObserver(string name) { observerName = name; } override public void Update() { //将数据显示出来 Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, s.ImportantSubjectData); } }
主函数: public class Client { public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); ConcreteObserver o1 = new ConcreteObserver("first observer"); ConcreteObserver o2 = new ConcreteObserver("second observer"); //注册观察者 s.Attach(o1); s.Attach(o2); s. ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; } }
模式分析 Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。 C#作为一种先进的现代面向对象语言,不但吸收了许多语言的精华,并创造了一些非常有用的新特性。在学习了C#语言之后,我发现利用C#独有的Delegate可以来较好的解决这个问题。 改进后的Observer模式实现 先定义一个Delegate: delegate void UpdateDelegate(string SubjectData);
Subject(抽象目标): class Subject { private string strImportantSubjectData = "Initial"; //定义一个事件容器,代替前面的观察者对象列表 public event UpdateDelegate UpdateHandle; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Notify() { //发出事件 if(UpdateHandle != null) UpdateHandle(strImportantSubjectData); } }
Observer(抽象观察者): 无,因为不需要抽象接口类了,所以可以省去抽象观察者类。 ConcreteSubject(实体目标): //没有任何改变 class ConcreteSubject : Subject { public void GetState() { } public void SetState() { } }
ConcreteObserver(实体观察者): //为了能更加清楚的说明问题,这里定义了两个实体观察者,注意,它们之间并没有任何关系 class Observer1 { private string observerName; public Observer1(string name) { observerName = name; } public void Update1(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } } class Observer2 { private string observerName; public Observer2(string name) { observerName = name; } public void Update2(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } }
主函数: public class Client { public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); Observer1 o1 = new Observer1("first observer"); Observer2 o2 = new Observer2("second observer"); //向目标注册对象两个观察者,请注意,这里仅仅只是添加了两个方法, //不需要关心方法从何而来,也不需要关心目标类如何去调用他们。 s.UpdateHandle += new UpdateDelegate(o1.Update1); s.UpdateHandle += new UpdateDelegate(o2.Update2); s.ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; } }
在这段代码里,没有看到链表,没有看到遍历操作,没有看到更新的方法是如何被调用,甚至没有看到那些被联系到一起的类的抽象类和抽象接口,但是目标对象却能将数据更新信息逐一发送到每个观察者对象,并且还能更加容易的增加新的不同的观察者对象,根本不需要知道它从何处继承而来,也不需要统一他们的接口调用方法。这一切都归功于灵活强大的C#。 完整的源码 以下是完整的源码: namespace Observer_DesignPattern { using System; delegate void UpdateDelegate(string SubjectData); class Subject { private string strImportantSubjectData = "Initial"; public event UpdateDelegate UpdateHandle; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Notify() { if(UpdateHandle != null) UpdateHandle(strImportantSubjectData); } } class ConcreteSubject : Subject { public void GetState() { } public void SetState() { } } class Observer1 { private string observerName; public Observer1(string name) { observerName = name; } public void Update1(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } } class Observer2 { private string observerName; public Observer2(string name) { observerName = name; } public void Update2(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } } public class Client { public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); Observer1 o1 = new Observer1("first observer"); Observer2 o2 = new Observer2("second observer"); s.UpdateHandle += new UpdateDelegate(o1.Update1); s.UpdateHandle += new UpdateDelegate(o2.Update2); s.ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; } } }
16.Template Method 说明:定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.非常简单,大家自己看下面的实例吧!
实例: 使用Java的抽象类时,就经常会使用到Template模式,因此Template模式使用很普遍.而且很容易理解和使用。 public abstract class Benchmark { /** * 下面操作是我们希望在子类中完成 */ public abstract void benchmark(); /** * 重复执行benchmark次数 */ public final long repeat (int count) { if (count <= 0) return 0; else { long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) benchmark();
long stopTime = System.currentTimeMillis(); return stopTime - startTime; } } }
在上例中,我们希望重复执行benchmark()操作,但是对benchmark()的具体内容没有说明,而是延迟到其子类中描述:
public class MethodBenchmark extends Benchmark { /** * 真正定义benchmark内容 */ public void benchmark() {
for (int i = 0; i < Integer.MAX_VALUE; i++){ System.out.printtln("i="+i); } } }
至此,Template模式已经完成,是不是很简单?
我们称repeat方法为模板方法, 它其中的benchmark()实现被延迟到子类MethodBenchmark中实现了,
看看如何使用:
Benchmark operation = new MethodBenchmark(); long duration = operation.repeat(Integer.parseInt(args[0].trim())); System.out.println("The operation took " + duration + " milliseconds");
也许你以前还疑惑抽象类有什么用,现在你应该彻底明白了吧? 至于这样做的好处,很显然啊,扩展性强,以后Benchmark内容变化,我只要再做一个继承子类就可以,不必修改其他应用代码.

|