1. 程式人生 > 其它 >設計模式之行為型模式--責任鏈模式、直譯器模式

設計模式之行為型模式--責任鏈模式、直譯器模式

一、責任鏈模式(職責鏈模式)

模式的定義與特點

責任鏈(Chain of Responsibility)模式的定義:為了避免請求傳送者與多個請求處理者耦合在一起,於是將所有請求的處理者通過前一物件記住其下一個物件的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止。

在責任鏈模式中,客戶只需要將請求傳送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞。所以責任鏈將請求的傳送者和請求的處理者解耦了。

主要優點

1.降低了物件之間的耦合度。該模式使得一個物件無須知道到底是哪一個物件處理其請求以及鏈的結構,傳送者和接收者也無須擁有對方的明確資訊。
2.增強了系統的可擴充套件性。可以根據需要增加新的請求處理類,滿足開閉原則。
3.增強了給物件指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
4.責任鏈簡化了物件之間的連線。每個物件只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。
5.責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個物件完成,明確各類的責任範圍,符合類的單一職責原則。

主要缺點。

1.不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
2.對比較長的職責鏈,請求的處理可能涉及多個處理物件,系統性能將受到一定影響。
3.職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設定而導致系統出錯,如可能會造成迴圈呼叫。

模式的結構與實現

通常情況下,可以通過資料鏈表來實現職責鏈模式的資料結構。

1. 模式的結構

職責鏈模式主要包含以下角色。
1.抽象處理者(Handler)角色:定義一個處理請求的介面,包含抽象處理方法和一個後繼連線。
2.具體處理者(Concrete Handler)角色

:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。
3. 客戶類(Client)角色:建立處理鏈,並向鏈頭的具體處理者物件提交請求,它不關心處理細節和請求的傳遞過程。

責任鏈模式的本質是解耦請求與處理,讓請求在處理鏈中能進行傳遞與被處理;理解責任鏈模式應當理解其模式,而不是其具體實現。責任鏈模式的獨到之處是將其節點處理者組合成了鏈式結構,並允許節點自身決定是否進行請求處理或轉發,相當於讓請求流動起來。

其結構圖如圖 1 所示。客戶端可按圖 2 所示設定責任鏈。

             圖1 責任鏈模式的結構圖
		圖2 責任鏈
2. 模式的實現
main
package chainOfResponsibility;

public class ChainOfResponsibilityPattern {
    public static void main(String[] args) {
        //組裝責任鏈
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.setNext(handler2);
        //提交請求
        handler1.handleRequest("two");
    }
}
抽象處理者角色Handler

//抽象處理者角色
abstract class Handler {
    private Handler next;

    public void setNext(Handler next) {
        this.next = next;
    }

    public Handler getNext() {
        return next;
    }

    //處理請求的方法
    public abstract void handleRequest(String request);
}


具體處理者角色ConcreteHandler1

//具體處理者角色1
class ConcreteHandler1 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("one")) {
            System.out.println("具體處理者1負責處理該請求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求!");
            }
        }
    }
}

具體處理者角色ConcreteHandler2
//具體處理者角色2
class ConcreteHandler2 extends Handler {
    public void handleRequest(String request) {
        if (request.equals("two")) {
            System.out.println("具體處理者2負責處理該請求!");
        } else {
            if (getNext() != null) {
                getNext().handleRequest(request);
            } else {
                System.out.println("沒有人處理該請求!");
            }
        }
    }
}

程式執行結果如下:
具體處理者2負責處理該請求!

模式的應用場景

前邊已經講述了關於責任鏈模式的結構與特點,下面介紹其應用場景,責任鏈模式通常在以下幾種情況使用。
1.多個物件可以處理一個請求,但具體由哪個物件處理該請求在執行時自動確定。
2.可動態指定一組物件處理請求,或新增新的處理者。
3.需要在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。

模式的擴充套件

職責鏈模式存在以下兩種情況。
1.純的職責鏈模式:一個請求必須被某一個處理者物件所接收,且一個具體處理者對某個請求的處理只能採用以下兩種行為之一:自己處理(承擔責任);把責任推給下家處理。
2.不純的職責鏈模式:允許出現某一個具體處理者物件在承擔了請求的一部分責任後又將剩餘的責任傳給下家的情況,且一個請求可以最終不被任何接收端物件所接收。

二、直譯器模式

模式的定義與特點

直譯器(Interpreter)模式的定義:給分析物件定義一個語言,並定義該語言的文法表示,再設計一個解析器來解釋語言中的句子。也就是說,用編譯語言的方式來分析應用中的例項。這種模式實現了文法表示式處理的介面,該介面解釋一個特定的上下文。

這裡提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的語法規則,而“句子”是語言集中的元素。例如,漢語中的句子有很多,“我是中國人”是其中的一個句子,可以用一棵語法樹來直觀地描述語言中的句子。

主要優點

1.擴充套件性好。由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。
2.容易實現。在語法樹中的每個表示式節點類都是相似的,所以實現其文法較為容易。

主要缺點。

1.執行效率較低。直譯器模式中通常使用大量的迴圈和遞迴呼叫,當要解釋的句子較複雜時,其執行速度很慢,且程式碼的除錯過程也比較麻煩。
2.會引起類膨脹。直譯器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數將急劇增加,導致系統難以管理與維護。
3.可應用的場景比較少。在軟體開發中,需要定義語言文法的應用例項非常少,所以這種模式很少被使用到。

模式的結構與實現

直譯器模式常用於對簡單語言的編譯或分析例項中,為了掌握好它的結構與實現,必須先了解編譯原理中的“文法、句子、語法樹”等相關概念。

1) 文法

文法是用於描述語言的語法結構的形式規則。沒有規矩不成方圓,例如,有些人認為完美愛情的準則是“相互吸引、感情專一、任何一方都沒有戀愛經歷”,雖然最後一條準則較苛刻,但任何事情都要有規則,語言也一樣,不管它是機器語言還是自然語言,都有它自己的文法規則。例如,中文中的“句子”的文法如下。
〈句子〉::=〈主語〉〈謂語〉〈賓語〉
〈主語〉::=〈代詞〉|〈名詞〉
〈謂語〉::=〈動詞〉
〈賓語〉::=〈代詞〉|〈名詞〉
〈代詞〉你|我|他
〈名詞〉7大學生I筱霞I英語
〈動詞〉::=是|學習

注:這裡的符號“::=”表示“定義為”的意思,用“〈”和“〉”括住的是非終結符,沒有括住的是終結符。

2) 句子

句子是語言的基本單位,是語言集中的一個元素,它由終結符構成,能由“文法”推匯出。例如,上述文法可以推出“我是大學生”,所以它是句子。

3) 語法樹

語法樹是句子結構的一種樹型表示,它代表了句子的推導結果,它有利於理解句子語法結構的層次。圖 1 所示是“我是大學生”的語法樹。

          圖1 句子“我是大學生”的語法樹

有了以上基礎知識,現在來介紹直譯器模式的結構就簡單了。直譯器模式的結構與組合模式相似,不過其包含的組成元素比組合模式多,而且組合模式是物件結構型模式,而直譯器模式是類行為型模式。

1. 模式的結構

直譯器模式包含以下主要角色。
1.抽象表示式(Abstract Expression)角色:定義直譯器的介面,約定直譯器的解釋操作,主要包含解釋方法 interpret()。
2.終結符表示式(Terminal Expression)角色:是抽象表示式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表示式與之相對應。
3.非終結符表示式(Nonterminal Expression)角色:也是抽象表示式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應於一個非終結符表示式。
4.環境(Context)角色:通常包含各個直譯器需要的資料或是公共的功能,一般用來傳遞被所有直譯器共享的資料,後面的直譯器可以從這裡獲取這些值。
5.客戶端(Client):主要任務是將需要分析的句子或表示式轉換成使用直譯器物件描述的抽象語法樹,然後呼叫直譯器的解釋方法,當然也可以通過環境角色間接訪問直譯器的解釋方法。

直譯器模式的結構圖如圖 2 所示。

        圖2 直譯器模式的結構圖

2. 模式的實現

直譯器模式實現的關鍵是定義文法規則、設計終結符類與非終結符類、畫出結構圖,必要時構建語法樹,其程式碼結構如下:

抽象表示式類AbstractExpression
package net.biancheng.c.interpreter;

//抽象表示式類
interface AbstractExpression {
    public void interpret(String info);    //解釋方法
}
終結符表示式類TerminalExpression
//終結符表示式類
class TerminalExpression implements AbstractExpression {
    public void interpret(String info) {
        //對終結符表示式的處理
    }
}
非終結符表示式類NonterminalExpression
//非終結符表示式類
class NonterminalExpression implements AbstractExpression {
    private AbstractExpression exp1;
    private AbstractExpression exp2;

    public void interpret(String info) {
        //非對終結符表示式的處理
    }
}

模式的應用場景

前面介紹瞭解釋器模式的結構與特點,下面分析它的應用場景。
1.當語言的文法較為簡單,且執行效率不是關鍵問題時。
2.當問題重複出現,且可以用一種簡單的語言來進行表達時。
3.當一個語言需要解釋執行,並且語言中的句子可以表示為一個抽象語法樹的時候,如 XML 文件解釋。

注意:直譯器模式在實際的軟體開發中使用比較少,因為它會引起效率、效能以及維護等問題。如果碰到對錶達式的解釋,在 Java 中可以用 Expression4J 或 Jep 等來設計。

模式的擴充套件

在專案開發中,如果要對資料表示式進行分析與計算,無須再用直譯器模式進行設計了,Java 提供了以下強大的數學公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它們可以解釋一些複雜的文法,功能強大,使用簡單。

環境類Context
//環境類
class Context {
    private AbstractExpression exp;

    public Context() {
        //資料初始化
    }

    public void operation(String info) {
        //呼叫相關表示式類的解釋方法
    }
}

現在以 Jep 為例來介紹該工具包的使用方法。Jep 是 Java expression parser 的簡稱,即 Java 表示式分析器,它是一個用來轉換和計算數學表示式的 Java 庫。通過這個程式庫,使用者可以以字串的形式輸入一個任意的公式,然後快速地計算出其結果。而且 Jep 支援使用者自定義變數、常量和函式,它包括許多常用的數學函式和常量。

使用前先下載 Jep 壓縮包,解壓後,將 jep-x.x.x.jar 檔案移到選擇的目錄中,在 Eclipse 的“Java 構建路徑”對話方塊的“庫”選項卡中選擇“新增外部 JAR(X)...”,將該 Jep 包新增專案中後即可使用其中的類庫。

下面以計算存款利息為例來介紹。存款利息的計算公式是:本金x利率x時間=利息,其相關程式碼如下:

JepDemo
package net.biancheng.c.interpreter;

import com.singularsys.jep.*;

public class JepDemo {
    public static void main(String[] args) throws JepException {
        Jep jep = new Jep();
        //定義要計算的資料表示式
        String 存款利息 = "本金*利率*時間";
        //給相關變數賦值
        jep.addVariable("本金", 10000);
        jep.addVariable("利率", 0.038);
        jep.addVariable("時間", 2);
        jep.parse(存款利息);    //解析表示式
        Object accrual = jep.evaluate();    //計算
        System.out.println("存款利息:" + accrual);
    }
}

程式執行結果如下:
存款利息:760.0