十三、中介者模式與解釋器模式詳解

19.中介者模式

19.1.課程目標

1、掌握中介者模式和解釋器模式的應用場景。

2、瞭解設計群聊的底層邏輯。

3、掌握解析表達式的基本原理。

4、理解中介者模式和解釋器模式的優缺點。

19.2.內容定位

適合參與軟件框架設計開發的人群。

19.3.迭代器模式

中介者模式( Mediator Pattern )又稱為調解者模式或調停者模式。用一箇中介對象封裝一系列的對象交互,中介者使各對象不需要顯示地相互作用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互。屬於行為型模式。

原文:Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

中介者模式包裝了一系列對象相互作用的方式,使得這些對象不必相互明顯作用。從而使它 們可以鬆散耦合。當某些對象之間的作用發生改變時,不會立即影響其他的一些對象之間的作 用。保證這些作用可以彼此獨立的變化。其核心思想是,通過中介者解耦系統各層次對象的直接耦合,層次對象的對外依賴通信統統交由中介者轉發。

19.4.應用場景

在現實生活中,中介者的存在是不可缺少的,如果沒有了中介者,我們就不能與遠方的朋友進行交流了。各個同事對象將會相互進行引用,如果每個對象都與多個對象進行交互時,將會 形成如下圖所示的網狀結構。

十三、中介者模式與解釋器模式詳解

從上圖可以發現,每個對象之間過度耦合,這樣的既不利於信息的複用也不利於擴展。如果引 入了中介者模式,那麼對象之間的關係將變成星型結構,採用中介者模式之後會形成如下圖所 示的結構:

十三、中介者模式與解釋器模式詳解

從上圖可以發現,使用中介者模式之後,任何一個類的變化,只會影響中介者和美本身,不像 之前的設計,任何一個類的變化都會引起其關聯所有類的變化。這樣的設計大大減少了系統的 耦合度。

其實我們日常生活中每天在刷的朋友圈,就是一箇中介者。還有我們所見的信息交易平臺, 也是中介者模式的體現。

十三、中介者模式與解釋器模式詳解

中介者模式是用來降低多個對象和類之間的通信複雜性。這種模式通過提供一箇中介類,將 系統各層次對象間的多對多關係變成一對多關係,中介者對象可以將複雜的網狀結構變成以調 停者為中心的星形結構,達到降低系統的複雜性,提高可擴展性的作用。

若系統各層次對象之間存在大量的關聯關係,即層次對象呈複雜的網狀結構,如果直接讓它 們緊耦合通信,會造成系統結構變得異常複雜,且其中某個層次對象發生改變,則與其緊耦合 的相應層次對象也需進行修改,系統很難進行維護。而通過為該系統增加一箇中介者層次對象, 讓其他各層次需對外通信的行為統統交由中介者進行轉發,系統呈現以中介者為中心進行通訊 的星形結構,系統的複雜性大大降低。

簡單的說就是多個類相互耦合,形成了網狀結構,則考慮使用中介者模式進行優化。總結中 介者模式適用於以下場景:

1、 系統中對象之間存在複雜的引用關係,產生的相互依賴關係結構混亂且難以理解;

2、 交互的公共行為,如果需要改變行為則可以增加新的中介者類。

首先來看下中介者模式的通用UML類圖:

十三、中介者模式與解釋器模式詳解

從 UML類圖中,我們可以看到,中介者模式主要包含4 個角色:

抽象中介者( Mediator) : 定義統一的接口,用於各同事角色之間的通信;

具體中介者(ConcreteMediator) : 從具體的同事對象接收消息,向具體同事對象發出命 令 ,協調各同事間的協作;

抽象同事類(Colleague ) :每一個同事對象均需要依賴中介者角色,與其他同事間通信時, 交由中介者進行轉發協作;

具體同事類( ConcreteColleague ) : 負責實現自發行為( Self-Method ) , 轉發依賴方法 ( Dep-Method ) 交由中介者進行協調。

19.5.使用中介者模式設計群聊場景

假設我們要構建一個聊天室系統,用戶可以向聊天室發送消息,聊天室會向所有的用戶顯示 消息。實際上就是用戶發信息與聊天室顯示的通信過程,不過用戶無法直接將信息發給聊天室, 而是需要將信息先發到服務器上,然後服務器再將該消息發給聊天室進行顯示。具體代碼如下。

創建User類:

<code> public class User {
     private String name;
     private ChatMediator mediator;
 ​
     public User(String name, ChatMediator mediator) {
         this.name = name;
         this.mediator = mediator;
    }
 ​
     public void sendMessage(String msg) {
         this.mediator.send2ChatRoom(this, msg);
    }
 ​
     public String getName() {
         return name;
    }
 }/<code>

創 建 ChatRoom類:

<code> public class ChatRoom {
     private ChatMediator mediator;
 ​
     public ChatRoom(ChatMediator mediator) {

         this.mediator = mediator;
         this.mediator.setChatRoom(this);
    }
 ​
     public void showMsg(User user, String msg) {
         System.out.println("[" + user.getName() + "] :" + msg);
    }
 }/<code>

中介者

<code> public abstract class ChatMediator {
     protected ChatRoom room;
 ​
     public void setChatRoom(ChatRoom room) {
         this.room = room;
    }
 ​
     public abstract void send2ChatRoom(User user, String msg);
 }/<code>
<code> public class ServerChatMediator extends ChatMediator {
     @Override
     public void send2ChatRoom(User user, String msg) {
         this.room.showMsg(user, msg);
    }
 }/<code>

編寫客戶端測試代碼:

<code> public class Test {
     public static void main(String[] args) {
         ChatMediator mediator = new ServerChatMediator();
         ChatRoom room = new ChatRoom(mediator);
 ​
         User tom = new User("Tom",mediator);
         User jerry = new User("Jerry",mediator);
         tom.sendMessage("Hi! I am Tom.");
         jerry.sendMessage("Hello! My name is Jerry.");
    }
 }/<code>

運行結果如下:

十三、中介者模式與解釋器模式詳解

19.6.中介者模式在源碼中的體現

首先來看JDK中的Timer類 ,打開Timer的結構我們發現Timer類中有很多的schedule() 重載方法,如下圖:

十三、中介者模式與解釋器模式詳解

我們任意點開其中一個方法,發現所有方法最終都是調用了私有的sched()方法,我們來看 —下它的源碼: ::

<code> public class Timer {
     public void schedule(TimerTask task, long delay, long period) {
         if (delay < 0)

             throw new IllegalArgumentException("Negative delay.");
         if (period <= 0)
             throw new IllegalArgumentException("Non-positive period.");
         sched(task, System.currentTimeMillis()+delay, -period);
    }
     
     private void sched(TimerTask task, long time, long period) {
         if (time < 0)
             throw new IllegalArgumentException("Illegal execution time.");
 ​
         // Constrain value of period sufficiently to prevent numeric
         // overflow while still being effectively infinitely large.
         if (Math.abs(period) > (Long.MAX_VALUE >> 1))
             period >>= 1;
 ​
         synchronized(queue) {
             if (!thread.newTasksMayBeScheduled)
                 throw new IllegalStateException("Timer already cancelled.");
 ​
             synchronized(task.lock) {
                 if (task.state != TimerTask.VIRGIN)
                     throw new IllegalStateException(
                         "Task already scheduled or cancelled");
                 task.nextExecutionTime = time;
                 task.period = period;
                 task.state = TimerTask.SCHEDULED;
            }
 ​
             queue.add(task);
             if (queue.getMin() == task)
                 queue.notify();
        }
    }
 }    /<code>

而且,我們發現,不管是什麼樣的任務都被加入到一個隊列中順序執行。我們把這個隊列中 的所有對象稱之為"同事”。同事之間通信都是通過Timer來協調完成的,Timer就承擔了中 介者的角色。

19.7.中介者模式的優缺點

優點:

1、 減少類間依賴,將多對多依賴轉化成了一對多,降低了類間耦合;

2、 類間各司其職,符合迪米特法則。

缺點:

1、中介者模式中將原本多個對象直接的相互依賴變成了中介者和多個同事類的依賴關係。當同事類越多時,中介者就會越臃腫,變得複雜且難以維護。

19.8.思維導圖

十三、中介者模式與解釋器模式詳解

20.解釋器模式

20.1.定義

解釋器模式( Interpreter Pattern ) 是指給定一門語言,定義它的文法的一種表示,並定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。是一種按照規定的語法(文法)進行 解析的模式,屬於行為型模式。

原文:Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

就比如編譯器可以將源碼編譯解釋為機器碼,讓 CPU能進行識別並運行。解釋器模式的作用其實與編譯器一樣,都是將一些固定的文法(即語法)進行解釋,構建出一個解釋句子的解釋器。簡單理解,解釋器是一個簡單語法分析工具,它可以識別句子語義,分離終結符號和非終結符號,提取出需要的信息,讓我們能針對不同的信息做出相應的處理。其核心思想是識別文法,構建解釋。

20.2.解釋器模式的應用場景

其實我們每天都生活在解釋器模式中,我們平時所聽到的音樂都可以通過簡譜記錄下來;還 有戰爭年代發明的摩爾斯電碼(又譯為摩斯密碼,Morse code ) , 其實也是一種解釋器。

十三、中介者模式與解釋器模式詳解

我們的程序中,如果存在一種特定類型的問題,該類型問題涉及多個不同實例,但是具備固 定文法描述,那麼可以使用解釋器模式對該類型問題進行解釋,分離出需要的信息,根據獲取 的信息做出相應的處理。簡而言之,對於一些固定文法構建一個解釋句子的解釋器。解釋器模 式適用於以下應用場景:

1、一些重複出現的問題可以用一種簡單的語言來進行表達;

2、一個簡單語法需要解釋的場景。

首先來看下解釋器模式的通用UML類圖:

十三、中介者模式與解釋器模式詳解

從 UML類圖中,我們可以看到,解釋器模式 主要包含四種角色:

抽象表達式(Expression ) : 負責定義一個解釋方法interpret , 交由具體子類進行具體解釋 ;

終結符表達式(TerminalExpression ) :實現文法中與終結符有關的解釋操作。文法中的每 一個終結符都有一個具體終結表達式與之相對應,比如公式R=R1+R2 , R1和 R2就是終結符, 對應的解析R1和 R2的解釋器就是終結符表達式。通常一個解釋器模式中只有一個終結符表達 式 ,但有多個實例,對應不同的終結符( R1 , R2 ) ;

非終結符表達式(NonterminalExpression ) : 實現文法中與非終結符有關的解釋操作。文 法中的每條規則都對應於一個非終結符表達式。非終結符表達式一般是文法中的運算符或者其 他關鍵字,比如公式R=R1 +R2中 ,”+ "就是非終結符,解 析 ”的解釋器就是一個非終結符 表達式。非終結符表達式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結 符表達式;

上下文環境類(Context ) :包含解釋器之外的全局信息。它的任務一般是用來存放文法中 各個終結符所對應的具體值,比 如 R=R1 +R2,給 R1賦值100 , 給 R2賦值200 , 這些信息需 要存放到環境中。

20.3.使用解釋器模式解析數學表達式

下面我們用解釋器模式來實現一個數學表達式計算器,包含加減乘除運算。

首先定義抽象表達式角色IArithmeticInterpreter接口 :

<code> public interface IArithmeticInterpreter {
     int interpret();
 }/<code>

創建終結表達式角色Interpreter抽象類:

<code> public abstract class Interpreter implements IArithmeticInterpreter {
 ​
     protected IArithmeticInterpreter left;
     protected IArithmeticInterpreter right;
 ​

     public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
         this.left = left;
         this.right = right;
    }
 }/<code>

分別創建非終結表達式角色加、減、乘、除解釋器,加法運算表達式AddInterpreter類 :

<code> public class AddInterpreter extends Interpreter {
 ​
     public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
         super(left, right);
    }
 ​
     public int interpret() {
         return this.left.interpret() + this.right.interpret();
    }
 }/<code>

減法運算表達式Subinterpreter類 :

<code> public class SubInterpreter extends Interpreter {
     public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
         super(left, right);
    }
 ​
     public int interpret() {
         return this.left.interpret() - this.right.interpret();
    }
 }/<code>

乘法運算表達式Multilnterpreter類 :

<code> public class MultiInterpreter extends Interpreter {
 ​
     public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
         super(left,right);
    }
 ​
     public int interpret() {
         return this.left.interpret() * this.right.interpret();
    }
 }/<code>

除法運算表達式Divinterpreter類 :

<code> public class DivInterpreter extends Interpreter {
 ​
     public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
         super(left,right);
    }
 ​
     public int interpret() {
         return this.left.interpret() / this.right.interpret();
    }
 }/<code>

數字表達式 NumInterpreter類 :

<code> public class NumInterpreter implements IArithmeticInterpreter {
     private int value;
 ​
     public NumInterpreter(int value) {
         this.value = value;
    }
 ​
 ​
     public int interpret() {
         return this.value;
    }
 }/<code>

創建計算器GPCalculator類 :

<code> public class GPCalculator {
     private Stack<iarithmeticinterpreter> stack = new Stack<iarithmeticinterpreter>();
 ​
     public GPCalculator(String expression) {
         this.parse(expression);
    }
 ​
     private void parse(String expression) {
         String [] elements = expression.split(" ");
         IArithmeticInterpreter leftExpr, rightExpr;
 ​
         for (int i = 0; i < elements.length ; i++) {
             String operator = elements[i];
             if (OperatorUtil.isOperator(operator)){
                 leftExpr = this.stack.pop();
                 rightExpr = new NumInterpreter(Integer.valueOf(elements[++i]));
                 System.out.println("出棧: " + leftExpr.interpret() + " 和 " + rightExpr.interpret());
                 this.stack.push(OperatorUtil.getInterpreter(leftExpr, rightExpr,operator));
                 System.out.println("應用運算符: " + operator);

            }
             else{
                 NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i]));
                 this.stack.push(numInterpreter);
                 System.out.println("入棧: " + numInterpreter.interpret());
            }
        }
    }
 ​
     public int calculate() {
         return this.stack.pop().interpret();
    }
 }/<iarithmeticinterpreter>/<iarithmeticinterpreter>/<code>

工具類Operatorutil具體代碼:

<code> public class OperatorUtil {
 ​
     public static boolean isOperator(String symbol) {
         return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*"));
    }
 ​
     public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) {
         if (symbol.equals("+")) {
             return new AddInterpreter(left, right);
        } else if (symbol.equals("-")) {
             return new SubInterpreter(left, right);
        } else if (symbol.equals("*")) {
             return new MultiInterpreter(left, right);
        } else if (symbol.equals("/")) {
             return new DivInterpreter(left, right);
        }
         return null;
    }
 }/<code>

編寫客戶端代碼:

<code> public class Test {
     public static void main(String[] args) {
         System.out.println("result: " + new GPCalculator("10 + 30").calculate());
         System.out.println("result: " + new GPCalculator("10 + 30 - 20").calculate());
         System.out.println("result: " + new GPCalculator("100 * 2 + 400 * 1 + 66").calculate());
    }
 }/<code>

運行結果如下:

十三、中介者模式與解釋器模式詳解

20.4.解釋器模式在源碼中的體現

JDK源碼中的Pattern對正則表達式的編譯和解析。

<code> public final class Pattern implements java.io.Serializable {
     private Pattern(String p, int f) {
         pattern = p;
         flags = f;
 ​
         // to use UNICODE_CASE if UNICODE_CHARACTER_CLASS present
         if ((flags & UNICODE_CHARACTER_CLASS) != 0)
             flags |= UNICODE_CASE;
 ​
         // Reset group index count
         capturingGroupCount = 1;
         localCount = 0;
 ​

         if (pattern.length() > 0) {
             compile();
        } else {
             root = new Start(lastAccept);
             matchRoot = lastAccept;
        }
    }
     
     public static Pattern compile(String regex) {
         return new Pattern(regex, 0);
    }
     
     public static Pattern compile(String regex, int flags) {
         return new Pattern(regex, flags);
    }
 }       /<code>

再來看 Spring 中的 ExpressionParser 接口。

<code> public interface ExpressionParser {
     Expression parseExpression(String var1) throws ParseException;
 ​
     Expression parseExpression(String var1, ParserContext var2) throws ParseException;
 }/<code>

這裡的源碼我們不深入講解,通過前面我們自己編寫的案例大致能夠清楚其原理。我們不妨 來編寫一段客戶端代碼驗證一下功能。我們編寫如下測試代碼:

<code> public class Test {
     public static void main(String[] args) {
         ExpressionParser parser = new SpelExpressionParser();
         Expression expression = parser.parseExpression("100 * 2 + 400 * 1 + 66");
         int result = (Integer) expression.getValue();
         System.out.println("計算結果是:" + result);
    }
 }/<code>

其運行結果是:

十三、中介者模式與解釋器模式詳解

和我們所期望的是一致的。

20.5.解釋器模式的優缺點

優點:

1、擴展性強:在解釋器模式中由於語法是由很多類表示的,當語法規則更改時,只需修改 相應的非終結符表達式即可;若擴展語法時,只需添加相應非終結符類即可;

2、增加了新的解釋表達式的方式;

3、易於實現文法:解釋器模式對應的文法應當是比較簡單且易於實現的,過於複雜的語法 並不適合使用解釋器模式。

缺點:

1、 語法規則較複雜時,會引起類膨脹:解釋器模式每個語法都要產生一個非終結符表達式, 當語法規則比較複雜時,就會產生大量的解釋類,增加系統維護困難;

2、 執行效率比較低:解釋器模式採用遞歸調用方法,每個非終結符表達式只關心與自己有 關的表達式,每個表達式需要知道最終的結果,因此完整表達式的最終結果是通過從後往前遞 歸調用的方式獲取得到。當完整表達式層級較深時,解釋效率下降,且出錯時調試困難,因為 遞歸迭代層級太深。

20.6.思維導圖

十三、中介者模式與解釋器模式詳解

20.7.作業

1、你認為中介者模式和哪些設計模式最容易混淆?

  • 責任鏈模式、 命令模式、 中介者模式和觀察者模式用於處理請求發送者和接收者之間的不同連接方式:責任鏈按照順序將請求動態傳遞給一系列的潛在接收者, 直至其中一名接收者對請求進行處理。命令在發送者和請求者之間建立單向連接。中介者清除了發送者和請求者之間的直接連接, 強制它們通過一箇中介對象進行間接溝通。觀察者允許接收者動態地訂閱或取消接收請求。
  • 外觀模式和中介者的職責類似: 它們都嘗試在大量緊密耦合的類中組織起合作。外觀為子系統中的所有對象定義了一個簡單接口, 但是它不提供任何新功能。 子系統本身不會意識到外觀的存在。 子系統中的對象可以直接進行交流。中介者將系統中組件的溝通行為中心化。 各組件只知道中介者對象, 無法直接相互交流。
  • 中介者和觀察者之間的區別往往很難記住。 在大部分情況下, 你可以使用其中一種模式, 而有時可以同時使用。 讓我們來看看如何做到這一點。中介者的主要目標是消除一系列系統組件之間的相互依賴。 這些組件將依賴於同一個中介者對象。 觀察者的目標是在對象之間建立動態的單向連接, 使得部分對象可作為其他對象的附屬發揮作用。有一種流行的中介者模式實現方式依賴於觀察者。 中介者對象擔當發佈者的角色, 其他組件則作為訂閱者, 可以訂閱中介者的事件或取消訂閱。 當中介者以這種方式實現時, 它可能看上去與觀察者非常相似。當你感到疑惑時, 記住可以採用其他方式來實現中介者。 例如, 你可永久性地將所有組件鏈接到同一個中介者對象。 這種實現方式和觀察者並不相同, 但這仍是一種中介者模式。假設有一個程序, 其所有的組件都變成了發佈者, 它們之間可以相互建立動態連接。 這樣程序中就沒有中心化的中介者對象, 而只有一些分佈式的觀察者。

2、繼續完善數學表達式解釋器,完成用小括號提升運算優先級的功能。

<code> class Solution {
 ​
     public int evaluateExpr(Stack<object> stack) {
 ​
         int res = 0;
 ​
         if (!stack.empty()) {
             res = (int) stack.pop();
        }
 ​
         // Evaluate the expression till we get corresponding ')'
         while (!stack.empty() && !((char) stack.peek() == ')')) {
 ​
             char sign = (char) stack.pop();
 ​
             if (sign == '+') {
                 res += (int) stack.pop();
            } else if (sign == '-') {
                 res -= (int) stack.pop();
            } else if (sign == '*') {
                 res *= (int) stack.pop();
            } else if (sign == '/') {
                 res /= (int) stack.pop();
            }
        }
         return res;
    }
 ​
     public int calculate(String s) {
 ​
         int operand = 0;
         int n = 0;
         Stack<object> stack = new Stack<object>();
 ​
         for (int i = s.length() - 1; i >= 0; i--) {
 ​
             char ch = s.charAt(i);
 ​
             if (Character.isDigit(ch)) {
 ​
                 // Forming the operand - in reverse order.
                 operand = (int) Math.pow(10, n) * (int) (ch - '0') + operand;
                 n += 1;

 ​
            } else if (ch != ' ') {
                 if (n != 0) {
 ​
                     // Save the operand on the stack
                     // As we encounter some non-digit.
                     stack.push(operand);
                     n = 0;
                     operand = 0;
 ​
                }
                 if (ch == '(') {
 ​
                     int res = evaluateExpr(stack);
                     stack.pop();
 ​
                     // Append the evaluated result to the stack.
                     // This result could be of a sub-expression within the parenthesis.
                     stack.push(res);
 ​
                } else {
                     // For other non-digits just push onto the stack.
                     stack.push(ch);
                }
            }
        }
 ​
         //Push the last operand to stack, if any.
         if (n != 0) {
             stack.push(operand);
        }
 ​
         // Evaluate any left overs in the stack.
         return evaluateExpr(stack);
    }
 }/<object>/<object>/<object>/<code>

測試"2 + 3 + (3 * 4) + 2 + (4 - 3)"

結果為20


分享到:


相關文章: