命令模式
在 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;
}
@Override
public void execute(String param) {
mySQL.showDatabase(param);
}
}
public class SqlCommand implements ICommand{
private MySQL mySQL;
public SqlCommand(MySQL mySQL) {
this.mySQL = mySQL;
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public long interpret() {
return this.getExp1().interpret() + this.getExp2().interpret();
}
}
public class SubExpression extends AbstractMathExpression{
public SubExpression(Expression exp1, Expression exp2) {
super(exp1, exp2);
}
@Override
public long interpret() {
return this.getExp1().interpret() - this.getExp2().interpret();
}
}
public class MultiExpression extends AbstractMathExpression{
public MultiExpression(Expression exp1, Expression exp2) {
super(exp1, exp2);
}
@Override
public long interpret() {
return this.getExp1().interpret() * this.getExp2().interpret();
}
}
public class DivExpression extends AbstractMathExpression{
public DivExpression(Expression exp1, Expression exp2) {
super(exp1, exp2);
}
@Override
public long interpret() {
return this.getExp1().interpret() / this.getExp2().interpret();
}
}
public class MyCalculate {
private Stack<Expression> 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<String> 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<String> 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());
}
}
总结
优点
扩展性强,可以很方便的扩展语法
缺点
- 实际利用场景比较少
- 语法规则复杂时,会引起类膨胀,增加系统维护难度