【设计模式】命令模式和解释器模式
创始人
2024-06-01 22:20:50
0

命令模式

在 GoF 的《设计模式》⼀书中,命令模式是这么定义的:

The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.

命令模式将请求(命令)封装为⼀个对象,这样可以使⽤不同的请求参数化其他对象(将不同请求依赖注⼊到其他对象),并且能够⽀持请求(命令)的排队执⾏、记录⽇志、撤销等(附加控制)功能。

落实到编码实现,命令模式⽤的最核⼼的实现⼿段,是将函数封装成对象。我们知道,C 语⾔⽀持函数指针,我们可以把函数当作变量来传递。但是,在其他⼤部分编程语⾔中,函数是不能作为参数传递的。借助命令模式,我们可以将函数封装成对象。具体来说就是,设计⼀个包含这个函数的类,通过实例化这样一个对象的方式来传递函数。

命令模式的主要作⽤和应⽤场景,是⽤来控制命令的执⾏,⽐如,异步、延迟、排队执⾏命令、撤销重做命
令、存储命令、给命令记录⽇志等等。

命令模式主要有4个角色:

抽象命令(Command)角色:定义命令接口
具体命令(ConcreteCommand)角色:通常会持有接收者对象,并调用接收者对象的相应功能来完成命令要执行的操作。
接收者(Receiver)角色:真正执行命令的对象
调用者(Invoker)角色:接收客户的请求,并执行对应命令

代码示例

以mysql的客户端和服务端的交互为例,我们在cmd命令窗口输入一个命令,服务端就会执行相应命令后返回给我们

//接收者
public class MySQL {public void showDatabase(String databaseName) {System.out.println("mysql open the db:" + databaseName);}public void executeSql(String sql) {System.out.println("mysql execute the sql:" + sql);}}
public interface ICommand {void execute(String param);
}
public class ShowDatabaseCommand implements ICommand{private MySQL mySQL;public ShowDatabaseCommand(MySQL mySQL) {this.mySQL = mySQL;}@Overridepublic void execute(String param) {mySQL.showDatabase(param);}
}
public class SqlCommand implements ICommand{private MySQL mySQL;public SqlCommand(MySQL mySQL) {this.mySQL = mySQL;}@Overridepublic void execute(String param) {mySQL.executeSql(param);}
}
//调用者
public class Controller {public void execute(ICommand command, String param) {command.execute(param);}
}
public class Test {public static void main(String[] args) {MySQL mysql = new MySQL();Controller controller = new Controller();controller.execute(new ShowDatabaseCommand(mysql), "test");controller.execute(new SqlCommand(mysql), "select * from table1");}}

总结

优点 :

  • 降低耦合 : 通过引入中间件(命令抽象接口), 将请求发起者与请求的接收者进行解耦 ;
  • 扩展性高 : 如果要扩展新命令 , 直接定义新的命令对象即可

缺点 :

  • 增加复杂度 : 扩展命令会导致类的数量增加 , 增加了系统的复杂度
  • 需要针对每个命令 开发一个与之对应的命令类

命令模式VS策略模式

在策略模式中,不同的策略具有相同的⽬的、不同的实现、互相之间可以替换。⽐如,BubbleSort、SelectionSort 都是为了实现排序的,只不过⼀个是⽤冒泡排序算法来实现的,另⼀个是⽤选择排序算法来实现的。
⽽在命令模式中,不同的命令具有不同的⽬的,对应不同的处理逻辑,并且互相之间不可替换。

解释器模式

在 GoF 的《设计模式》⼀书中,解释器模式是这样定义的

Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar.

解释器模式为某个语⾔定义它的语法表⽰,并定义⼀个解释器⽤来处理这个语法。属于行为型模式。

解释器模式的核⼼思想,就是将语法解析的⼯作拆分到各个⼩类中,以此来避免⼤⽽全的解析类。⼀般的做法是,将语法规则拆分⼀些⼩的独⽴的单元,然后对每个单元进⾏解析,最终合并为对整个语法规则的解析。

解释器模式主要包含4个角色
Context 上下文环境 : 用来存储解释器的上下文环境
Expression 抽象表达式,定义一个抽象的解释方法interpret
TerminalExpression 终结符表达式, 实现文法中与终结符相关的解释操作, 一个句子中的每个终结符都对应一个终结符表达式的实例,eg. R = R1 + R2 中,R1,R2就是终结符,解析R1,R2的解释器就是终结符表达式
NonterminalExpression 非终结符表达式, 实现文法中与非终结符有关的解释操作,每个规则都对应一个非终结符表达式。eg. R = R1 + R2 中 的"+"就是一个非终结符

代码示例

我们用解释器模式来实现一个数学表达式计算器。 常见的数学运算有加减乘除四种,那就需要实现四个非终结符表达式,再加上一个数字的终结符表达式 ,一共5个表达式

public interface Expression {long interpret();
}
public class NumberExpression implements Expression {private Long number;public NumberExpression(Long number) {this.number = number;}@Overridepublic long interpret() {return number;}
}
//定义一个非终结表达式的抽象父类,提取公共属性(也可以不定义抽象父类,直接实现接口)
public abstract class AbstractMathExpression implements Expression{private Expression exp1;private Expression exp2;public AbstractMathExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}public Expression getExp1() {return exp1;}public Expression getExp2() {return exp2;}}
public class AddExpression extends AbstractMathExpression{public AddExpression(Expression exp1, Expression exp2) {super(exp1, exp2);}@Overridepublic long interpret() {return this.getExp1().interpret() + this.getExp2().interpret();}
}public class SubExpression extends AbstractMathExpression{public SubExpression(Expression exp1, Expression exp2) {super(exp1, exp2);}@Overridepublic long interpret() {return this.getExp1().interpret() - this.getExp2().interpret();}
}public class MultiExpression extends AbstractMathExpression{public MultiExpression(Expression exp1, Expression exp2) {super(exp1, exp2);}@Overridepublic long interpret() {return this.getExp1().interpret() * this.getExp2().interpret();}
}public class DivExpression extends AbstractMathExpression{public DivExpression(Expression exp1, Expression exp2) {super(exp1, exp2);}@Overridepublic long interpret() {return this.getExp1().interpret() / this.getExp2().interpret();}
}
public class MyCalculate {private Stack stack = new Stack<>();public MyCalculate(String expression) throws Exception {this.parse(expression);}//这里顺序执行,如果当前元素是数字,则放入到stack里,否则取出前一个expression进行计算private void parse(String expression) throws Exception {String[] elements = expression.split(" ");//由于乘除运算需要优先计算,先提前将表达式处理一遍List elementList = assginElement(elements);for (int i = 0; i < elementList.size(); i++) {String element = elementList.get(i);if (!isOperator(element)) {stack.push(new NumberExpression(element));} else {Expression numberExpression = stack.pop();Expression nextNumberExpression = new NumberExpression(elementList.get(++i));stack.push(getMathExpression(element, numberExpression, nextNumberExpression));}}}private List assginElement(String[] elements) throws Exception {for (int i = 0; i < elements.length; i++) {String element = elements[i];if (isMultiOrDiv(element)) {elements[i] = String.valueOf(getMathExpression(element, new NumberExpression(elements[i-1]), new NumberExpression(elements[i+1])).interpret());elements[i-1] = null;elements[i+1] = null;i ++;}}return Arrays.stream(elements).filter(Objects::nonNull).collect(Collectors.toList());}public long calculate() {return stack.pop().interpret();}private Expression getMathExpression(String operator, Expression exp1, Expression exp2) throws Exception {if ("*".equalsIgnoreCase(operator)) {return new MultiExpression(exp1, exp2);} else if ("+".equalsIgnoreCase(operator)) {return new AddExpression(exp1, exp2);} else if ("-".equalsIgnoreCase(operator)) {return new SubExpression(exp1, exp2);} else if ("/".equalsIgnoreCase(operator)) {return new DivExpression(exp1, exp2);}throw new Exception("not has the operator");}private boolean isOperator(String element) {return "+".equalsIgnoreCase(element.trim()) ||"-".equalsIgnoreCase(element.trim()) ||"*".equalsIgnoreCase(element.trim()) ||"/".equalsIgnoreCase(element.trim());}private boolean isMultiOrDiv(String element) {return "*".equalsIgnoreCase(element.trim()) ||"/".equalsIgnoreCase(element.trim());}public static void main(String[] args) throws Exception {System.out.println(new MyCalculate("100 * 2 + 400 * 2 + 66").calculate());System.out.println(new MyCalculate("30 + 30 - 10").calculate());}}

在这里插入图片描述

总结

优点
扩展性强,可以很方便的扩展语法

缺点

  1. 实际利用场景比较少
  2. 语法规则复杂时,会引起类膨胀,增加系统维护难度

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...