设计模式第一次是由架构设计师 Christopher Alexander 在他所著的 A Pattern Language: Towns, Buildings, Construction(Oxford University Press,1977)一书中提到的。它是对于反复出现设计问题的抽象解决方案,一个设计模式不是一种可以直接转变成代码的最终设计,它只是一种如何解决在多个不同场景下的问题的模板或描述。设计模式的开创性文献是 GOF95,文献中提到了 23 种经典设计模式,之后,基于这 23 种模式又扩展出了多种用于解决特定问题的设计模式。下面我们介绍两种从观察者模式和访问者模式扩展而来的模式:QueueObserver 模式和 Extension Object 模式。
在介绍 QueueObserver 模式前,我们先了解下常用的观察着模式。它的定义如下:观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象。这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新。类图如下:
当然,我们可以遵照上述方式实现自己的观察者模式。但是,Java 语言已经内置了观察者模式的
支持。在 Java 语言的 java.util 库里面,提供了一个 Observable 类以及一个 Observer 接口,构成 Java 语言对观察者模式的支持。通常,我们在 Java 中实现一段观察者模式的代码如下:
import java.util.Observable; import java.util.Observer; import java.util.Observable; public class SimpleTest { public static void main(String[] args){ SimpleObservable doc = new SimpleObservable ("Hello Java"); SimpleObserver view = new SimpleObserver (doc); doc.changeInstate("Hello Scala"); } } // 被观察者 class SimpleObservable extends Observable { private String inState; public SimpleObservable(String inState) { this.inState = inState; } public String getState() { return inState; } public void changeInstate(String newState) { if (!newState.equals(inState)) { this.inState = newState; //setChanged() 后 notifyObservers() 才生效 setChanged(); } notifyObservers(); } } // 观察者 class SimpleObserver implements Observer { public SimpleObserver(SimpleObservable so){ so.addObserver(this); } public void update(Observable o,Object arg){ System.out.println("Data has changed to" + ((SimpleObservable) o).getState()); } }
1. 我们考虑一种常见的场景:有多个 Observer 对象同时观察一个 Observable 对象,并且每个 Observer 对象的 update(Observable o, Object arg) 方法都是一个耗时操作,比如更新数据库。此时当我们调用 Observable 对象的 notifyobservers() 方法后,方法会一直阻塞,直到 Observable 关联的所有的 Observer 对象执行完它们各自的 update 方法后才能恢复执行。假设我们有 3 个观察者,每个观察者的 update 方法要进行一个 3 秒的耗时操作,类似下面的代码就需要 9 秒左右才能结束运行:
MyObservable mo = new MyObservable("Hello Java"); Observer1 o1 = new Observer1(); o1.attachObservable(mo); Observer2 o2 = new Observer2(); o2.attachObservable(mo); Observer3 o3 = new Observer3(); o3.attachObservable(mo); // 该方法会阻塞 9 秒,直到 3 个观察者 o1,o2,o3 依此执行完各自的 update 方法 mo.changeInstate("Hello Spark");
2. 在一些高并发并且对实时要求很高的环境下,这很显然是不符合要求的。有没有可能让 notifyObservers 成为一个异步调用且
改进方法本身?答案是肯定的,我们将介绍一种新的设计模式:QueueObserver
3. 基本的设计思路是实现一个新的对象 ObserverQueue,它用于将原先的 Observable 和 Observer 对象解耦。首先,它被设计为一个线程对象,另外让它同时成为一个观察者和一个被观察者 (通过同时继承 Observable 对象和实现 Observer 接口的方式)。作为观察者,它可以观察原先的 Observable 对象,当 Obervable 对象变化后,消息只会发送给 ObserverQueue 这个观察者对象,并且由于它的 update() 方法只是将变化的消息对象加入一个私有变量 Queue 中,因此 Observable 对象的 notifyObservers 方法不会被阻塞。另一方面,作为被观察者,它会把变化消息传递给原先的多个观察者,传递的方式是通过将其作为一个常运行的线程在其 run 方法中将之前放入队列中的消息取出并发送给各个观察者。类图如下:
import java.util.Observable; import java.util.Observer; import java.util.LinkedList; import java.util.List; public class ObserverQueue extends Observable implements Observer, Runnable { //queue 用于存放消息 private LinkedList queue = new LinkedList(); public void run() { while(true) { Object o = get(); setChanged(); notifyObservers(o); } } public void attachObservable(Observable watched) { watched.addObserver(this); } public void update(Observable o, Object arg) { queue.add(arg); } public synchronized Object get() { try { while (queue.size() == 0) this.wait(); } catch (InterruptedException ex) { ex.printStackTrace(); } return queue.removeFirst(); } }
ObserverQueue oq = new ObserverQueue(); Thread threadPacker = new Thread(oq); Observer1 o1 = new Observer1(); o1.attachObservable(oq); Observer2 o2 = new Observer2(); o2.attachObservable(oq); Observer3 o3 = new Observer3(); o3.attachObservable(oq); MyObservable mo = new MyObservable("A"); threadPacker.start(); oq.attachObservable(mo); // MyObservable 的 notifyObservers() 会立即返回 mo.changeInstate("B");
同样,我们首先看看经典的访问者模式的定义:表示一个作用于某对象结构中的各元素的操作。它使您可以在不改变各元素类的前提下定义作用于这些元素的新操作。类图如下:
1. 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
2. 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。
3. 元素角色(Element):定义一个 Accept 操作,它以一个访问者为参数。
4. 具体元素角色(Concrete Element):实现由元素角色提供的 Accept 操作。
5. 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
1. 一个对象结构包含很多类对象,它们有不同的接口,而您想对这些对象实施一些依赖于其具体类的操作。
2. 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而您想避免让这些操作“污染”这些对象的类。Visitor 使得您可以将相关的操作集中起来定义在一个类中。
3. 当该对象结构被很多应用共享时,用 Visitor 模式让每个应用仅包含需要用到的操作。
4. 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
1. 我们已经知道了访问者模式的定义,下面我们结合一个实例分析如何将访问者模式实际应用到一个场景中。同时,观察访问者模式无法解决的一些场景,并以此引出 Extension Object 模式
2. 假设有调制解调器 (Moderm) 对象的层次结构,如下图:
同样,假设有一个需求,要向该调制解调器增加一个方法,名为 configureForUnix,这个方法会对调制解调器进行配置,使之可以工作于 UNIX 操作系统中,在每个调制解调器的子类中,该函数的实现都不同。
3. 解决方案:我们可以向 Moderm 接口增加一个 configureForUnix 方法,并让不同的子类进行不同的实现。但是这样的解决方法有一个明显的弊端,就是如果今后需要增加针对 Windows 操作系统的方法 configureForWindow,针对 Mac OS 操作系统的方法 configureForMac 该怎么办?这导致永远无法封闭 Moderm 接口,违反了面向对象设计的最基本的开-闭原则。
4. 我们能否不在接口 Moderm 中增加 ConfigureForUnix 方法,并且让这些子类仍然可以在 Unix 中使用呢?答案是肯定的,这里就可以用到访问者模式。通过增加一个访问者接口,以及在 Moderm 接口中增加 accpet 方法来实现,类图如下:
5. 从类图中可以看出,当构建完成访问者模式后。如果要增加新的操作系统配置函数,就可以通过增加新的 ModermVisitor 派生类来实现,而完全不用对 Moderm 层次结构进行更改。
6. 假设经过若干次迭代后,对象 UnixModermConfigure 中针对不同 Moderm 子类的 visit 方法实现已经越来越复杂,它们的代码混合在类 UnixModermConfigure 中,当我们想分离每种 Moderm 类型和其针对 Unix 的系统的配置方法的逻辑时,访问者模式已经无法再满足该需求。为此,我们需要使用一种新的设计模式:Extension Object 模式
1.Extension Object 模式也是一种不更改类层次结构时, 向其中新增功能的设计模式。它的层次结构中每一个对象都持有一个特定扩展对象的 Map,通过扩展对象提供了操作原始层次结构对象的方法。Extension Object 模式类图如下:
2. 在原有的 Moderm 类层次结构中,我们增加了一个 AbstractModerm 抽象类。它有一个实例变量用于存储每个 Moderm 子类对应的 Extension 对象。MordermExtension 是一个标记接口,它的子接口 ConfigureForExtension 有一个对对 Unix 系统的配置的方法 configure,该接口的每个子类对应响应的 Moderm 子类对象。在每个子类初始化的时候,将各自对应的 Extension 对象进行注册,在运行时可以方便的得到合适的扩展来执行相应功能。
3. 我们可以通过定义多个 ModermExtension 的子接口从而为 Moderm 类层次结构实现多种功能,如增加调制解调器缓存功能,可以通过定义子接口 CacheExtension 来实现。
public abstract class AbstractModerm implements Moderm { private Map<String, ModermExtension> its =new HashMap<String, ModermExtension>(); public void addExtension(String name, ModermExtension extension) { its.put(name, extension); } public ModermExtension getExtension(String name) { ModermExtension extension = its.get(name); if (extension == null) return new BadExtension(); return extension; } }
public class Main { public static void main(String[] args) { AbstractModerm hayez = new Hayez(); HayezConfigureForUnixExtension extension = (HayezConfigureForUnixExtension)hayez.getExtension("unix"); extension.confgiure(); ModermExtension badEx = hayez.getExtension("non-existent"); if (badEx instanceof BadExtension) { System.out.println("The extesion object is not exits"); } } }
可以看到,在运行时可以动态得到合适的扩展来执行相应功能。如果我们获取一个不存在的扩展对象时,并不会返回 null,而是返回一个 BadExtension 对象来表示不存在的扩展对象
以上介绍的两种模式是在 23 种基本设计模式之上扩展而来,用于解决一些特殊的场景下遇到的问题。在面向对象领域里,尽管有很多种设计模式,但是它们都或多或少是遵循了面向对象设计的几个基本原则:单一职责原则 (Single Responsiblity Principle SRP)、开闭原则(Open Closed Principle,OCP)、里氏代换原则(Liskov Substitution Principle,LSP)、依赖倒转原则(Dependency Inversion Principle,DIP)、接口隔离原则(Interface Segregation Principle,ISP)、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)、最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)。因此,我们也不必拘泥于设计模式的形式,对一些设计模式生搬硬套,只要代码能遵循上面提到的几个基本的原则,同时这些代码可以在一些相似的场景复用,就可以把这些代码抽象出来作为一种设计模式。