[Design Mode] Command Mode and Interpreter Mode

command mode

In the GoF's "Design Patterns" book, the command pattern is defined as follows:

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.

The command mode encapsulates a request (command) into an object, so that different requests can be used to parameterize other objects (dependency injection of different requests into other objects), and it can support queued execution and recording of requests (commands) Logging, undo, etc. (additional control) functions.

When it comes to coding implementation, the core implementation method used in the command mode is to encapsulate functions into objects. We know that the C language supports function pointers, and we can pass functions as variables. However, in most other programming languages, functions cannot be passed as parameters. With the command pattern, we can encapsulate functions into objects. Specifically, design a class that contains this function, and pass the function by instantiating such an object.

The main function and application scenarios of the command mode are to control the execution of commands , such as asynchronous, delay, queue execution of commands, undo and redo commands, store
commands, record logs for commands, and so on.

Command mode has four main roles:

Abstract command (Command) role: define the command interface
Concrete Command (ConcreteCommand) role: usually hold the receiver object, and call the corresponding function of the receiver object to complete the operation to be performed by the command.
Receiver (Receiver) role: the object that actually executes the command
Invoker (Invoker) role: receives the customer's request and executes the corresponding command

code example

Take the interaction between the mysql client and server as an example, we enter a command in the cmd command window, and the server will execute the corresponding command and return it to us

//接收者
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");
    }

}

Summarize

Advantages :

  • Reduce coupling: By introducing middleware (command abstract interface), decouple the request initiator from the request receiver;
  • High scalability: If you want to extend new commands, just define a new command object directly

Disadvantages :

  • Increased complexity: Extending commands will lead to an increase in the number of classes, increasing the complexity of the system
  • Need to develop a corresponding command class for each command

Command Pattern VS Strategy Pattern

In the strategy pattern, different strategies have the same purpose, different implementations, and can be replaced with each other. For example, BubbleSort and SelectionSort are both for sorting, but one is implemented with the bubble sort algorithm, and the other is implemented with the selection sort algorithm.
In the command mode, different commands have different purposes, correspond to different processing logics, and are irreplaceable with each other.

interpreter mode

In the GoF's Design Patterns book, the interpreter pattern is defined like this

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

An interpreter pattern defines a grammatical representation for a language and an interpreter for processing that grammar. Belongs to the behavioral model.

The core idea of ​​the interpreter mode is to split the work of grammar analysis into various small classes, so as to avoid large and comprehensive analysis classes. The general approach is to split the grammatical rules into some small independent units, then parse each unit, and finally combine them into the parsing of the whole grammatical rules.

The interpreter mode mainly includes 4 roles
Context context environment: used to store the context environment of the interpreter
Expression abstract expression, define an abstract interpretation method interpret
TerminalExpression terminal expression, realize the interpretation operation related to the terminal in the grammar, a Each terminal in a sentence corresponds to an instance of a terminal expression, eg. R = R1 + R2, R1, R2 are terminal symbols, and the interpreter that parses R1, R2 is a terminal expression NonterminalExpression
Nonterminal expression formula, which implements the interpretation operations related to non-terminal symbols in the grammar, and each rule corresponds to a non-terminal symbol expression. eg. "+" in R = R1 + R2 is a non-terminal

code example

We use the interpreter pattern to implement a math expression calculator. Common mathematical operations include addition, subtraction, multiplication, and division, so you need to implement four non-terminal expressions, plus a digital terminal expression, a total of 5 expressions

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

    }

}

insert image description here

Summarize

Advantages
Strong extensibility, you can easily extend the grammar

shortcoming

  1. There are few actual usage scenarios
  2. When the grammar rules are complex, it will cause class expansion and increase the difficulty of system maintenance

Guess you like

Origin blog.csdn.net/qq_35448165/article/details/129466625