不变(Immutable)模式
模式结构
策略(Strategy)模式
模式结构
应用场景
缺点
模版方法(Tmplate Method)模式
模式结构
应用场景
观察者(Observer)模式
模式结构
应用场景
迭代(Iterator)模式
白箱迭代结构
黑箱迭代结构
应用场景
责任链(Chain of Responsibility)模式
模式结构
纯与不纯
命令(Command)模式
特点
模式结构
应用场景
备忘录(Memento)模式
三种角色
模式结构
应用场景
状态(State)模式
模式结构
应用场景
访问者(Visitor)模式
问题与解决
模式结构
应用场景
解释器(Interpreter)模式
模式结构
示例:模拟布尔表达计算
应用场景
调停者(Mediator)模式
为什么需要这个模式
模式结构
优点
应用场景
行为模式
行为模式是对在不同的对象之间划分责任算法的抽象化。行为模式不仅仅是关于类和对象的,而且是关于它们之间的相互作用。
不变(Immutable)模式
一个对象的状态在对象被创建之后就不再变化,这就是所谓的不变模式。
模式结构
如果需要修改一个不变对象的状态,那么就需要建立一个新的同类型对象,并在创建时将这个新的状态存储在新的对象里。
不变以模式只涉及到一个类,一个类的内部状态创建后,在整个生命周期都不会发生变化时,这样的类叫做不变类。这种使用不变类的做法叫做不变模式,不变模式不需要类图描述。
注,不变与只读是不同的,比如人的生日是只读的,随着时间它会变化。
Java中的不变类:String,各种包装类。
优点:比可变对象更加容易维护和使用;本身就是线程安全的,可以安全共享,不需要同步带来的开销。
缺点:频繁修改会产生大量的不变对象,会占用资源。
策略(Strategy)模式
定义一系列的算法,把它们一个个封装到共同接口的独立类中,并且使它们可相互替换。
模式结构
将变化的算法单独抽象来了。
public interface Strategy {//策略接口
//算法的接口
void algorithmInterface();
}
public class ConcreteStrategyA implements Strategy {//具体策略角色A
public void algorithmInterface() {
System.out.println("ConcreteStrategyA");
}
}
算法使用的上下文环境,持有一个Strategy的引用
public class Context {//环境角色
//持有的策略对象
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void contextInterface() {//策略方法
strategy.algorithmInterface();
}
}
public class Client {//场景
public static void main(String[] args) {
ConcreteStrategyC csc = new ConcreteStrategyC();
Context c = new Context(csc);
c.contextInterface();
c.setStrategy(new ConcreteStrategyB());//替换算法
c.contextInterface();
c.setStrategy(new ConcreteStrategyA());//替换算法
c.contextInterface();
}
}
应用场景
需要根据发出请求的客户或被处理的数据对算法作出选择。
出现大量的if、switch语句时,将这种选择从程序内部移到了客户端,让用户自己选择。
一个系统需要动态地在几种算法中选择一种。
需要实现类的状态,将类的状态抽象成共同接口,可以动态地改变类的状态。
缺点
客户端必须知道与了解所使用的算法或行为。
可能会造成很多的策略类。
模版方法(Tmplate Method)模式
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。此模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模式结构
abstract public class AbstractClass {//抽象模板
//模板方法,即算法骨架
public void templateMethod() {
doOperation1();//由子类实现
doOperation2();//由子类实现
doOperation3();//自己已实现
}
protected abstract void doOperation1();//基本方法,强制子类实现
protected abstract void doOperation2();//基本方法,强制子类实现
private final void doOperation3() {基本方法,已实现
//do something
}
}
public class ConcreteClass extends AbstractClass {//具体模板
public void doOperation1() {//实现基本算法
System.out.println("doOperation1();");
}
public void doOperation2() {//实现基本算法
System.out.println("doOperation2();");
}
}
HttpServlet的service()方法就是一个模板方法,7个do方法便是基本方法,但它们都已经提供了默认空的实现。
基本方法命名一般以do开头。
以多态性取代条件
应用场景
一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
控制子类扩展。
观察者(Observer)模式
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
模式结构
下面是被观察者,从API复制过来,但将Vector改为了ArrayList。在实现的应用中我们直接使用类库中的Observable即可。
public class Observable {//抽象主题,被观察者
private boolean changed = false;//状态是否改变
private ArrayList obs = new ArrayList();//登记观察
//观察者注册
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.add(o);
}
}
//注册某个观察者
public synchronized void deleteObserver(Observer o) {
obs.remove(o);
}
public void notifyObservers() {
notifyObservers(null);
}
/*
* 如果 hasChanged 方法指示对象已改变,则通知其所有观察者,并
* 调用 clearChanged 方法来指示此对象不再改变。
*/
public void notifyObservers(Object arg) {
Object[] arrLocal;
/*
* 在同步块中拷贝一个,这样的拷贝会很快,但通知所有
* 可能比较慢,所以为了提高性能需要拷贝一个,在通知
* 时不会阻塞其他操作
*/
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length - 1; i >= 0; i--)
((Observer) arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.clear();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
在实际的应用中直接继承类库中的Observable即可。
public class ConcreteObservable extends Observable {//具体主题
private String state;//具体主题状态
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;//改变状态
this.setChanged();//通知前需要设置成改变状态才起作用
this.notifyObservers(state);//通知所有观察者
}
}
下面是观察者,从API复制过来,在实现的应用中我们直接使用类库中的Observer接口即可。
public interface Observer {//抽象观察者
/**
* 主题状态改变时调用
* @param o observable 对象
* @param state 传递给 notifyObservers 方法的参数
*/
void update(Observable o, Object state);
}
在实际应用中我们直接实现类库中的Observer接口即可。
public class ConcreteObserver implements Observer {//具体观察者
/**
* @param o observable 对象,便于回调用被观察者方法
* @param state 传递给 notifyObservers 方法的参数
*/
public void update(Observable o, Object state) {
System.out.println("I am notified. countObservers="
+ o.countObservers() + " newState=" + state);
}
}
public class Client {//场景
public static void main(String[] args) {
ConcreteObservable obs = new ConcreteObservable();
obs.addObserver( new ConcreteObserver());
obs.addObserver( new ConcreteObserver());
obs.setState("stat1");
obs.setState("stat2");
}
}
应用场景
当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象需要改变。
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
迭代(Iterator)模式
提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部结构。
将数据结构与访问方式分离,让它们可以独立的演化,用户在不必关心集合数据结构的情况下,就能通过统一的迭代接口来遍历集合。
从代码重构的角度上讲,迭代在客户端和聚集之间增加了一个中介层,从而使得客户端与聚集之间的通信从直接变成间接。
白箱迭代结构
一个白箱集合需要向外界提供了访问自己内部元素的接口(如下面getElement、size()方法),从而使迭代器可以通过集合遍历方法来实现迭代功能。
public interface Aggregate {//集合接口
//一定要向外界提供一个返还迭代器的工厂方法
public Iterator createIterator();
}
public interface Iterator {//迭代接口
void first();//移到第一个元素
void next();//移到下一个元素
boolean isDone();//是否是最后一个元素
Object currentItem();//返回当前元素
}
public class ConcreteAggregate implements Aggregate {//具体集合
//以数组结构来实现集合
private Object objs[] = { "Monk Tang", "Monkey", "Pigsy",
"Sandy", "Horse" };
public Iterator createIterator() {//返回迭代器
return new ConcreteIterator(this);
}
//白箱:向外界提供聚集元素
public Object getElement(int index) {
if (index < objs.length) {
return objs[index];
} else {
return null;
}
}
//白箱:向外界提供聚集大小
public int size() {
return objs.length;
}
}
public class ConcreteIterator implements Iterator {//迭代器
private ConcreteAggregate agg;//要进行迭代的集合
private int index = 0;//游标
private int size = 0;//集合大小
public ConcreteIterator(ConcreteAggregate agg) {
this.agg = agg;
size = agg.size();
index = 0;
}
public void first() {//移到第一个元素
index = 0;
}
public void next() {//移到下一个元素
if (index < size) {
index++;
}
}
public boolean isDone() {//是否还有下一个元素
return (index >= size);
}
public Object currentItem() {//取当前游标位置所在的元素
return agg.getElement(index);
}
}
public class Client {//场景
//针对抽象编程,可以随时替换掉集合与迭代器
private Iterator it;
private Aggregate agg = new ConcreteAggregate();
public void operation() {
it = agg.createIterator();
while (!it.isDone()) {
System.out.println(it.currentItem().toString());
it.next();
}
}
public static void main(String[] args) {
Client client = new Client();
client.operation();
}
}
一个常常会问的问题是:既然白箱集合已经向外界提供了遍历方法,客户端已经可以自行进行迭代了,为什么还要应用迭代模式,并创建一个迭代对象进行迭代呢?客户端当然可以自行进行替代,不一定非得需要一个迭代对象。但是,迭代对象会将迭代过程抽象化,使得集合与访问独立演化,在聚集对象的各类发生变化,或者迭代的方法发生改变时,迭代器作为中介层可以吸收变化的因素,而避免修改客户端或者集合本身。比如你在程序中使用for循环与索引来对List进行遍历,那么你就不能将集合替换为Set集合,因为Set集合没有索引,但List与Set都提供了共同的迭代接口,这使得使用迭代器遍历时并不依赖于底层的数据结构,这里我们并不关心它是List还是Set,只要是使用迭代器遍历的,则可以相互替换。
此外,如果需要对同一集合进行不同方式的迭代,那么迭代器将也很有作用,我们可以使用新的迭代方法去替换原有的迭代算法。
黑箱迭代结构
一个黑箱聚集不向外部提供遍历自己元素对象的接口(没有getElement、size方法了),因此,这些元素对象只可以被集合内部成员访问,这用要求迭代器的实现类要是集合的一个内部类,这样迭代器就可以访问集合的任何成员了。
public class ConcreteAggregate implements Aggregate {//具体集合
private Object objs[] = { "Monk Tang", "Monkey", "Pigsy",
"Sandy", "Horse" };
private class ConcreteIterator implements Iterator {//内部迭代器
private int currentIndex = 0;
public void first() {
currentIndex = 0;
}
public void next() {
if (currentIndex < objs.length) {
currentIndex++;
}
}
public boolean isDone() {
return (currentIndex == objs.length);
}
public Object currentItem() {
return objs[currentIndex];
}
}
public Iterator createIterator() {//返回迭代器
return new ConcreteIterator();
}
}
Java类库中的AbstractList,选择了使用内部迭代,但同时这个类也向外部提供了自己的遍历方法,换言这,如果设计师使用AbstractList集合,也同样可以定义自己的外部迭代器。
应用场景
访问一个聚合对象的内容而无需暴露它的内部表示。
支持对聚合对象的多种遍历。
为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代),集合结构与访问方法需独立的演化。
责任链(Chain of Responsibility)模式
使多个对象都有机会处理请求,从而避免请求发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
责任链模式并不创建出责任链,责任链的创建必须由系统其他部分创建出来。
责任链模式减低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。
模式结构
public abstract class Handler {//抽象处理者角色
protected Handler successor;//持有的下家
public abstract void handleRequest();//调用此方法处理请求
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public Handler getSuccessor() {
return successor;
}
}
具体处理者角色,可以选择将请求处理掉,或者将请求会传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
public class ConcreteHandler1 extends Handler {//具体处理者1
public void handleRequest() {
//条件是有下家就往下传,具体的应用中可能还有其它的一些条件
if (getSuccessor() != null) {
System.out.println("ConcreteHandler1的下家是:" + getSuccessor());
getSuccessor().handleRequest();
} else {//没有就执行
System.out.println("ConcreteHandler1结束了");
}
}
public String toString() {
return "ConcreteHandler1";
}
}
public class ConcreteHandler2 extends Handler {//具体处理者2
public void handleRequest() {
if (getSuccessor() != null) {
System.out.println("ConcreteHandler2的下家是:" + getSuccessor());
getSuccessor().handleRequest();
} else {
System.out.println("ConcreteHandler2结束了");
}
}
public String toString() {
return "ConcreteHandler2";
}
}
public class Client {//场景
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1();
Handler handler2 = new ConcreteHandler2();
//将第一个处理器的下家设置成第二个处理器
handler1.setSuccessor(handler2);
//开始传递
handler1.handleRequest();
}
}
纯与不纯
一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,二是把责任推给下家,不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。
在一个纯的责任链模式里,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。
纯的责任链模式的实际例子很难打到,一般看到的例子都是不纯的责任链模式。
命令(Command)模式
命令模式把一个请求或者操作封装到一个对象中,命令模式允许系统使用不同的请求把客户端参数化,对请求排除或者记录请求日志,可以提供命令的撤销和恢复功能。
特点
命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分开,委派给不同的对象。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作,命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不
必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何
时坡执行,以及是怎么被执行的。
命令允许请求的一方和接收请求的一方能够独立演化,从而具有以下的优点:
l 命令模式使新的命争很容易地被加入到系统里。
l 允许接收请求的一方决定是否要否决(Veto)请求。
l 能较容易地设计一个命令队列。
l 可以容易地实现对请艰的Undo和Redo。
l 在需要的情况下可以较容易地将命令记入日志。
其实命令模式中的Command接口就是回调接口。
模式结构
public interface Command {//命令接口
void execute();//执行方法
}
public class ConcreteCommand implements Command {//具体命令
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
//命令执行方法,负责调用接收者相应操作
public void execute() {
receiver.action();
}
}
调用者是一个已存在的具体类,它的产生应该比命令类要早。
public class Invoker {//调用者
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void action() {//行动方法,调用命令的执行方法
command.execute();
}
}
负责执行请求。任何一个类都可以成为接收者,一般是一个已存在的具体类,它的产生应该比命令类要早,在没有使用命令模式时,Invoker是直接调用Receiver的方法,属强耦合,现在在它上面加了一层Command接口,并封装了接收者,这样每个请求可以发求给不同的接收者。
public class Receiver {//接收者
public void action() {//行动方法
System.out.println("Action has been taken.");
}
}
public class Client {//场景,解耦请求者与执行者
public static void main(String[] args) {
Receiver receiver = new Receiver();//指定哪个接收
Command command = new ConcreteCommand(receiver);//命令
Invoker invoker = new Invoker(command);//将请求封装成命令
invoker.action();//调用者发出请求
}
}
应用场景
使用命令模式进行“回调”,“回调”是指程序构建在回调接口之上,而实现允许稍后进行。
需要在不同的时间指定请求、将请求排队,一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
系统需要支持命令撤消(Undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令的效果。
如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更断命令,重新调用excute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
备忘录(Memento)模式
备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。
三种角色
Memento(备忘录)角色
1、 将发起人(Originator)对象的内部状态存储起来。
2、 备忘录可以保护其内部不被发起人对象之外的任何对象所读取。备忘录模式要求备忘录对象提供两个不同的接口(interface):一个宽接口(Wide interface)提供给发起人对象,另一个窄接口(Narrow interface)提供给所有其他的对象,包括负责人对象。宽接口允许发起人读取到所有的数据;窄接口只允许它把备忘录对象传给其他对象而看不到内部数据。
Originator(发起人)角色
1、 创建一个含有当前的内部状态的备忘录对象。
2、 使用备忘录对象存储其内部状态。
Caretaker(负责人)角色
负责保存备忘录对象(但不检查备忘录对象的内容)。
模式结构
在Java语言中实现备忘录模式时,实现“宽”和“窄”两个接口(interface)并不是容易的事。如果暂时忽略两个接口的区别,仅为备忘录角色提供一个宽接口的话,情况就变得简单得多。但是由于备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状志就对所有对象公开,因此是破坏封装性的,这也违反了备忘录模式的用意。
在这里,我们可以将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外提供一个标识接口MementoIF(即什么方法都没有的接口,自然是窄接口)给Caretaker以及其他对象。这样,Originator类能看到Memento的所有接口,而Caretaker以及其他对象看到的仅仅是窄接口MementoIF所暴露出来的接口。
备忘录的窄接口,也即标示接口,它是用来提供给除发起人以外的对象使用
public interface MementoIF {}
发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来。
public class Originator {//发起人角色
private String state;
/*
* 备忘录,将发起人对象传入的状态存储起来。
* 私有的,除了能在 Originator 访问外,任何地
* 方都不能访问它。
*/
private class Memento implements MementoIF {
private String savedState;//保存发起人的状态
private Memento(String someState) {
savedState = someState;
}
private String getState() {
return savedState;
}
private void setState(String state) {
this.savedState = state;
}
}
//工厂方法,返回一个新的备忘录对象。注,这里返回的是一个窄接口 MementoIF
public MementoIF createMemento() {
return new Memento(state);
}
//恢复到备忘录对象所记载的状态
public void restoreMemento(MementoIF memento) {
state = ((Memento) memento).getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("current state:" + state);
}
}
负责人角色,负责保存备忘录对象,不检查备忘录对象内容。它是以MementoIF接口来引用备忘录对象的,所以不能修改这个备忘录对象的状态。
public class Caretaker {//负责人
private Originator o;
//持有的备忘录对象
private MementoIF memento;
public Caretaker(Originator o) {
this.o = o;
}
/*
* 这里的负责人功能增强,不再只是保存备忘录,还
* 能直接创建与恢复,所以retrieveMemento()方法
* 与 saveMemento(MementoIF memento) 被注释了
*/
//创建一个新的备忘录对象
public void createMemento() {
this.memento = this.o.createMemento();
}
//将发起人恢复到备忘录纪录状态上
public void restoreMemento() {
this.o.restoreMemento(this.memento);
}
// public MementoIF retrieveMemento() {
// return memento;
// }
// public void saveMemento(MementoIF memento) {
// this.memento = memento;
// }
}
public class Client {//场景
public static void main(String[] args) {
Originator o = new Originator();
Caretaker c = new Caretaker(o);
o.setState("on");
// c.saveMemento(o.createMemento());
c.createMemento();//保存状态到备忘录中
o.setState("off");
// o.restoreMemento(c.retrieveMemento());
c.restoreMemento();//从备忘录中恢复
System.out.println(o.getState());
}
}
应用场景
有时一些发起人对象的内部信息必须保存在对象以外的地方,但是必须只能由发起人对象自己来读取。这时,使用备忘录可以把发起人内部复杂的信息对象其他对象屏蔽起来,是状态不被破坏。
必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性(因为有些状态可能是私有的)。
状态(State)模式
一个对象的内部状态改变时,它的行为也随之发生变化。
GoF:允许一个对象在其内部状态改变时改变它的行为。
模式结构
public interface State {//抽象状态角色
//不同状态的相应接口,此处为呼吸的情况
void handle();
}
跑状态。注,状态对象中并没有状态,因为状态类本身就是从各种不同的状态中抽象出来的,它本身就代表了一种状态,所以状态类中只有与本状态相对应的行为。
public class ConcreteStateA implements State {//具体状态角色A
public void handle() {
System.out.println("气喘吁吁");
}
}
走状态
public class ConcreteStateB implements State {//具体状态角色B
public void handle() {
System.out.println("气定神闲");
}
}
public class Context {//环境角色
//持有的状态,可以动态的变化
private State state;
public void request() {//请求操作
state.handle();//将请求委派到状态所对应的行为
}
public void setState(State state) {//改变状态
this.state = state;
}
}
不同的状态有不同的行为,让用户自己选择状态,也就选择了行为,这可以去掉程序中的大量条件语句。
public class Client {//场景
public static void main(String[] args) {
Context c = new Context();
c.setState(new ConcreteStateA());//选择跑状态
c.request();//开始跑
c.setState(new ConcreteStateB());//选择走状态
c.request();//开始走
}
}
应用场景
一个对象的行为取决于它的状态, 并且它需要在运行时刻根据状态改变它的行为。
对象在某个方法里依赖于一重或多重的条件转移语句,其中有大量的代码。状态模式把条件转移语句的每一个分支都包装到一个单独的类里。这使得这些条件转移分支能够以类的方式独立存在和演化。维护这些独立的类也就不再影响到系统的其他部分。
访问者(Visitor)模式
访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
问题与解决
集合是大多数的系统都要处理的种容器对象,它保存了对其他对象的引用。很多的时候,处理的是同类对象的集合,换言之,在集合上采取的操作都是一些针对同类型对象的同类操作,而迭代模式就是为这种情况准备的设计模式。
但如果是针对一个保存有不同类型对象的集合时,就会要进行不同的操作。换言这,如果需要针对一个包含不同类型元素的集合采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型判断的条件转移语句。
访问者模式适用于数据结构相对未定的系统,它把数据结构和作用于结构上的操作之间的耦合解开,使得操作集合可以相对自由地演化。
数据结构的每一个元素都可以接受一个访问者的调用,此元素向访问者对象传入元素对象,而访问者对象则返过来执行元素对象的操作。
模式结构
可以看到,抽象访问者角色为每一个具体元素都准备了一个访问操作。由于有两个具体元素,因此,对应就有两个访问操作。抽象访问者角色为每一个具体元素都提供了一个访问操作,接收相应的具体元素对象作为参数。
public interface Visitor {//抽象访问者角色
//用来访问ConcreteElementA
void visit(ConcreteElementA cea);
//用来访问ConcreteElementB
void visit(ConcreteElementB ceb);
}
具体访问者。两个访问操作分别对应于系统的两个节点。
public class ConcreteVisitor implements Visitor {//具体访问者
//由ConcreteElementA的accept()方法来调用
public void visit(ConcreteElementA cea) {
//又回调ConcreteElementA的operationA()方法
System.out.println(cea.operationA());
}
//由ConcreteElementB的accept()方法来调用
public void visit(ConcreteElementB ceb) {
//又回调ConcreteElementB的operationB()方法
System.out.println(ceb.operationB());
}
}
每个元素都应该有一个接受某个访问都的方法。
public interface Element {//抽象元素角色
//接受访问者
void accept(Visitor visitor);
}
具体元素除了接受访问方法外,还有自己相应的业务方法。
public class ConcreteElementA implements Element {//具体元素A
//接受访问者
public void accept(Visitor visitor) {
visitor.visit(this);
}
//由访问者来调用
public String operationA() {//业务方法
return "operation from A";
}
}
public class ConcreteElementB implements Element {//具体元素B
//接受访问者
public void accept(Visitor visitor) {
visitor.visit(this);
}
//由访问者来调用
public String operationB() {
return "operation from B";
}
}
模拟一个数据结构。
public class ObjectStructure {//数据结构对象角色
private List elements;
public ObjectStructure() {
elements = new ArrayList();
}
public void add(Element el) {
elements.add(el);
}
//统一访问每一个元素
public void action(Visitor visitor) {
Iterator it = elements.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
/*
* 现在像访问同一种类型元素那样统一访问
* 每个不同类型的元素
*/
e.accept(visitor);
}
}
}
public class Client {//场景
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
//创建一个访问
Visitor visitor = new ConcreteVisitor();
os.action(visitor);//使用访问者统一访问所有的元素
}
}
应用场景
访问者模式仅应当在被访问的数据对象结构非常稳定的情况下使用。换言这,系统很少出现需要加入新节点的情况。如果出现需要加入新的元素情况怎么办呢?那就必须在抽象访问角色与每个具体访问者里加入一个对应于这个新元素的访问操作,而这是对一个系统的大规模修改,因此是违背“开-闭”原则的。
一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施统一访问操作。
定义对象结构的类很少改变,但经常需要在此结构上定义新的操作(增加一个新的访问者)。
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类(因为这些操作在访问者里集中调用了)。
解释器(Interpreter)模式
给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器来解释这个语言中的句子。
GoF:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
模式结构
public interface AbstractExpression {// 抽象表达式角色
// 解释操作
void interpret(Context c);
}
public class TerminalExpression implements AbstractExpression {//终结表达式
//持有的语句
private String statement;
public TerminalExpression(String statement){
this.statement = statement;
}
public void interpret(Context c) {
//这里的解释操作就是简单地输出
System.out.println(statement);
}
}
public class NonterminalExpression implements AbstractExpression {// 非终结表达式角色
//非终结表达式会持有另一表达式
private AbstractExpression expression;
private int times = 0;//循环的次数
public NonterminalExpression(AbstractExpression exp, int times) {
expression = exp;
this.times = times;
}
//解释操作实现
public void interpret(Context c) {
for (int i = 0; i < times; i++) {
expression.interpret(c);
}
}
}
//环境角色,提供解释器之外的一些全局信息,这里没有用到
public class Context {}
public class Client {
public static void main(String[] args) {
Context c = new Context();
AbstractExpression ae = new NonterminalExpression(
new TerminalExpression("hello my friend!"), 2);
ae.interpret(c);
}
}
示例:模拟布尔表达计算
该示例来自GoF中的经典例子。
/**
* 这个抽象类代表终结类和非终结类的抽象化
* 其中终结类和非终结类来自下面的文法
* BooleanExp ::=
* BooleanExp AND BooleanExp
* | BooleanExp OR BooleanExp
* | NOT BooleanExp
* | Variable
* | Constant
* Variable ::= ... //可打印的任何非空白字符,即变量名
* Contant ::= "true" | "false" //即布尔常量
*/
public abstract class Expression {//抽象表达式
/**
* 以环境类为准,本文法解释给定的任何一个表达式
*/
public abstract boolean interpret(Context ctx);
public abstract boolean equals(Object o);
public abstract int hashCode();
public abstract String toString();
}
public class Context {// 全局上下文信息,提供解释器一些全局信息
private HashMap map = new HashMap();
//给布尔变量分配值
public void assign(Variable var, boolean value) {
map.put(var, new Boolean(value));
}
//获取布尔变量值
public boolean lookup(Variable var) throws IllegalArgumentException {
Boolean value = (Boolean) map.get(var);
if (value == null) {
throw new IllegalArgumentException();
}
return value.booleanValue();
}
}
public class Constant extends Expression {//布尔常量终结符表达式
private boolean value;
public Constant(boolean value) {
this.value = value;
}
//解释操作,因为是常量,所以直接返回即可
public boolean interpret(Context ctx) {
return value;
}
public boolean equals(Object o) {
if (o != null && o instanceof Constant) {
return this.value == ((Constant) o).value;
}
return false;
}
public int hashCode() {
return (this.toString()).hashCode();
}
public String toString() {
return new Boolean(value).toString();
}
}
public class Variable extends Expression {//布尔变量终结符表达式
private String name;//变量的名字
public Variable(String name) {
this.name = name;
}
public boolean interpret(Context ctx) {
return ctx.lookup(this);//在上下文中查找变量的值
}
public boolean equals(Object o) {
if (o != null && o instanceof Variable) {
return this.name.equals(((Variable) o).name);
}
return false;
}
public int hashCode() {
return (this.toString()).hashCode();
}
public String toString() {
return name;
}
}
public class And extends Expression {//非终结符and表达式
//and操作有两个操作数
private Expression left, right;
public And(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public boolean interpret(Context ctx) {
//使用递归左右计算
return left.interpret(ctx) && right.interpret(ctx);
}
public boolean equals(Object o) {
if (o != null && o instanceof And) {
return this.left.equals(((And) o).left) && this.right.equals(((And) o).right);
}
return false;
}
public int hashCode() {
return (this.toString()).hashCode();
}
public String toString() {
return "(" + left.toString() + " AND " + right.toString() + ")";
}
}
public class Or extends Expression {//非终结符or表达式
private Expression left, right;
public Or(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public boolean interpret(Context ctx) {
return left.interpret(ctx) || right.interpret(ctx);
}
public boolean equals(Object o) {
if (o != null && o instanceof Or) {
return this.left.equals(((Or) o).left) && this.right.equals(((Or) o).right);
}
return false;
}
public int hashCode() {
return (this.toString()).hashCode();
}
public String toString() {
return "(" + left.toString() + " OR " + right.toString() + ")";
}
}
public class Not extends Expression {//非终结符not表达式
private Expression exp;//一元操作
public Not(Expression exp) {
this.exp = exp;
}
public boolean interpret(Context ctx) {
return !exp.interpret(ctx);
}
public boolean equals(Object o) {
if (o != null && o instanceof Not) {
return this.exp.equals(((Not) o).exp);
}
return false;
}
public int hashCode() {
return (this.toString()).hashCode();
}
public String toString() {
return " (Not " + exp.toString() + ")";
}
}
public class Client {//场景
private static Context ctx;
private static Expression exp;
public static void main(String[] args) {
ctx = new Context();
//开始创建抽象语法树:
Variable x = new Variable("x");//创建变量
Variable y = new Variable("y");//创建变量
Constant c = new Constant(true);//创建常量
ctx.assign(x, false);//给变量分配置
ctx.assign(y, true);//给变量分配置
//生成布尔表达式:((true AND x) OR (y AND (Not x)))
exp = new Or(new And(c, x), new And(y, new Not(x)));
//下面开始调用解释器操作interpret()
System.out.println("x = " + x.interpret(ctx));//打印:x = false
System.out.println("y = " + y.interpret(ctx));//打印:x = true
//打印:((true AND x) OR (y AND (Not x))) = true
System.out.println(exp.toString() + " = " + exp.interpret(ctx));
}
}
应用场景
当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。
效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。
调停者(Mediator)模式
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使它们松散耦合。当这些对象中的某些对象之间的相互作用发生改变时,不会立即影响到其他一些对象之间的交互,从而保证这些交互可以彼此独立的变化。
为什么需要这个模式
下面图中这些对象既会影响另的对象,又会被别的对象所影响,几乎每一个对象都需要与其他的对象发生相互作用,而这种相互作用表现为一个对象与另一个对象的直接耦合。
通过引入调停者对象,可以将系统的网状结构变成以中介者为中心的星形结构,如下图,它们通过调停者与另一个对象发生相互作用。
模式结构
public interface Mediator {// 抽象的调停者角色
// 表示在调停者这里可以协调各同事类的动作
void colleagueChanged(Colleague colleague);
}
public class ConcreteMediator implements Mediator {//具体的调停者
private ConcreteColleague1 col1;//持有的同事1
private ConcreteColleague2 col2;//持有的同事2
/*
* 事件方法,当某个同事的状态发生了变化时,这个同事对象可以调用这个事件
* 方法来通知调停者,从而更新所有有关的同事对象。
*/
public void colleagueChanged(Colleague colleague) {
col1.action();
col2.action();
}
public ConcreteColleague1 getConcreteColleague1() {
return col1;
}
public void setConcreteColleague1(ConcreteColleague1 col1) {
this.col1 = col1;
}
public ConcreteColleague2 getConcreteColleague2() {
return col2;
}
public void setConcreteColleague2(ConcreteColleague2 col2) {
this.col2 = col2;
}
}
public abstract class Colleague {// 抽象同事角色
private Mediator mediator;// 持有的调停者对象
public Colleague(Mediator med) {
mediator = med;
}
public Mediator getMediator() {
return mediator;
}
/*
* 动作,由子类实现。一个同事对象在得知其他对角有变化时
* 会执行这个操作
*/
public abstract void action();
// 对象的内部状态改变后调用此方法通知所有相关同事
public void change() {
mediator.colleagueChanged(this);
}
}
public class ConcreteColleague1 extends Colleague {//具体的同事类1
private String helloWords = "happy new year!";
public ConcreteColleague1(Mediator med) {
super(med);
}
public void action() {
System.out.println(helloWords+" from Colleague1");
}
public void setHelloWords(String helloWords) {
this.helloWords = helloWords;
}
}
public class ConcreteColleague2 extends Colleague {//具体的同事类2
public ConcreteColleague2(Mediator med) {
super(med);
}
public void action() {
System.out.println("hello from Colleague2");
}
}
public class Client {//场景
public static void main(String[] args) {
ConcreteMediator med = new ConcreteMediator();
ConcreteColleague1 c1 = new ConcreteColleague1(med);
ConcreteColleague2 c2 = new ConcreteColleague2(med);
med.setConcreteColleague1(c1);
med.setConcreteColleague2(c2);
med.colleagueChanged(null);
c1.setHelloWords("good job!");
c1.change();
}
}
优点
适当使用调停者模式可以较少使用静态继承关系,使得具体同事类可以更加容易地被复用。
适当使用调停者模式可以避免同事对象之间的过度耦合,使得调用停类与同事类可以相对独立地演化。
调停者模式将多对多的相互作用转化为一对多的相互作用,使得对象之间的关系更加易于维护和理解。
调停者模式将对象的行为和协作抽象化,把对象在小尺度的行为上与其他对象的相互作用分开处理。
应用场景
一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
想定制一个分布在多个类中的行为,而又不想生成太多的子类。