SOLID 软件设计原则
Initial |
Stands for |
Concept |
S |
SRP |
Single responsibility principle The notion that an object should have only a single responsibility. |
O |
OCP |
Open/closed principle The notion that “software entities … should be open for extension, but closed for modification”. |
L |
LSP |
Liskov substitution principle The notion that “objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program”. See also design by contract. |
I |
ISP |
Interface segregation principle The notion that “many client specific interfaces are better than one general purpose interface. |
D |
DIP |
Dependency inversion principle The notion that one should “Depend upon Abstractions. Do not depend upon concretions.” |
迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。英文简写为: LoD.
迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
迪米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。
有兴趣可以研究一下设计模式的门面模式(Facade)和中介模式(Mediator),都是迪米特法则应用的例子。
狭义的迪米特法则的缺点:
在系统里造出大量的小方法,这些方法仅仅是传递间接的调用,与系统的商务逻辑无关。
遵循类之间的迪米特法则会是一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
门面模式和调停者模式实际上就是迪米特法则的应用。
常见23种模式概述:
1) 抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
2) 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
3) 桥梁模式(Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
4) 建造模式(Builder):将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。
5) 责任链模式(Chain of Responsibility):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
6) 命令模式(Command):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
7) 合成模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
8) 装饰模式(Decorator):动态地给一个对象添加一些额外的职责。就扩展功能而言,它能生成子类的方式更为灵活。
9) 门面模式(Facade):为子系统中的一组接口提供一个一致的界面,门面模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
10) 工厂方法(Factory Method):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method 使一个类的实例化延迟到其子类。
11) 享元模式(Flyweight):运用共享技术以有效地支持大量细粒度的对象。
12) 解释器模式(Interpreter):给定一个语言,定义它的语法的一种表示,并定义一个解释器,该解释器使用该表示解释语言中的句子。
13) 迭代子模式(Iterator):提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。
14) 调停者模式(Mediator):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的内部表示。
15) 备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
16) 观察者模式(Observer):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
17) 原始模型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这个原型创建新的对象。
18) 代理模式(Proxy):为其他对象提供一个代理以控制对这个对象的访问。
19) 单例模式(Singleton):保证一个类仅有一个实例,并提供一个访问它的全局访问点。
20) 状态模式(State):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
21) 策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
22) 模板模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
23) 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。该模式可以实现在不改变各元素的类的前提下定义作用于这些元素的新操作。
Bridge模式的特征:
意图:将一组实现与另一组使用他们的对象分离
问题:一个抽象类 的派生类 必须使用多个实现 ,但出现类数量增长
未使用Bridge实例 |
Bridge模式实例 |
|
|
我想大家小时候都有用蜡笔画画的经历吧。红红绿绿的蜡笔一大盒,根据想象描绘出格式图样。而毛笔下的国画更是工笔写意,各展风采。而今天我们的故事从蜡笔与毛笔说起。
设想要绘制一幅图画,蓝天、白云、绿树、小鸟,如果画面尺寸很大,那么用蜡笔绘制就会遇到点麻烦。毕竟细细的蜡笔要涂出一片蓝天,是有些麻烦。如果有可能,最好有套大号蜡笔,粗粗的蜡笔很快能涂抹完成。至于色彩吗,最好每种颜色来支粗的,除了蓝天还有绿地呢。这样,如果一套12种颜色的蜡笔,我们需要两套24支,同种颜色的一粗一细。呵呵,画还没画,开始做梦了:要是再有一套中号蜡笔就更好了,这样,不多不少总共36支蜡笔。
再看看毛笔这一边,居然如此简陋:一套水彩12色,外加大中小三支毛笔。你可别小瞧这"简陋"的组合,画蓝天用大毛笔,画小鸟用小毛笔,各具特色。
呵呵,您是不是已经看出来了,不错,我今天要说的就是Bridge模式。为了一幅画,我们需要准备36支型号不同的蜡笔,而改用毛笔三支就够了,当然还要搭配上12种颜料。通过Bridge模式,我们把乘法运算3×12=36改为了加法运算3+12=15,这一改进可不小。那么我们这里蜡笔和毛笔到底有什么区别呢?
实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。【GOF95】桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化"。关键就在于能否脱耦。蜡笔的颜色和蜡笔本身是分不开的,所以就造成必须使用36支色彩、大小各异的蜡笔来绘制图画。而毛笔与颜料能够很好的脱耦,各自独立变化,便简化了操作。在这里,抽象层面的概念是:"毛笔用颜料作画",而在实现时,毛笔有大中小三号,颜料有红绿蓝等12种,于是便可出现3×12种组合。每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。
蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建36种对象才能完成任务。Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。
Bridge与Strategy模式
桥接(Bridge)模式是结构型模式的一种,而策略(strategy)模式则属于行为模式。以下是它们的UML结构图。
在桥接模式中,Abstraction通过聚合的方式引用Implementor。
在策略模式中,Context也使用聚合的方式引用Startegy抽象接口。
从他们的结构图可知,在这两种模式中,都存在一个对象使用聚合的方式引用另一个对象的抽象接口的情况,而且该抽象接口的实现可以有多种并且可以替换。可以说两者在表象上都是调用者与被调用者之间的解耦,以及抽象接口与实现的分离。
那么两者的区别体现在什么地方呢?
1. 首先,在形式上,两者还是有一定区别的,对比两幅结构图,我们可以发现,在桥接模式中不仅Implementor具有变化(ConcreateImplementior),而且Abstraction也可以发生变化(RefinedAbstraction),而且两者的变化是完全独立的,RefinedAbstraction与ConcreateImplementior之间松散耦合,它们仅仅通过Abstraction与Implementor之间的关系联系起来。而在策略模式中,并不考虑Context的变化,只有算法的可替代性。
2. 其次在语意上,桥接模式强调Implementor接口仅提供基本操作,而Abstraction则基于这些基本操作定义更高层次的操作。而策略模式强调Strategy抽象接口的提供的是一种算法,一般是无状态、无数据的,而Context则简单调用这些算法完成其操作。
3. 桥接模式中不仅定义Implementor的接口而且定义Abstraction的接口,Abstraction的接口不仅仅是为了与Implementor通信而存在的,这也反映了结构型模式的特点:通过继承、聚合的方式组合类和对象以形成更大的结构。在策略模式中,Startegy和Context的接口都是两者之间的协作接口,并不涉及到其它的功能接口,所以它是行为模式的一种。行为模式的主要特点就是处理的是对象之间的通信方式,往往是通过引入中介者对象将通信双方解耦,在这里实际上就是将Context与实际的算法提供者解耦。
所以相对策略模式,桥接模式要表达的内容要更多,结构也更加复杂。桥接模式表达的主要意义其实是接口隔离的原则,即把本质上并不内聚的两种体系区别开来,使得它们可以松散的组合,而策略在解耦上还仅仅是某一个算法的层次,没有到体系这一层次。从结构图中可以看到,策略的结构是包容在桥接结构中的,桥接中必然存在着策略模式,Abstraction与Implementor之间就可以认为是策略模式,但是桥接模式一般Implementor将提供一系列的成体系的操作,而且Implementor是具有状态和数据的静态结构。而且桥接模式Abstraction也可以独立变化。
Design Pattern: State 模式
如果您不了解TCP的连线方式,在看 Gof 的书介绍State模式时,大概会看得一头雾水吧!TCP的连线状态图,光是要了解就要花点精神了,它的连线状态很多,用来说明状态模式确实很适合,但不适合教导初学模式的人。
由简单的开始会比较好理解状态模式的作用,先来看一个例子,如果您有一个只能顺时针转动的瓦斯开关,转动一次的状态为off、 small fire、medium fire与large fire,您如何在程式中控制状态的变化与行为呢?一个最简单的方式就是用if..else或是switch流程来控制,例如:
- State.java
public class State {
private int state;
public State() {
state = 0;
}
public void switchFire() {
if (state == 0) {
state = 1;
System.out.println( "small fire" );
} else if (state == 1) {
state = 2;
System.out.println( "medium fire" );
} else if (state == 2) {
state = 3;
System.out.println( "large fire" );
} else {
state = 0;
System.out.println( "turning off" );
}
}
}
- Main.java
public class Main {
public static void main(String[] args) {
State state = new State();
state.switchFire();
state.switchFire();
state.switchFire();
state.switchFire();
}
}
这个方法很简单,每个人都会,但如果您的状态变化并不是流水式的变化,而是像TCP连线状态一样,会是一个网络图的时候,用 if...else或switch来写的话,您的程式就会乱的不像话了;来考虑如何让物件控制自己的状态转换与所应表现的行为,这个程式可以这样改写:
- IState.java
public interface IState {
public void switchFire(FireSwitch sw);
}
- OffState
public class OffState implements IState {
public void switchFire(FireSwitch sw) {
sw.setState(new SmallState());
System.out.println( "small fire" );
}
}
- SmallState.java
public class SmallState implements IState {
public void switchFire(FireSwitch sw) {
sw.setState(new MediumState());
System.out.println( "medium fire" );
}
}
- MediumState.java
public class MediumState implements IState {
public void switchFire(FireSwitch sw) {
sw.setState(new LargeState());
System.out.println( "large fire" );
}
}
- LargeState.java
public class LargeState implements IState {
public void switchFire(FireSwitch sw) {
sw.setState(new OffState());
System.out.println( "off fire" );
}
}
- FireSwitch.java
public class FireSwitch {
private State current;
public FireSwitch() {
current = new OffState();
}
public void setState(State s) {
current = s;
}
public void switchFire() {
current.switchFire(this);
}
}
- Main.java
public class Main {
public static void main(String[] args) {
FireSwitch fireSwitch = new FireSwitch();
fireSwitch.switchFire();
fireSwitch.switchFire();
fireSwitch.switchFire();
fireSwitch.switchFire();
}
}
程式执行结果与上一个例子是一样的,但这次并没有用流程控制来进行状态转换,而由物件自行控制自己的状态,与必须表现的行为,这个方式就是State 模式,将这个例子的 UML 类别结构画出就如下所示:
再进一步考虑开关可以顺时针与逆时针转动,这时如果您仍以if...else或switch来写,就会让流程显示复杂,来看看如何使用状态模式来撰写:
- IState.java
public interface IState {
public void switchClockWise(FireSwitch sw);
public void switchCountClock(FireSwitch sw);
}
- OffState.java
public class OffState implements IState {
public void switchClockWise(FireSwitch sw) {
sw.setState(new SmallState());
System.out.println("small fire");
}
public void switchCountClock(FireSwitch sw) {
sw.setState(new LargeState());
System.out.println("large fire");
}
}
- SmallState.java
public class SmallState implements IState {
public void switchClockWise(FireSwitch sw) {
sw.setState(new MediumState());
System.out.println("medium fire");
}
public void switchCountClock(FireSwitch sw) {
sw.setState(new OffState());
System.out.println("off fire");
}
}
- MediumState.java
public class MediumState implements IState {
public void switchClockWise(FireSwitch sw) {
sw.setState(new LargeState());
System.out.println("large fire");
}
public void switchCountClock(FireSwitch sw) {
sw.setState(new SmallState());
System.out.println("small fire");
}
}
- LargeState.java
public class LargeState implements State {
public void switchClockWise(FireSwitch sw) {
sw.setState(new OffState());
System.out.println("off fire");
}
public void switchCountClock(FireSwitch sw) {
sw.setState(new MediumState());
System.out.println("medium fire");
}
}
- FireSwitch.java
public class FireSwitch {
private State current;
public FireSwitch() {
current = new OffState();
}
public void setState(State s) {
current = s;
}
public void switchClockWise() {
current.switchClockWise(this);
}
public void switchCountClock() {
current.switchCountClock(this);
}
}
- Main.java
public class Main {
public static void main(String[] args) {
FireSwitch fireSwitch = new FireSwitch();
fireSwitch.switchClockWise();
fireSwitch.switchClockWise();
fireSwitch.switchClockWise();
fireSwitch.switchClockWise();
System.out.println();
fireSwitch.switchCountClock();
fireSwitch.switchCountClock();
fireSwitch.switchCountClock();
fireSwitch.switchCountClock();
}
}
接下来您可以任意的转动开关了,无论是顺时针转动或是逆时针转动,状态的转换都由物件自己来表现,这是双向状态转换下的例子,如果一个状态可能转换至三个以上的状态,使用State模式就更可以看出它的好处了,就像Gof的TCP连线例子一样,如果您了解TCP连线,可以看看原书是如何实现TCP连线之间的状态转换的。
State模式的UML结构图如下:
Design Pattern: Observer 模式
假设今天您设计一个试算表程式,当中有一个资料物件,您可以用表格图形物件、柱状图形物件、圆饼图形物件等方式来呈现物件,无论您是用哪种图形物件,重点是若资料物件的内容作了更改,则图形物件的内容也必须跟着修改,或许您的程式中有两个以上的图形物件来呈现资料,您在图形物件上更动资料,则另一个图形物件也必须作出相对应的变化。
主题 |
资料物件 |
||
观察者 |
柱状图形 |
表格图形 |
圆饼图形 |
又假设您今天设计一个网路游戏,您在伺服器上维护一个连线客户端共享的资料物件,当其中一个客户端作了操作,将对此资料物件作修改,则伺服器必须通知其它客户端作相对应的变化(像是人物位置走动、建了一个城堡等)。
主题 |
资料物件 |
||
观察者 |
客户端一 |
客户端二 |
客户端三 |
在Observer模式中的主角为主题(subject)与观察者(observer),观察者订阅它感兴趣的主题,一个主题可以被多个观察者订阅,当主题的状态发生变化时,它必须通知(notify)所有订阅它的观察者,观察者检视主题的状态变化,并作出对应的动作,所以Observer 模式也称之为Publish-Subscribe模式。
Observer模式的 UML 图如下所示:
Subject类中有一个notify()方法,通常是在Subject的状态发生改变时呼叫它,notify()中会呼叫 Observer的update()方法,通常会先取得Subject的新状态,然后更新Observer的显示或行为,这个过程我们可以透过 Sequence Diagram来表达:
在Java中支援观察者模式,要成为观察者的类必须实作Observer介面,这个介面中定义了一个update()方法,这个方法会被主题物件在通知状态变化时呼叫,您必须在这个方法中实作您所想要的对应行为。
主题物件会是Observable的子类,在这边注意两个重要的方法:setChanged()与notifyObserver()。 setChanged()是用来设定主题物件的状态已经被改变,而notifyObserver()方法会通知所要订阅主题物件的观察者,调用其 update()方法。
有兴趣的话,建议看一下Java的Observable.java中是如何实作的,这有助于了解Observer模式的运作方式。
Design Pattern: Adapter 模式 - Object Adapter
您的电脑是个旧电脑,新的滑鼠都在使用USB接口了,而您的电脑上并没有USB,而只有一个PS2接口,这时您可以使用一个USB转PS2的接头作为转换,这样您的电脑就可以使用新滑鼠了(当然您也可以使用USB扩充卡,意思是相同的)。
类似的概念,有时候您想在原来的程式中加入一个外部元件,例如一个类别,但是这个类别与您目前所设计的程式在介面上并不一致,为了让这个外部类与原程式的介面一致,您必须使用一个类别作为中介来配接它们,这时您可以采用Adapter模式。
举个例子来说,在Java 1.0中有个Enumeration,您在这个版本发行之后,使用它来设计了一个MessageApplication,例如:
- MessageApplication.java
import java.util.*;
public class MessageApplication {
public void showAllMessage(Enumeration enum) {
Object msg;
while(enum.hasMoreElements()) {
msg = enum.nextElement();
System.out.println(msg);
}
}
}
您的客户端程式是这么使用MessageApplication的:
- MessageClient.java
import java.util.*;
public class MessageClient {
private MessageApplication msgApp;
public void run() {
Vector vector = new Vector();
for(int i = 0; i < 10; i++)
vector.addElement("物件 " + i);
msgApp = new MessageApplication();
msgApp.showAllMessage(vector.elements());
}
public static void main(String[] args) {
MessageClient msgClient = new MessageClient();
msgClient.run();
}
}
现在Java 1.2中新增了Iterator,您想要使用它的功能,但基本上您不想更动原来程式中已设计好的MessageApplication类别,这时候您可以使用Adapter模式,将Iterator的介面转换为Enumeration相容,例如:
- IteratorAdapter.java
import java.util.*;
public class IteratorAdapter implements Enumeration {
private Iterator iterator;
IteratorAdapter(Iterator iterator) {
this.iterator = iterator;
}
// 转接介面
public boolean hasMoreElements() {
return iterator.hasNext();
}
public Object nextElement() throws NoSuchElementException {
return iterator.next();
}
}
您可以在客户端程式中照样使用MessageApplication类别,而不用作任何的变动:
- MessageClient.java
import java.util.*;
public class MessageClient {
// We could still use MessageApplication
private Enumeration iteratorAdapter;
public void run() {
List arrayList = new ArrayList();
for(int i = 0; i < 10; i++)
arrayList.add("物件 " + i);
iteratorAdapter = new IteratorAdapter(arrayList.iterator());
// We could still use MessageApplication
MessageApplication msgApp = new MessageApplication();
msgApp.showAllMessage(iteratorAdapter);
}
public static void main(String[] args) {
MessageClient msgClient = new MessageClient();
msgClient.run();
}
}
如程式所示的,透过Adapter模式,您原有程式中已设计好的类别不用更动,就可以引进新类别的功能,将上面的程式UML类别结构画出如下:
上面的作法,是将要引进的新类别当作Adapter类别的一个物件成员,这是IbObject Adapter模式,其抽象结构如下:
Design Pattern: Adapter 模式 - Class Adapter
Adapter模式的另一种作法是Class Adapter模式,在这个模式下,Adapter直接继承Adaptee(要引进的新类别),以拥有当中的成员及方法,在C++中的话可以这么作:
C++中可以多重继承,但在Java中不行,所以在Java中若要采用Class Adapter,必须作点修改,一方面继承Adaptee,一方面实作Target的介面:
代码的实现是这样的:
public class Adapter extends Adaptee implements Target {
// ....
}
当然,这必须您原先的Target定义了共同的介面,所以Class Adapter在Java中适用的场合较少,事实上,也比较建议使用Object Adapter,这样的Adapter模式比较有弹性,例如,您可以在Adapter上加上个setter,以随时可以抽换Adaptee。
在Java中,Class Adapter的一个应用场合是达到多重继承的效果,您一定在很多时候听别人说,介面(interface)可以达到多重继承的效果,这是怎么回事?
其实要讨论这个问题,首先您对于C++中多重继承要先有认识,新手看了书说介面可以达到多重继承,切莫人云亦云,尤其是没有学过C++的新手们,如果您对于C++多重继承想要有所认识,请先看看 多重继承(一)与 多重继承(二)。
Java不能多重继承,但为何说Java中可以使用介面(interface)来达到多重继承的效果,首先效果之一,就如 多重继承(二) 中描述的“ 多重继承时通常其中一个基底类别作为private实作体,而其它的用以表现完全的抽象介面。”,在Java中这个效果可以使用介面来达到,介面此时所扮演的即 多重继承(二) 中的抽象类别,一个完全的抽象介面,这个效果的达成方式,如 介面(interface)型态 中所介绍的,您可以直接对应这两个主题中的程式实作来了解,了解Java中如何使用介面(interface)来达到C++中所谓多重继承的“一种”效果。
来看看另一个情况。
如果有SomeClass类别与OtherClass类别,您想要SomeAndOther类别可以同时拥有SomeClass类别与 OtherClass类别中已定义好的操作,并可以进行多型操作,在C++中可以用多重继承来达到,但在Java中显然的无法使用多重继承,怎么办?您可以在设计上先绕个弯,先使用两个介面分别定义好SomeClass与OtherClass两个类别的公开方法,例如:
public interface ISome {
public void doSome();
}
public interface IOther {
public void doOther();
}
接着让SomeClass与OtherClass分别实作两个介面:
public class SomeClass implements ISome {
public void doSome() {
....
}
}
public class OtherClass implements IOther {
public void doOther() {
....
}
}
SomeAndOther如何同时拥有两个SomeClass与OtherClass类别已定义好的操作?并可以多型操作?SomeAndOther可以继承其中之一,并拥有其中之一,例如:
public class SomeAndOther extends SomeClass implements IOther {
private IOther other = new OtherClass();
public void doOther() {
other.doOther();
}
}
虽不满意,但至少解决了目前的问题,当然这边只是其中一例,毕竟C++是C++,Java是Java,两者语法并不是一对一的关系,视实际需求还可以变化一下。
Design Pattern: Visitor 模式
在Java中所有的物件都继承自Object物件,这样作的优点之一,就是使得一些集合物件的资料结构容易管理,例如您可以将任何型态的物件放入Vector中。
然而现在有个问题是,如果您的集合(connection)物件中不仅储存一种型态的物件,如果想要对这些物件作出一些个别化的操作,首要条件就是要知道该物件的型态,使用 instanceof 似乎是个不错的方式,在程式简单的情况下,也许您会这么作:
public class ElementA {
// some implementing
}
public class ElementB {
// some implementing
}
public class ElementC {
// some implementing
}
// ......
Iterator iterator = arrayList.iterator()
while (iterator.hasNext()) {
if (o instanceof ElementA)
(ElementA) o.operationA();
else if (o instanceof ElementB)
(ElementB) o.operationB();
else if (o instanceof ElementC)
(ElementC) o.operationC();
else
System.out.println(
"Sorry! I don't know who you are! "
+ o.toString());
//....
}
//....
这么作并不是不可以,只是将来的扩充性不大,如果今天您想要一次改变对每一种类型物件的操作,您必须修改很多地方。
从物件自身的角度来想好了,物件在一个个的房子中,物件说:“不要在房子外费尽心思判断了,即然您不知道我是谁,那么您就进来访问我好了,我告诉您我是谁,这么一来您就知道如何操作我了!”
用程式来实现上面这个描述:
- IElement.java
public interface IElement {
public void accept(IVisitor visitor);
}
- ElementA.java
public class ElementA implements IElement {
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public void operationA() {
System.out.println("do A's job....such-and-such....");
}
}
- ElementB.java
public class ElementB implements IElement {
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public void operationB() {
System.out.println("do B's job....such-and-such....");
}
}
- ElementC.java
public class ElementC implements IElement {
public void accept(IVisitor visitor) {
visitor.visit(this);
}
public void operationC() {
System.out.println("do C's job....such-and-such....");
}
}
- IVisitor.java
public interface IVisitor {
public void visit(ElementA element);
public void visit(ElementB element);
public void visit(ElementC element);
}
- VisitorA.java
public class VisitorA implements IVisitor {
public void visit(ElementA element) {
element.operationA();
}
public void visit(ElementB element) {
element.operationB();
}
public void visit(ElementC element) {
element.operationC();
}
}
- Main.java
public class Main {
public static void main(String[] args) {
// know nothing about their type after storing them into Element array
IElement[] list = {new ElementA(), new ElementB(), new ElementC()};
IVisitor visitor = new VisitorA();
for (int i=0; i < list.length; i++)
list[i].accept(visitor);
}
}
Visitor访问是基于overload来完成,对于每一个实现IElement的物件来说,它接受IVisitor来访问它,在accept()方法中,IVisitor使用正确的方法来访问IElement(显然的,这么部份可以靠不同的函式名称,或是overload来达成),并在visit() 中对IElement作出对应的操作,如果您今天想要换掉每一个IElement的操作,只要更换IVisitor类型的物件就可以了,也就是这行:
// IVisitor visitor = new VisitorA();
// 换掉一个IVisitor,就可以换掉所有的操作
// 不用修改多个地方
IVisitor visitor = new VisitorB();
举个实际的例子,假设VisitorA只是个懒惰的推销员好了,今天有一个比较勤快的推销员VisitorB,在访问过IElement之后,会对 IElement作出更多的操作,要在程式中实现VisitorB,只要增加一个VisitorB类别就可以了:
- VisitorB.java
public class VisitorB implements IVisitor {
public void visit(ElementA element) {
System.out.println("VisitorB is a hard worker....");
element.operationA();
System.out.println("I want to do some extra work on A....");
}
public void visit(ElementB element) {
System.out.println("VisitorB is a hard worker....");
element.operationB();
System.out.println("I want to do some extra work on B....");
}
public void visit(ElementC element) {
System.out.println("VisitorB is a hard worker....");
element.operationC();
System.out.println("I want to do some extra work on C....");
}
}
改一下Main来示范:
- Main.java
public class Main {
public static void main(String[] args) {
IElement[] list = {new ElementA(), new ElementB(), new ElementC()};
System.out.println("visitorA is coming.......");
IVisitor visitorA = new VisitorA();
for (int i=0; i < list.length; i++)
list[i].accept(visitorA);
System.out.println("\nvisitorB is coming.......");
IVisitor visitorB = new VisitorB();
for (int i=0; i < list.length; i++)
list[i].accept(visitorB);
}
}
在范例中的System.out.println();只是个示意,它也可能是您对IElement的额外方法的直接调用。
Visitor模式的 UML 结构类图如下:
在Java World中有一篇文章,提到可以利用reflection来改进使用访问者模式时的弹性,有兴趣的可以进一步参考一下Reflect on the Visitor design pattern。
一、 职责链(Chain of Responsibility)模式
责任链模式是一种对象的行为模式【GOF95】。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
从击鼓传花谈起
击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。
击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分。
二、 责任链模式的结构
责任链模式涉及到的角色如下所示:
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
三、 责任链模式的示意性源代码
// Chain of Responsibility pattern -- Structural example
using System;
// "Handler"
abstract class Handler
{
// Fields
protected Handler successor;
// Methods
public void SetSuccessor( Handler successor )
{
this.successor = successor;
}
abstract public void HandleRequest( int request );
}
// "ConcreteHandler1"
class ConcreteHandler1 : Handler
{
// Methods
override public void HandleRequest( int request )
{
if( request >= 0 && request < 10 )
Console.WriteLine("{0} handled request {1}",
this, request );
else
if( successor != null )
successor.HandleRequest( request );
}
}
// "ConcreteHandler2"
class ConcreteHandler2 : Handler
{
// Methods
override public void HandleRequest( int request )
{
if( request >= 10 && request < 20 )
Console.WriteLine("{0} handled request {1}",
this, request );
else
if( successor != null )
successor.HandleRequest( request );
}
}
// "ConcreteHandler3"
class ConcreteHandler3 : Handler
{
// Methods
override public void HandleRequest( int request )
{
if( request >= 20 && request < 30 )
Console.WriteLine("{0} handled request {1}",
this, request );
else
if( successor != null )
successor.HandleRequest( request );
}
}
/**////
/// Client test
///
public class Client
{
public static void Main( string[] args )
{
// Setup Chain of Responsibility
Handler h1 = new ConcreteHandler1();
Handler h2 = new ConcreteHandler2();
Handler h3 = new ConcreteHandler3();
h1.SetSuccessor(h2);
h2.SetSuccessor(h3);
// Generate and process request
int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };
foreach( int request in requests )
h1.HandleRequest( request );
}
}
四、 纯的与不纯的责任链模式
一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一个是承担责任,二是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。
在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的例子是不容易找到的,一般看到的例子均是不纯的责任链模式的实现。
五、 责任链模式的实际应用案例
下面的责任链模式代码演示了不同职务的人根据所设定的权限对一个购买请求作出决策或将其交给更高的决策者。
// Chain of Responsibility pattern -- Real World example
using System;
// "Handler"
abstract class Approver
{
// Fields
protected string name;
protected Approver successor;
// Constructors
public Approver( string name )
{
this.name = name;
}
// Methods
public void SetSuccessor( Approver successor )
{
this.successor = successor;
}
abstract public void ProcessRequest( PurchaseRequest request );
}
// "ConcreteHandler"
class Director : Approver
{
// Constructors
public Director ( string name ) : base( name ) {}
// Methods
override public void ProcessRequest( PurchaseRequest request )
{
if( request.Amount < 10000.0 )
Console.WriteLine( "{0} {1} approved request# {2}",
this, name, request.Number);
else
if( successor != null )
successor.ProcessRequest( request );
}
}
// "ConcreteHandler"
class VicePresident : Approver
{
// Constructors
public VicePresident ( string name ) : base( name ) {}
// Methods
override public void ProcessRequest( PurchaseRequest request )
{
if( request.Amount < 25000.0 )
Console.WriteLine( "{0} {1} approved request# {2}",
this, name, request.Number);
else
if( successor != null )
successor.ProcessRequest( request );
}
}
// "ConcreteHandler"
class President : Approver
{
// Constructors
public President ( string name ) : base( name ) {}
// Methods
override public void ProcessRequest( PurchaseRequest request )
{
if( request.Amount < 100000.0 )
Console.WriteLine( "{0} {1} approved request# {2}",
this, name, request.Number);
else
Console.WriteLine( "Request# {0} requires " +
"an executive meeting!", request.Number );
}
}
// Request details
class PurchaseRequest
{
// Member Fields
private int number;
private double amount;
private string purpose;
// Constructors
public PurchaseRequest( int number,
double amount, string purpose )
{
this.number = number;
this.amount = amount;
this.purpose = purpose;
}
// Properties
public double Amount
{
get{ return amount; }
set{ amount = value; }
}
public string Purpose
{
get{ return purpose; }
set{ purpose = value; }
}
public int Number
{
get{ return number; }
set{ number = value; }
}
}
/// ChainApp Application
public class ChainApp
{
public static void Main( string[] args ) {
// Setup Chain of Responsibility
Director Larry = new Director( "Larry" );
VicePresident Sam = new VicePresident( "Sam" );
President Tammy = new President( "Tammy" );
Larry.SetSuccessor( Sam );
Sam.SetSuccessor( Tammy );
// Generate and process different requests
PurchaseRequest rs = new PurchaseRequest( 2034, 350.00, "Supplies" );
Larry.ProcessRequest( rs );
PurchaseRequest rx = new PurchaseRequest( 2035, 32590.10, "Project X" );
Larry.ProcessRequest( rx );
PurchaseRequest ry = new PurchaseRequest( 2036, 122100.00, "Project Y" );
Larry.ProcessRequest( ry );
}
}
六、 责任链模式的实现
责任链模式并不创建责任链。责任链的创建必须由系统的其它部分创建出来。
责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。一个链可以是一条线,一个树,也可以是一个环。如下图所示,责任链是一个树结构的一部分。