十三、中介者模式与解释器模式详解

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


分享到:


相關文章: