命令模式二

2022-10-04 08:36:23 浏览数 (2)

与中介者的关系

就比如中介者模式那里,BangZi 在交保护费的时候,还看 GuiZi 交没交,交多少,底层横向发生联系。

代码语言:javascript复制
public BangZiPayCommand extends Command {
    public BangZiPayCommand(int money) {
        super(money);
    }
    public void execute() {
        // 比如 GuiZi 交多少有个方法返回
        if (gz.getPayMoney() < 4) {
            gz.pay(money / 2)
        } else {
            gz.pay(money);
        }
        gz.bite(false);
        gz.build(false);
    }
}

那这我怎么又感觉回到中介者了呢,这两个模式分界有点越来越模糊的感觉。感觉命令就是中介者的再一次封装,原来的中介类会变得臃肿,命令将中介者内部的 if/else 抽象出一种 Command,将具体逻辑挪到 Command 里。等于说将 Mediator 一个大的类拆分成许多个 Command,就是单一职责,原来的中介者职责太多,现在一个职责抽取一个 Command。

上一节中介者模式例子是这么写的

代码语言:javascript复制
public abstract class AbstractMediator { 
    protected DengTa dt; 
    protected GuiZi gz;  
    protected BangZi bz;

    // setter/getter

    //中介者最重要的方法叫做事件方法,处理多个对象之间的关系 
    // str 是具体指令,objects 是可能调用方法需要的参数 
    public abstract void execute(String str,Object...objects);
}

public class Mediator extends AbstractMediator {
    public void execute(String str,Object...objects){ 
        if(str.equals("dengTa.shouBHF")){ //灯塔收保护费
            this.dengTaShouBHF(); 
        }else if(str.equals("guiZi.jiaoBHF")){ //鬼子交保护费
            this.guiZiJiaoBHF(); 
        }else if(str.equals("bangZi.jiaoBHF")){ //棒子交保护费
            this.bangZiJiaoBHF();  
        }
    }

    private void dengTaShouBHF() {
        gz.jiaoBHF(4); // 鬼子上交 4 千亿
        bz.jiaoBHF(1); // 棒子上交 1 千亿
    }


    private int guiZiJiaoBHF() {
        int bhf = number;
        if (bz.maGuiZi()) { // 棒子骂我
            if (dt.shouShi("BangZi")) { // 爸爸帮我收拾它一顿
                bhf = number * 2; // 心情好,交两倍
            } else { // 爸爸没有帮我收拾
                bhf = number / 2; // 心情不好,少交一半
            }
        }
        bhf = number;
        gz.setBHF(bhf);
        System.out.println("给爸爸交保护费:"   bhf);
    }

    private int bangZiJiaoBHF() {
        int gzBHF = gz.getBFH();
        if (gzBHF == 0) {
            System.out.println("他还没交,主人先从他那收到钱,我再给");
        } else if (gzBHF < 4) {
            System.out.println("他才交了这么一点,那我只能交保护费 "   number/2);
        } else {
            System.out.println("给主人交保护费:"   number);
        }
    }

}

改造一下试试,将 Mediator 抽取出来的单一职责的逻辑变成 Command,然后自己变成 Invoker。

代码语言:javascript复制
// 原来的 AbstractMediator
public abstract class Command { 
    // 接收者 Receiver
    protected DengTa dt; 
    protected GuiZi gz;  
    protected BangZi bz;

    // setter/getter

    // 不需要指令参数 str 了,Command 类型本身就代表了什么指令
    // 我上面例子参数是通过 Command 的构造方法传进去的,这里通过
    // 执行方法参数,区别不大
    public abstract void execute(Object...objects);
}

// dengTa.shouBHF 变成一个具体 Command
public class DengTaShouBHFCommand extend Command {
    public void execute(Object...objects) {
        this.dengTaShouBHF(); 
    }
    private void dengTaShouBHF() {
        gz.jiaoBHF(4); // 鬼子上交 4 千亿
        bz.jiaoBHF(1); // 棒子上交 1 千亿
    }
}

// guiZi.jiaoBHF 变成一个具体 Command
public class GuiZiJiaoBHFCommand extend Command {
    public void execute(Object...objects) {
        this.guiZiJiaoBHF();
    }
    private int guiZiJiaoBHF() {
        int bhf = number;
        if (bz.maGuiZi()) { // 棒子骂我
            if (dt.shouShi("BangZi")) { // 爸爸帮我收拾它一顿
                bhf = number * 2; // 心情好,交两倍
            } else { // 爸爸没有帮我收拾
                bhf = number / 2; // 心情不好,少交一半
            }
        }
        bhf = number;
        gz.setBHF(bhf);
        System.out.println("给爸爸交保护费:"   bhf);
    }
}

// bangZi.jiaoBHF 同样是一个 Command

// 就是原来的 Mediator
public class InVoker {
    private Command cmd; 
    // 通过 setter 方法
    public void setCommand(Command cmd) {
        this.cmd = cmd;
    }

    // 这方法就是原来的 execute,为了和 Command 的 execute 区分
    // 写成 action,其实写成 execute 也行,
    // str 参数不需要了,因为 setCommand 取代了这个功能
    public void action(Object...objects){ 
        // 原来 if/else 判断不需要了,因为逻辑分散到具体 Command 了
        cmd.execute();
    }
}

我去,真的感觉两者本质是一样的了。中介者是一层封装,命令是在中介者基础上再一次封装。且没什么高层低层的区别,都是一样的,即便低层跪着给高层请安,那也可以是一个 Command。

撤销

命令本身有撤销的逻辑。

代码语言:javascript复制
public abstract class Command {  
    // 其它语句
    public abstract void execute();
    // 撤销方法
    public abstract void undo();
}

public GuiZiPayCommand extends Command {
    public GuiZiPayCommand(int money) {
        super(money);
    }
    public void execute() {
        gz.pay(money);
        gz.bite(false);
        gz.build(false);
    }
    public void undo() {
        // 内部做撤销逻辑,比如原来是 build(true),bite(true) 的
        gz.build(true);
        gz.bite(true);
        gz.back(money); // 比如还有个把钱收回来的方法
        
    }
}

然后中间的发布者要内部记录下执行的命令,这样方便找到并撤销。

代码语言:javascript复制
public class Invoker {
    private List<Command> cmdList; // 记录命令列表
    public void setCommand(Command command) {
        cmdList.add(command);
    }
    public void action() {
        cmdList.get(command.size()-1).execute();
    }
    public void unAction() { // 删掉最后一个命令,并撤销
        cmdList.remove(command.size()-1).undo();
    }
}
代码语言:javascript复制
public class FatherDengTa() {
    private Invoker invoker = new Invoker(); // 中介
    
    public void shouGuiZiBHF() {
        Command cmd = new GuiZiPayCommand(1);
        invoker.setCommand(cmd);
        invoker.action();
        // GuiZi 欢欣鼓舞,一分钟过去了
        // 说弄错了,少说一个 0,先撤销
        invoker.unAction(); 
    }
}

通过状态值撤销,适合命令本身也就是设置一些不同的状态,然后执行命令时记录下当前状态,撤销就是根据记下来的状态再回滚回去。

宏编程

就是一个命令封装若干个其它命令,然后调用它一次性执行多个。

代码语言:javascript复制
public SonPayCommand extends Command {
    private Command[] cmds;
    // 由其它命令数组构造
    public SonPayCommand(Command[] cmds) {
        this.cmds = cmds;
    }
    public void execute() {
        for (int i=0; i<cmds.length; i  ) {
            cmds[i].execute();
        }
    }
}

public class FatherDengTa() {
    private Invoker invoker = new Invoker(); // 中介

    // 爸爸不需要发一个命令给儿子,再发一个命令给另一个儿子
    // 爸爸把命令直接打包好,一起发出去
    public void shouSonBHF() {
        Command cmd = new GuiZiPayCommand(4);
        Command cmd2 = new BangZiPayCommand(1);
        Command[] cmds = { cmd, cmd2 };
        SonPayCommand sonCmd = new SonPayCommand(cmds);
        invoker.setCommand(sonCmd);
        invoker.action();
    }
}

其它应用

  1. 队列请求:由于 Command 内部已经封装了接收者和具体执行的动作,所以可以用命令封装后放入一个队列,其它线程读取队列,取出 Command,调用它的 execute,接收者比如 GuiZi 就自己把事情办了。
  2. 日志请求:还是由于 Command 封装了接收者和动作的特性,一些操作过程中,每次都记录一个 Command 当做日志,如果系统死机了,就可以从日志中取出命令,再一次一次的执行,就能够恢复到死机前的状态。如果倒过来,一个一个撤销,就可以恢复到之前的某种状态。

0 人点赞