Observer(观察者)模式又被称作发布-订阅(Publish-Subscribe)模式,是一种对象的行为型模式。《设计模式》一书对Observer是这样描述的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。
Observer模式的功用,是希望两个(或多个)对象,我们称之为Subject和Observer,当一方的状态发生改变的时候,另一方能够得到通知。也就是说,作为Observer的一方,能够监视到Subject的某个特定的状态变化,并为之做出反应。一个简单的例子就是:当一个用户视图中的数据被用户改变后,后端的数据库能够得到更新,而当数据库被其他方式更新后,用户视图中的数据显示也会随之改变。
Observer的结构如下:
图13-1 观察者模式
上面的类图中包括如下组成部分:
Subject(抽象主题)角色:主题角色把所有对观察者对象的引用保存在一个列表里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。
Observer(抽象观察者)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。
ConcreteSubject(具体主题)角色:将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observerable)。具体主题角色通常用一个具体子类实现。
ConcreteObserver(具体观察者)角色:存储与主题的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。
举个例子,在现实生活中,父母与孩子是最亲密的人。父母做为孩子(被观察者)的监护人(观察者),当孩子和别人打架后,一定会告诉他的父母这件事,当孩子获得奖学金后,也一定会告诉他的父母。下面我用Observer实现这个程序,代码如下:
import java.util.ArrayList; //ConcreteSubject:被观察者,即ConcreteObservable class Children { static private ArrayList<Observer> obs; //维护一个观察者列表 static private String state = null; static { obs = new ArrayList<>(); } //添加观察者 public static void attach(Observer o) { obs.add(o); } //删除观察者 public static void detach(Observer o) { obs.remove(o); } //被观察对象的状态 public void setState(String str) { state = str; } public String getState() { return state; } //状态改变时通知所有观察者 public void notifyObs() { for (Observer o : obs) { o.update(this); //使观察者更新状态 } } } //观察者接口 interface Observer { public void update(Children child); } //具体的观察者 class Parent implements Observer { @Override public void update(Children child) { switch (child.getState()) { //使用Java 7的switch语句对string的判断支持 case "fight": System.out.println("Parent,他和别人打架了"); break; case "scholarship": System.out.println("告诉Parent,他得到了奖学金"); break; } } } class Mother implements Observer { @Override public void update(Children child) { switch (child.getState()) { //使用Java 7的switch语句对string的判断支持 case "fight": System.out.println("告诉Mother,他和别人打架了"); break; case "scholarship": System.out.println("告诉Mother,他得到了奖学金"); break; } } } //客户端程序 public class ObserverClient { public static void main(String[] args) { Children child = new Children(); Observer parent = new Parent(); Observer mother = new Mother(); Children.attach(parent); Children.attach(mother); child.setState("fight"); child.notifyObs(); child.setState("scholarship"); child.notifyObs(); } }输出如下:
Parent,他和别人打架了 告诉Mother,他和别人打架了 告诉Parent,他得到了奖学金 告诉Mother,他得到了奖学金里没有写抽象的Subject,直接实现了一个具体的Subject。一般被观察者必须可观察,即必须要维护一个观察者列表,并具有添加删除观察者的操作,可以把这些抽象成一个Subject接口或抽象类。
import java.util.Observable; import javax.swing.JFrame; import java.awt.event.*; //ConcreteSubject的数据部分 class DataSubject extends Observable { private int widthInfo; private int heightInfo; public int getWidthInfo() { return widthInfo; } public void setWidthInfo(int widthInfo) { this.widthInfo = widthInfo; //数据改变后,setChanged()必须被调用以通知视图,否则视图的 //notifyObservers()方法会不起作用 this.setChanged(); } public void setHeightInfo(int heightInfo) { this.heightInfo = heightInfo; this.setChanged(); } public int getHeightInfo() { return heightInfo; } } //ConcreteSubject的视图部分 class FrameSubject extends JFrame { //因为无法使用多重继承(不能同时继承JFrame和Observerable),这儿就只能使用对象组合的方式 //来引入一个java.util.Observerable对象了。 DataSubject subject = new DataSubject(); //...... //这个方法转发添加Observer消息到DateSubject。 public void registerObserver(java.util.Observer o) { //注册观察者 subject.addObserver(o); } //数据改变,事件被触发后调用notifyObservers()来通知Observer。 void jButton1_actionPerformed(ActionEvent e) { subject.setWidthInfo(Integer.parseInt(jTextField1.getText())); subject.setHeightInfo(Integer.parseInt(jTextField2.getText())); subject.notifyObservers(); } //...... } //ConcreteObserver:要有更新方法,并传入Subject,根据Subject的状态变化来 //来更新自己的状态 class FrameObserver extends JFrame implements java.util.Observer { //观察的数据 int widthInfo = 0; int heightInfo = 0; //...... //在update()方法中实现对数据的更新和其它必要的反应 @Override public void update(Observable o, Object arg) { DataSubject subject = (DataSubject) o; widthInfo = subject.getWidthInfo(); heightInfo = subject.getHeightInfo(); jLabel1.setText("The heightInfo from subject is: "); jLabel3.setText(String.valueOf(heightInfo)); jLabel2.setText("The widthInfo from subject is: "); jLabel4.setText(String.valueOf(widthInfo)); } //....... } public class EntryClass { public static void main(String[] args) { //...... FrameSubject frame1 = new FrameSubject(); FrameObserver frame2 = new FrameObserver(); //在Subject中注册Observer,将两者联系在一起 frame1.registerObserver(frame2); //...... frame1.setVisible(true); frame2.setVisible(true); //...... } }可见当FrameSubject中的数据改变时触发事件通知FrameObserver,使FrameObserver的数据也得到更新。这里真正的ConcreteSubject是数据部分DataSubject,因此由它实现Observable接口,注册观察者的工作委托给视图FrameSubject,然后在内部将注册调用转发给DataSubject。
在以下任一情况下可以使用观察者模式:
1、当一个抽象模型有两个方面,其中一个方面依赖于另一方面,将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2、当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
3、当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
Observer模式实现了表示层和数据逻辑层的分离,允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
下面是观察者模式其它一些优缺点:
1、目标和观察者间的抽象和松耦合。一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。
因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,这样就保持了系统层次的完整。如果目标和观察者混在一块,那么得到的对象要么横贯两个层次,违反了层次性;要么必须放在这两层的某一层中,这可能会损害层次抽象。
2、支持广播通信。不像通常的请求,目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣。它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
3、意外的更新。因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。
简单的更新协议不提供具体细节说明目标中什么被改变了,这就使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。
Command模式定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
Commad模式是一种对象行为模式,它可以对请求发送者(sender)和接收者(receiver)完全解耦(decoupling)。"发送者" 是请求操作的对象,"接收者" 是接收请求并执行某操作的对象。有了 "解耦",请求者对接收者的接口一无所知。这里,"请求"(request)这个术语指的是要被执行的命令。Command模式还让我们可以对 "何时" 以及 "如何" 完成请求进行改变。因此,Command模式为我们提供了灵活性和可扩展性。
Command模式的结构图如下:
图14-1 Command模式类图
上图中包括如下角色:
客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
具体命令(ConcreteCommand)角色:定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。execute()方法通常叫做执行方法。
请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
从Command模式的结构似乎可以看出几分Adapter模式的影子,确实如此,Invoker(源)通过Command(Adapter)对Receiver(Adptee)来执行Receiver::Action(),但Command模式的意义不在于此,Command模式的目的在于通过将需要操作的对象及所执行的操作封装成一个独立的对象,而不在于简单的接口的转换;此外,二者还有一个显著的区别在于Adaptee对client往往是不可见的,而Receiver对Client往往是可见的。Client只知道Adapter::Request(),而不知(或无需知道)其内部其实调的是Adaptee::SpecialRequest(),而在Command模式中Client往往需要显式地把一个Receiver对象传给一个ConcreteCommand对象,以使其能调用Receiver::Action()。
下面例子模拟文档的显示、撤消和重做命令:
class Document { //Receiver:请求接收者,负责执行各命令 public void display() { //显示命令 System.out.println("显示文档内容"); } public void undo() { //撤消命令 System.out.println("撤销文档内容"); } public void redo() { System.out.println("重做文档内容"); } } interface DocumentCommand { //Command:抽象的接口,对多种命令进行抽象 public void execute(); } class DisplayCommand implements DocumentCommand { //具体命令,要聚集一个命令接收对象, //并在执行方法中将命令执行转发给接收者 private Document document; public DisplayCommand(Document doc) { document = doc; } @Override public void execute() { document.display(); } } class UndoCommand implements DocumentCommand { //具体命令 private Document document; public UndoCommand(Document doc) { document = doc; } @Override public void execute() { document.undo(); } } class RedoCommand implements DocumentCommand { //具体命令 private Document document; public RedoCommand(Document doc) { document = doc; } @Override public void execute() { document.redo(); } } class DocumentInvoker { //Invoker:命令请求者 private DisplayCommand _dcmd; private UndoCommand _ucmd; private RedoCommand _rcmd; public DocumentInvoker(DisplayCommand dcmd, UndoCommand ucmd, RedoCommand rcmd) { this._dcmd = dcmd; this._ucmd = ucmd; this._rcmd = rcmd; } public void display() { //调用命令对象执行请求,即把命令执行转发给命令对象 _dcmd.execute(); } public void undo() { _ucmd.execute(); } public void redo() { _rcmd.execute(); } /* 要对请求进行排队、调度时可用下面的方式实现 private java.util.ArrayList<Command> comList=new java.util.ArrayList<Command>(); public void addCommand(Command com){ comList.add(com); } public void schedulePerform(){ for(Command co:comList){ co.execute(); } } */ } public class CommandTest { public static void main(String[] args) { Document doc = new Document(); //客户端要把接收者对象传给具体命令对象 DisplayCommand display = new DisplayCommand(doc); UndoCommand undo = new UndoCommand(doc); RedoCommand redo = new RedoCommand(doc); DocumentInvoker invoker = new DocumentInvoker(display, undo, redo); invoker.display(); invoker.undo(); invoker.redo(); } }回味一下, 基本思想是把一个请求/命令本身封装成一个对象,并有一个请求者和一个或多个接收者,真正的命令函数代码在接收者中实现,每个命令写一个命令类,它要引入某个接收者并有一个执行函数,真正的执行动作转发给接收者完成。请求者一般会维护一个命令对象队列以便对请求进行排队(当命令很多时),然后调用各命令对象执行请求。注意任何一个类都可以成为接收者。
public class SelectTool extends AbstractTool { //... public void mouseDragged(MouseEvent e) { //... if (!isDragRegistered) { desk.addUndoableEdit(new AdjustUndoableEdit(desk, (ModelObject) oldC)); isDragRegistered = true; } } } public class MainFrame extends JFrame implements PropertyChangeListener{ //Invoker,the Main Window //... protected void commandRedo() { // Find active child window JInternalFrame internalframe = this.getActiveChild(); // notify the active view to execute the command if (internalframe != null && internalframe instanceof ChildFrame) { ChildFrame child = (ChildFrame) internalframe; ModelView view = child.getView(); view.redo(); } } } public class AdjustUndoableEdit extends AbstractUndoableEdit { //ConcreteCommand ModelObject com; // state Desktop desk; // Receiver, the Active View Rectangle oldRec; public AdjustUndoableEdit(Desktop desk, ModelObject com) { this.com = com; this.desk = desk; oldRec = getBounds(); } Rectangle getBounds() { Rectangle rec = ( (Component) com).getBounds(); return rec; } public String getUndoPresentationName() { return "Undo_Adjust"; } public String getRedoPresentationName() { return "Redo_Adjust"; } public void undo() throws CannotUndoException { // Execute 1 super.undo(); Rectangle newRec = getBounds(); ((Component)com).setBounds(oldRec); oldRec = newRec; desk.fireAssociatorChanged(); } public void redo() throws CannotRedoException { // Execute 2 super.redo(); Rectangle newRec = getBounds(); ((Component)com).setBounds(oldRec); oldRec = newRec; desk.fireAssociatorChanged(); } }上面的代码片断,虽然不是一个完整的Command模式的应用,但其中已经可以十分清晰地看到各个Role,以及他们之间的协作关系。
使用Command模式会导致某些系统有过多的具体Command类。某些系统可能需要几十个,几百个甚至几千个具体Command类,这会使Command模式在这样的系统里变得不实际。
图16-1 CoR模式类图示意
上述类图中包括如下角色:
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法,以设定和返回对下家的引用。这个角色通常由一个抽象类或接口实现。
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
先举一个纯责任链模式的例子。现在的女孩子找男朋友基本上都有三个要求:有车、有房、有责任心,如果你这三样都没有,就险了。代码如下:
class Boy { //请求的接收者:完成对请求的响应 private boolean hasCar; //是否有车 private boolean hasHouse; //是否有房 private boolean hasResponsibility; //是否有责任心 public Boy() { } public Boy(boolean hasCar, boolean hasHouse, boolean hasResponsibility) { this.hasCar = hasCar; this.hasHouse = hasHouse; this.hasResponsibility = hasResponsibility; } public boolean isHasCar() { return hasCar; } public void setHasCar(boolean hasCar) { this.hasCar = hasCar; } public boolean isHasHouse() { return hasHouse; } public void setHasHouse(boolean hasHouse) { this.hasHouse = hasHouse; } public boolean isHasResponsibility() { return hasResponsibility; } public void setHasResponsibility(boolean hasResponsibility) { this.hasResponsibility = hasResponsibility; } } interface Handler { //抽象处理者: 也可以多定义出一个方法,以设定和返回对下一个处理者的引用 public void handleRequest(Boy boy); } class CarHandler implements Handler { //具体处理者1 private Handler handler; //下一处理者的引用 public CarHandler(Handler handler) { //由构造函数传入下一处理者 this.handler = handler; } public Handler getHandler() { return handler; } public void setHandler(Handler handler) { //也可用setter方法传入下一处理者 this.handler = handler; } @Override public void handleRequest(Boy boy) { //本对象不能处理任何请求,直接把请求传递给下一处理者 handler.handleRequest(boy); } } class HouseHandler implements Handler { //具体处理者2 private Handler handler; public HouseHandler(Handler handler) { this.handler = handler; } public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } @Override public void handleRequest(Boy boy) { //本处理器能处理所有请求,处理完后终止,不传给下一处理者 if (boy.isHasCar()) { System.out.println("呵呵,我有辆车"); } else { System.out.println("我也没有房"); } if (boy.isHasHouse()) { System.out.println("没想到吧,我还有房子"); } else { System.out.println("我也没有房"); } if (boy.isHasResponsibility()) { System.out.println("同时我也非常有责任心"); } else { System.out.println("更没有责任心"); } } } class ResponsibilityHandler implements Handler { //具体处理器3 private Handler handler; public ResponsibilityHandler(Handler handler) { this.handler = handler; } public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; } @Override public void handleRequest(Boy boy) { //本处理器能处理所有请求,处理完后终止,不传给下一处理者 if (boy.isHasCar()) { System.out.println("呵呵,我有辆车"); } else { System.out.println("我也没有房"); } if (boy.isHasHouse()) { System.out.println("没想到吧,我还有房子"); } else { System.out.println("我也没有房"); } if (boy.isHasResponsibility()) { System.out.println("同时我也非常有责任心"); } else { System.out.println("更没有责任心"); } } } class GirlForTest { //请求发起者 public static void main(String[] args) { //这个boy有车,也有房,也很有责任心 Boy boy = new Boy(true, true, true); //也可以使用setHanlder方法 Handler handler = new CarHandler(new HouseHandler(new ResponsibilityHandler(null))); handler.handleRequest(boy); } }可以把上面的例子改为不纯的责任链模式。为了丰富性,这里用另外一个非纯责任链的例子。这个例子模拟了汽车组装的过程:假设一辆汽车从生产到出厂要经过以下四个过程:组装车头,车身,车尾,以及上色。具体处理者按它能处理的功能来命名。代码如下:
abstract class CarHandler { //抽象处理者:这里定义了一个设定和返回对下家的引用的方法 public static final int STEP_HANDLE_HEAD = 0; public static final int STEP_HANDLE_BODY = 1; public static final int STEP_HANDLE_TAIL = 2; public static final int STEP_HANDLE_COLOR = 3; protected CarHandler carHandler; public CarHandler setNextCarHandler(CarHandler carHandler) { this.carHandler = carHandler; return this.carHandler; } abstract public void handleCar(int lastStep); } class CarHeadHandler extends CarHandler { //具体处理者:组装车头 @Override public void handleCar(int lastStep) { if (STEP_HANDLE_HEAD <= lastStep) { //处理能处理的部分:组装车头 System.out.println("Handle car's head."); } if (carHandler != null) { carHandler.handleCar(lastStep); //不能处理的部分传递给下一处理者 } } } class CarBodyHandler extends CarHandler { @Override public void handleCar(int lastStep) { if (STEP_HANDLE_BODY <= lastStep) { System.out.println("Handle car's body."); } if (carHandler != null) { carHandler.handleCar(lastStep); } } } class CarTailHandler extends CarHandler { @Override public void handleCar(int lastStep) { if (STEP_HANDLE_TAIL <= lastStep) { System.out.println("Handle car's tail."); } if (carHandler != null) { carHandler.handleCar(lastStep); } } } class CarColorHandler extends CarHandler { @Override public void handleCar(int lastStep) { if (STEP_HANDLE_COLOR == lastStep) { System.out.println("Handle car's color."); } if (carHandler != null) { carHandler.handleCar(lastStep); } } } public class CarClient { public static void main(String[] args) { //工作流程1:先组装车头,然后是车身,车尾,最后是上色 System.out.println("---workfolow1----"); CarHandler carHandler1 = new CarHeadHandler(); carHandler1.setNextCarHandler( new CarBodyHandler() ).setNextCarHandler( new CarTailHandler() ).setNextCarHandler( new CarColorHandler() ); carHandler1.handleCar(CarHandler.STEP_HANDLE_COLOR); //工作流程2:因为某种原因,我们需要先组装车尾,然后是车身,车头,最后是上色 System.out.println( "---workfolow2---"); CarHandler carHandler2 = new CarTailHandler(); carHandler2.setNextCarHandler( new CarBodyHandler()).setNextCarHandler( new CarHeadHandler()).setNextCarHandler( new CarColorHandler()); carHandler2.handleCar(CarHandler.STEP_HANDLE_COLOR); } }大部分的实际应用中使用不纯的责任链模式,即每个对象都要处理部分请求。纯的责任链用的较少,因为如果一个对象不能处理任何请求,那它的存在就没有意义了。如果坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大的意义了。
public boolean action(Event event, Object obj) { if (event.target == btnOK) { doOKBtnAction(); } else if (event.target == btnExit) { doExitBtnAction(); } else { return super.action(event, obj); } return true; }在这段代码里面,action()判断目标部件是不是btnOK或btnExit;如果是,便运行相应的方法;如果不是,便返还true。一个方法返还true便使得事件停止浮升。
import java.applet.Applet; import java.awt.*; public class MouseSensor extends Frame { public static void main(String[] args) { MouseSensor ms = new MouseSensor(); ms.setBounds(10, 10, 200, 200); ms.show(); } public MouseSensor() { setLayout(new BorderLayout()); add(new MouseSensorCanvas(), "Center"); } } class MouseSensorCanvas extends Canvas { public boolean mouseUp(Event event, int x, int y) { System.out.println("mouse up"); return true; // Event has been handled. Do not propagate to container. } public boolean mouseDown(Event event, int x, int y) { System.out.println("mouse down"); return true; // Event has been handled. Do not propagate to container. } }
AWT1.0的事件处理的模型的缺点:
(1)AWT1.0的事件处理的模型是基于继承的。如为了给按钮添加鼠标移动处理,必须通过派生新的按钮子类来完成,也就是置换掉action()方法或者handleEvent()方法。这不是应当提倡的做法:在一个面向对象的系统里,经常使用的应当是委派,继承不应当是常态。
在一个复杂的GUI系统里,这样为所有有事件的部件提供子类,会导致很多的子类,这是不是很麻烦的吗?当然,由于事件浮升机制,可以在部件的树结构的根部部件里面处理所有的事件。但是这样一来,就需要使用复杂的条件转移语句在这个根部部件里辨别事件的起源和处理方法。这种非常过程化的处理方法很难维护,并且与面向对象的设计思想相违背。
(2)由于每一个事件都会沿着部件树结构向上传播,因此事件浮升机制会使得事件的处理变得较慢。这也是缺点之一。
比如在有些操作系统中,鼠标每移动一个色素,都会激发一个MOUSE_MOVE事件。每一个这样的事件都会沿着部件的容器树结构向上传播,这会使得鼠标事件成灾。
(3)AWT1.0的事件处理的模型只适用于AWT部件类。这是此模型的另一个缺点。
(4)消息处理与其它类的方法混杂在类的实现中,这种方式有失灵活性,不便于消息处理的动态添加、删除(虽然可以通过添加flag来封闭消息处理逻辑,但相信没有人会认为这是一种好办法),不便于管理。
基于以上诸多原因,Java设计者为了维持语言本身的简单性,坚决地淘汰了AWT中旧的基于CoR的消息处理机制,在引入新的界面方案javax.swing的同时,引入了新的基于Observer模式的消息处理机制。
以下是新的基于Observer模式的消息处理:
import java.awt.*; import java.awt.event.*; public class MouseSensor extends Frame { public static void main(String[] args) { MouseSensor ms = new MouseSensor(); ms.setBounds(10, 10, 200, 200); ms.show(); } public MouseSensor() { Canvas canvas = new Canvas(); canvas.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { System.out.println("mouse down"); } public void mouseReleased(MouseEvent e) { System.out.println("mouse up"); } }); setLayout(new BorderLayout()); add(canvas, "Center"); } }与基于CoR的消息处理相比,基于Observer的消息处理由于将消息处理交给了单独的Observer来处理,使得程序结构更清晰,而且高效(消息直接被对应的Observer处理,无需轮询)。
GOF定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。Interpreter是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。Interpreter描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造一个语言的文法。
Interpreter(解释器)模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在这里使用语言这个词似乎将Interpreter模式的应用范围限制到了一个过于狭小的范围,毕竟,我们不是自然语言或者编程语言设计者,需要注意的是,这里所讨论的语言并非指复杂的自然语言或者编程语言,而是一种语义标记,Interpreter模式负责实现这种标记的定义以及将其转换为实际功能。
Interpreter模式的结构图如下:
1、抽象表达式角色:声明一个抽象的解释操作,这个接口为所有具体表达式角色(抽象语法树中的节点)都要实现的。
什么叫做抽象语法树呢?《java与模式》中给的解释为:抽象语法树的每一个节点都代表一个语句,而在每个节点上都可以执行解释方法。这个解释方法的执行就代表这个语句被解释。由于每一个语句都代表一个常见的问题的实例,因此每一个节点上的解释操作都代表对一个问题实例的解答。
2、终结符表达式角色:具体表达式。实现与文法中的终结符相关联的解释操作。句子中的每个终结符需要该类的一个实例与之对应。
3、非终结符表达式角色:具体表达式。文法中的每条规则R=R1R2…Rn都需要一个非终结符表达式角色。对于从R1到Rn的每个符号都维护一个抽象表达式角色的实例变量。实现解释操作,解释一般要递归地调用表示从R1到Rn的那些对象的解释操作。
4、上下文(环境)角色:包含解释器之外的一些全局信息。解释器上下文环境类,用来存储解释器的上下文环境,比如需要解释的文法等。
5、客户角色:构建(或者被给定)表示该文法定义的语言中的一个特定的句子的抽象语法树,调用解释操作。
举一个加减乘除运算解释器例子,实现思路来自于《java与模式》中的例子。代码如下:
import java.util.HashMap; import java.util.Map; class Context { //上下文环境 private Map valueMap = new HashMap(); //一个变量对应一个值,相当于文法的一个推导规则 public void addValue(Variable x, int y) { Integer yi = new Integer(y); valueMap.put(x, yi); } //从左边的变量(即非终结符),返回右边的终结符 public int LookupValue(Variable x) { int i = ((Integer) valueMap.get(x)).intValue(); return i; } } //AbstractExpression:抽象表达式,也可以用接口来实现 abstract class Expression { public abstract int interpret(Context con); //声明一个抽象的解释操作 } //TerminalExpression:终结符表达式 //实现与文法中的终结符相关联的解释操作。句子中的每个 //终结符需要该类的一个实例与之对应 class Constant extends Expression { private int i; public Constant(int i) { this.i = i; } @Override public int interpret(Context con) { //用文法解释终结符:直接返回此终结符 return i; } } //单个非终结符(变量)的表达式,文法每条规则左边的非终结符需要该类的一个实例 class Variable extends Expression { @Override public int interpret(Context con) { //this为调用interpret方法的Variable对象 return con.LookupValue(this); } } //NonterminalExpression:非终结符表达式R1+R2,有多个非终结符 class Add extends Expression { private Expression left, right; public Add(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Context con) { //用文法来解释非终结符表达R1+R2 //一般要递归地调用表示从R1到Rn的那些对象的解释操作 return left.interpret(con) + right.interpret(con); } } class Subtract extends Expression { private Expression left, right; public Subtract(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Context con) { return left.interpret(con) - right.interpret(con); } } class Multiply extends Expression { private Expression left, right; public Multiply(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Context con) { return left.interpret(con) * right.interpret(con); } } class Division extends Expression { private Expression left, right; public Division(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret(Context con) { try { return left.interpret(con) / right.interpret(con); } catch (ArithmeticException ae) { System.out.println("被除数为0!"); return -11111; } } } //测试程序,计算 (a*b)/(a-b+2) public class InterpreterTest { private static Expression ex; private static Context con; public static void main(String[] args) { con = new Context(); //设置变量、常量 Variable a = new Variable(); Variable b = new Variable(); Constant c = new Constant(2); //为变量赋值 con.addValue(a, 8); con.addValue(b, 7); //这些工作相当于完成文法的存储 //运算,对句子的结构(即抽象语法树)由我们自己来分析和构造 //可见解释器模式并没有说明如何创建一个抽象语法树 ex = new Division(new Multiply(a, b), new Add(new Subtract(a, b), c)); System.out.println("运算结果为:" + ex.interpret(con)); } }解释器模式并没有说明如何创建一个抽象语法树,抽象语法树是则Client来负责构建(或被给定)的。因此它的实现可以多种多样,在上面我们是直接在客户端Test类中提供的,当然还有更好、更专业的实现方式。