23种设计模式分析(5):行为型模式

1.1.13 Observer观察者模式


  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接口或抽象类。
  在JDK中实际上有一个对Observer模式的简单的实现,就是类java.util.Observerable和接口java.util.Observer。 java.util.Observerable类对应于Subject,而java.util.Observer接口就是对应于Observer了。可见JDK中并没有把这两个部分都设计为接口,而是把Subject设计成类java.util.Observerable,它提供了部分的实现,简化了许多编程的工作。当然,这也减少了一定的灵活性。
  下面列出了Observer和Observerable的函数列表,及其简单的功能说明。
  java.util.Observer:
  public void update(Observeable obs, Object obj)
  java.util.Observer 接口很简单,只定义了这一个方法,狭义的按照Observer模式的说法,Observer应该在这个方法中调用Subject的getXXX()方法来取得最新的状态,而实际上,你可以只是在其中对Subject的某些事件进行响应。这便是Java中的代理事件模型的一个雏形---对事件进行响应。只不过,在Observer模式中将事件特定化为某个状态/数据的改变了。
  Java.util.Observable:
  public void addObserver(Observer obs)
  向Subject注册一个Observer。也就是把这个Observer对象添加到了一个java.util.Observable内部的列表中。在JDK中对于这个列表是简单的通过一个java.util.Vector类来实现的,而实际上,在一些复杂的Observer模式的应用中,需要把这个部分单另出来形成一个Manager类,来管理Subject和Observer之间的映射。这样,Subject和Observer进一步的被解藕,程序也会具有更大的灵活性。
  public void deleteObserver(Observer obs)
  从Subject中删除一个已注册的Observer的引用。
  public void deleteObservers()
  从Subjec中删除所有注册的Observer的引用。
  public int countObservers()
  返回注册在Subject中的Observer个数。
  protected void setChanged()
  设置一个内部的标志以指明这个Ovserver的状态已经发生改变。注意这是一个protected方法,也就是说只能在Observer类和其子类中被调用,而在其它的类中是看不到这个方法的。
  protected void clearChanged()
  清除上叙的内部标志。它在notifyObservers()方法内部被自动的调用,以指明Subject的状态的改变已经传递到Ovserver中了。
  public Boolean hasChanged()
  确定Subject的状态是否发生了改变。
  public void notifyObservers(Object obj)
  它首先检查那个内部的标志,以判断状态是否改变,如果是的话,它会调用注册在Subject中的每个Observer的update()方法。在JDK中这个方法内部是作为synchronized来实现的,也就是如果发生多个线程同时争用一个java.util.Observerable的notifyObservers()方法的话,他们必须按调度的等待着顺序执行。在某些特殊的情况下,这会有一些潜在的问题:可能在等待的过程中,一个刚刚被加入的Observer会被遗漏没有被通知到,而一个刚刚被删除了的Observer会仍然收到它已经不想要了的通知。
  public void notifyObservers()
  等价于调用了notifyObservers(null)。
  因而在Java中应用Observer就很简单了,需要做的是:让需要被观察的Subject对象继承java.util.Observerable,让需要观察的对象实现java.util.Observer接口,然后用java.util.Observerable的addObserver(Observer obj)方法把Observer注册到Subject对象中。这已经完成了大部分的工作了。然后调用java.util.Observerable的notifyObservers(Object arg)等方法,就可以实现Observer模式的机理。我们来看一个简单使用了这个模式的例子。这个例子有三个类:FrameSubject,DataSubject,FrameObject和EntryClass,FrameSubject中用户可以设置被观察的值,然后自动的会在FrameObject中显示出来,DataSubject封装被观察的值,并且充当Observer模式中的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。
  注意ConcreteObserver的更新操作中并不知道是哪个ConcreteSubject的状态改变了,它只知道所有的ConcreteSubject都满足Subject接口(例子中的Observable类),因此它只要调用其中的方法,根据获得的状态来改变自己的状态。当ConcreteObserver需要知道是哪个ConcreteSubject的状态被改变时,就要保存这个ConcreteSubject的引用,可通过组合的方式包含进来。
  JDK中这个Observer模式的实现,对于一般的Observer模式的应用,已经是非常的足够了的。但是一方面它用一个类来实现了Subject,另一方面它使用Vector来保存Subject对于Observer的引用,这虽然简化了编程的过程,但会限制它在一些需要更为灵活,复杂的设计中的应用,有时候(虽然这种情况不多),我们还不得不重新编写新的Subject对象和额外的Manager对象来实现更为复杂的Observer模式的应用。
  在C++方面,ATL则提供了IConnectionPointContainer、IConnectionPoint、IEnumConnectionPoints、IEnumConnections等以支持所谓的连接点及可连接对象等,而在MFC中,一个文档可以对应多个视图,而当文档发生更新后,可以通过UpdateAllViews来同步更新所有视图,这虽然并非严格意义上的Observer模式的应用,但其本质是相似的。
  另外,聊天室软件实现就可以使用Observer模式。服务器端ChatRoom相当于Subject,客户端各个Chater相当于Observer。当ChatRoom接收到消息时(这里引起ChatRoom发生变化的是各个Chater本身),有两种通知方式,一种是不对消息的内容进行解析,而是直接通过Notify通知所有Chater,由所有Chater自己负责检查该消息是否是发送给自己的,这在一定程度上与广播非常相似,所以有时候, 我们可以采用Observer模式来模拟软件广播。为避免广播风暴,实际的ChatRoom通常使用另一种方式,结合使用Mediator模式和Observer模式,将Subject设计成一个Mediator,对消息内容进行检查,进而根据消息内容进行消息的Publish。
  实现方式:具体被观察者实现Observable接口、维护一个观察者列表、并具有注册和反注册观察者、通知所有观察者的操作。具体观察者实现Observer接口,在更新方法中传入观察者,根据观察者的状态变化更新自己的状态。

  在以下任一情况下可以使用观察者模式:
  1、当一个抽象模型有两个方面,其中一个方面依赖于另一方面,将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  2、当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
  3、当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。
  Observer模式实现了表示层和数据逻辑层的分离,允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。它也使你可以在不改动目标和其他的观察者的前提下增加观察者。
  下面是观察者模式其它一些优缺点:
  1、目标和观察者间的抽象和松耦合。
一个目标所知道的仅仅是它有一系列观察者,每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。
  因为目标和观察者不是紧密耦合的,它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,这样就保持了系统层次的完整。如果目标和观察者混在一块,那么得到的对象要么横贯两个层次,违反了层次性;要么必须放在这两层的某一层中,这可能会损害层次抽象。
  2、支持广播通信。不像通常的请求,目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣。它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
  3、意外的更新。因为一个观察者并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外,如果依赖准则的定义或维护不当,常常会引起错误的更新,这种错误通常很难捕捉。
  简单的更新协议不提供具体细节说明目标中什么被改变了,这就使得上述问题更加严重。如果没有其他协议帮助观察者发现什么发生了改变,它们可能会被迫尽力减少改变。


1.1.14 Command命令模式


  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();
    }
}
  回味一下, 基本思想是把一个请求/命令本身封装成一个对象,并有一个请求者和一个或多个接收者,真正的命令函数代码在接收者中实现,每个命令写一个命令类,它要引入某个接收者并有一个执行函数,真正的执行动作转发给接收者完成。请求者一般会维护一个命令对象队列以便对请求进行排队(当命令很多时),然后调用各命令对象执行请求。注意任何一个类都可以成为接收者。
  实际上,对于请求的处理有两种不同的方法,一种是Command只充当Proxy,将请求转发给某个接受者对象(如上面的例子),还有一种是Command对象自己处理完所有的请求操作。当然,这只是两个极端,更多的情况是Command完成一部分的工作,而另外的一部分这则交给接受者对象来处理。
  当Command对象自己完成请求操作时,一个Command对象中包含了待执行的一个动作语句序列,以执行特定的任务(当然并不是随便怎么样的语句序列都可以构成一个Command对象的),按照Command模式的设计,Command对象和它的调用者Invoker之间应该具有接口约定的(如上面例子中都有undo, redo,display,这时可以抽象出一个接口来)。也就是说,Invoker得到Command对象的引用,并调用其中定义好的方法,而当Command对象改变(或者是对象本身代码改变,或者干脆完全另外的一个Command对象)之后,Invoker中的代码可以不用更改。这样,通过封装请求,可以把任务和任务的实现加以分离。
  在新的JDK的代理事件模型中,一个事件监听者类EventListener监听某个事件,并根据接口定义,实现特定的操作。比如,当用Document对象的addDocumentListener(DocumentListener listener) 方法注册了一个DocumentListener后,以后如果在Document对象中发生文本插入的事件,DocumentListener中实现的insertUpdate(DocumentEvent e)方法就会被调用,如果发生文本删除事件,removeUpdate(DocumentEvent e)方法就会被调用。如果把Document理解成被观察者,各DocumentListener理解成观察者,则这是Observer模式,但如果把Document理解成Invoker,各DocumentListener理解成Command,则这也是一个Command模式。
  最经典的Command模式的应用,莫过于Swing中的Action接口。Action实际上继承的是ActionListener,也就是说,它也是一个事件监听者(EventListener)。但是Action作为一种ActionListener的扩展机制,提供了更多的功能。它可以在其中包含对这个Action动作的一个或者多个文字的或图标的描叙,它提供了Enable/Disable的功能许可性标志。并且,一个Action对象可以被多个Invoker,比如实现相同功能的按钮,菜单,快捷方式所共享。而这些Invoker都知道如何加入一个Action,并充分利用它所提供的扩展机制。可以说,在这儿Action更像一个对象了,因为它不仅仅提供了对方法的实现,更提供了对方法的描叙和控制。可以方便的描叙任何的事务,这更是面向对象方法的威力所在。
  JDK的AbstractUndoableEdit为我们提供了基本的Undo/Redo支持,当我们需要为Java应用引入Undo/Redo操作时,只需简单地从AbstractUndoableEdit类派生子类将操作封装成类即可。下面是一个简单的封装adjust操作的例子(代码取自XModeler,Java Code):

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,以及他们之间的协作关系。
  又如,要实现这样的一个任务:Task Schedule。也就是说,我想对多个任务进行安排,比如扫描磁盘,我希望它每1个小时进行一次,而备份数据,我希望它半个小时进行一次,等等。但是,我并不希望作为TaskSchedule的类知道各个任务的细节内容,TaskSchedule应该只是知道Task本身,而对具体的实现任务的细节并不理会。因而在这儿,我们就需要对TaskSchedule和Task进行解耦,将任务和具体的实现分离出来,这不正是Command模式的用武之地吗?
  在设计一般用途的软件的时候,在C或者C++语言中,用的很多的一个技巧就是回调函数(Callback),所谓的回调函数,意指先在系统的某个地方对函数进行注册,让系统知道这个函数的存在,然后在以后,当某个事件发生时,再调用这个函数对事件进行响应。在C或者C++中,实现的回调函数方法是使用函数指针。但是 在Java中,并不支持指针,而Command模式则可作为这一回调机制的面向对象版本。Command中的各命令动作函数相当于回调函数,先把各Command对象注册到Invoker中,以后当Invoker触发某个请求时,这些Command对象中的函数就会被调用。
   在下面的情况下应当考虑使用命令模式:
  1、使用命令模式作为"CallBack"在面向对象系统中的替代。
"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
   2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
   3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
  4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
   5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令,使用命令模式来实现交易结构可以使系统增加新的交易类型。
  Command模式允许请求的一方和接收请求的一方能够独立演化, 从而且有以下的优点:
  Command模式使新的命令很容易地被加入到系统里。
  允许接收请求的一方决定是否要否决(Veto)请求。
  能较容易地设计一个命令队列。
  可以容易地实现对请求的Undo和Redo。
  在需要的情况下,可以较容易地将命令记入日志。
  Command模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
  Command类与其他任何别的类一样,可以修改和推广。
  你可以把Command对象聚合在一起,合成为Composite Command。比如宏Command便是Composite Command的例子。
  由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
   Command模式的缺点如下:

  使用Command模式会导致某些系统有过多的具体Command类。某些系统可能需要几十个,几百个甚至几千个具体Command类,这会使Command模式在这样的系统里变得不实际。


1.1.15 Chain of Responsibility责任链模式


  Chain of Responsibility模式定义: 为了避免请求的发送者和接收者之间的耦合关系,使多个接受对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
  在不止一个对象可以处理客户端请求的时候,为了使每个对象都有处理请求的机会,把这些对象顺序地串联起来形成一个链,每个被串联的对象都有一个指向下一个对象的指针,当请求到来是,按照顺序,先有第一个对象来处理这个请求,这个对象有两个选择:要么处理,要么把请求传给下一个对象(每个对象都有这两个选择),就这样一直到有一个对象来处理这个请求为止,一旦有一个对象处理了这个请求就停止对请求的传递。当然也有可能到了对象链的最后,也没有一个对象来处理请求。我觉得这个与我们平常写的if…else if…else…要完成的功能太相似了。
  以上所说的只是Chain of Responsibility的一种情况,有的书上叫它纯责任链模式,也就是规定一个具体处理者角色只能对请求作出两种动作:自己处理、传给下家。不能出现处理了一部分,把剩下的传给了下家的情况,而且请求在责任链中必须被处理,而不能出现无果而终的结局。另一种情况也就是非纯责任链模式,把能处理的部分处理掉,处理不了的部分再转发给下一处理者。
  对于Chain中的各个对象,可以采用类似单向链表或双向链表的结构,保存各自后继或者前接元素的引用/指针来实现链接(紧密链接),也可以仅仅由各对象的Container保存这种逻辑上的链接关系,而各对象彼此间并不需要知晓Chain中的其它对象(松散链接)。对于Chain的结构,一个链可以是一条线,一个树(普通的树,或者平衡树、红黑树等),也可以是一个环,在使用中可以根据需要选择。
  以下是紧密链接CoR模式的典型结构:

图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);
    }
}
  大部分的实际应用中使用不纯的责任链模式,即每个对象都要处理部分请求。纯的责任链用的较少,因为如果一个对象不能处理任何请求,那它的存在就没有意义了。如果坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大的意义了。
  MFC中的消息处理机制也是典型的CoR模式,它采用的是前面所说的松散链接的线状Chain。虽然采用CoR进行消息处理的机制在MFC中根深蒂固地存在着(MFC采用消息映射表屏蔽了内部的复杂处理,DefWindowProc等辅助机制更进一步让整个消息处理显得自然而简单),但 采用CoR进行消息处理在现代软件设计中往往被认为是低效的。MFC,Java AWT 1.0都是这样的例子。
  Java的1.0版中AWT库使用了责任链模式和命令模式来处理GUI的事件。由于视窗部件往往处在容器部件里面,因此当事件发生在一个部件上时,此部件的事件处理器可以处理此事件,然后决定是否将事件向上级容器部件传播;上级容器部件接到事件后可以在此处理此事件然后决定是否将事件再次向上级容器部件传播,如此往复,直到事件到达顶层部件。一般责任链模式要求链上所有的对象都继承自一个共同的父类,在AWT中这个类便是java.awt.Component类。AWT1.0的事件处理的模型是不纯的责任链模式。显然,由于每一级的部件在接到事件时,都可以处理此事件;而不论此事件是否在这一级得到处理,事件都可以停止向上传播或者继续向上传播。这是典型的不纯的责任链模式。
比如,当一个视窗部件接到一个MOUSE_CLICKED事件时,事件首先传播到它所发生的部件上,然后向其容器部件传播。容器可以选择处理这个事件,或者再将此事件向更高一级的容器部件传播。事件如此一级级地向上传播,就像水底的气泡一点一点地冒到水面上一样,因此又叫做事件浮升(Event Bubbling)机制。下面就是一段典型的Java1.0版的AWT库里处理事件的代码:
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处理,无需轮询)。
  但这不表示基于CoR的消息处理没有存在的价值,基于CoR的消息处理可以极大程度上降低消息的发送者与接收者之间的耦合程度,同时,在有些情况下,Observer模式并不能替代CoR模式进行消息处理,如:
  1、需要消息被多个接收者依次处理时,并且消息可能在处理的过程中被修改或者处理顺序为我们所关注时;
  2、当消息没有经过严格分类时,应用Observer模式会变得比较困难。
  总结:
   在以下情况下可以考虑使用CoR模式:
  1、有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  2、你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3、可处理一个请求的对象集合应被动态指定。
   优缺点:
  CoR模式使得发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。从而可以很大程度地降低处理请求与处理对象,以及处理对象之间的耦合关系。
  需要注意的时,CoR模式并不创建Responsibility Chain,Responsibility Chain的创建必须由系统的其它部分创建出来,即需要上层应用对Chain进行维护,同时,需要注意的是,在大多数情况下,Chain的构建应该遵循一定的规则,如由主到次,由特殊到普通(就好像在if..else if...else中,将较General的限制条件放在前面,可能使得较Rigorous的限制条件永远得不到执行)。

1.1.16 Interpreter解释器模式


  GOF定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。Interpreter是行为模式之一,它是一种特殊的设计模式,它建立一个解释器,对于特定的计算机程序设计语言,用来解释预先定义的文法。Interpreter描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造一个语言的文法。
  Interpreter(解释器)模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在这里使用语言这个词似乎将Interpreter模式的应用范围限制到了一个过于狭小的范围,毕竟,我们不是自然语言或者编程语言设计者,需要注意的是,这里所讨论的语言并非指复杂的自然语言或者编程语言,而是一种语义标记,Interpreter模式负责实现这种标记的定义以及将其转换为实际功能。
  Interpreter模式的结构图如下:


图16-1 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类中提供的,当然还有更好、更专业的实现方式。
  上面的Context中只有一条文法规则,一般每一条文法规则用一个类来表示,通过类之间的组合来实现一定的语法规则。
  基本思想:用一个Context类存储所有文法规则(使用HashMap),并有一个文法规则的推导函数。一个AbstractExpression类,里面有一个解释操作函数,解释操作要用到文法规则,故要传入Context对象。在具体的终结符表达式中,解释操作直接返回终结符。在只有单个非终结符的表达式中,解释操作要用到一条文法规则,故把调用转发给Context对象的推导函数,以获得规则右边的符号。在一般的非终结符表达式中,对其中的每个符号递归调用此符号的解释操作。
  NonterminalExpression对象由各单个的终结符或非终结符组成,是这些原子对象的组合,而他们都有相同的解释操作接口,可见Interpreter模式实际上只是Composite模式的针对特殊应用的一个特化版本,但这并不表示Interpreter模式的提出没有意义,Interpreter模式的提出使Composite模式扩展到了更深的领域--语义转换,这一点有着一定的实际意义。
  对于终结符,GOF建议采用享元模式来共享它们的拷贝,因为它们要多次重复出现。但是考虑到享元模式的使用局限性,我建议还是当你的系统中终结符重复的足够多的时候再考虑享元模式。
  当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。
  上面的说法未免有点过于formal,简单说来,基应用可归纳为:
   1、当我们需要一个命令解释器以解释执行用户输入的指令时可以考虑使用Interpreter模式。
  2、当我们需要根据客户的输入对数据进行不同显示时可以考虑使用Interpreter模式。

  <<Java Design Pattern: A Tutorial>>一书给出了一个根据客户输入对数据进行不同形式输出的例子,很好地体现了以上几点。实质上,更多的情况下,我们可能通过组合客户选择(多项选择)得到一个命令串,交给专门的Interpreter进行解释执行,并将处理结果回显给客户,这样,可以很好地避免客户输入错误造成的不必要的复杂性。
   当存在以下情况时Interpreter模式效果最好:
  1、文法简单的情况。对于复杂的文法,文法的类层次变得庞大而无法管理,此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
  2、效率不是一个关键问题的情况。最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。
   优缺点:
  解释器模式提供了一个简单的方式来执行语法,而且容易修改或者扩展语法。
  在解释器中不同的规则是由不同的类来实现的,这样使得添加一个新的语法规则变得简单。
正如应用部分所说,Interpreter模式比较适用于文法简单,并且对处理的效率要求较低的情况,由于Interpreter模式使用类来标示每一条文法规则,因此,当处理复杂文法时,各规则类之间的调用及组合关系将变得难以维护,效率也将大大降低。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值