【设计模式】命令模式和解释器模式

命令模式

在 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());

    }

}

在这里插入图片描述

总结

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

缺点

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值