设计模式18-命令模式

概念


定义

将一个请求封装为一个对象,从而使你可用不同请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。本质“封装请求”

结构
这里写图片描述

该模式有五个角色。“命令接口”,“命令接口实现类”,“接收者”,“要求执行者”,“装配者”

Command:定义命令接口
ConcreteCommand:命令接口实现对象,持有接收者,调用接收者的功能来完成命令操作
Receiver:接收者,真正执行命令的对象
Invoker:要求命令对象执行请求,通常持有命令对象,是装配者触发命令并执行操作的入口
Client:装配者,创建具体的命令对象,设置命令对象的接收者,用于组装命令对象和接收者

关键说明

关键在于,将请求封装成命令对象,并定义统一执行操作接口。但是传统的命令模式,真正执行功能的是接收者,其他相关都是转调方法。装配者的工作是组装,让命令模式可以成功搭建

小示例

命令接口

/**
 * 命令接口,声明执行的操作
 */
public interface Command {
    /**
     * 执行命令对应的操作
     */
    public void execute();
}

调用者

/**
 * 调用者
 */
public class Invoker {
    /**
     * 持有命令对象
     */
    private Command command = null;
    /**
     * 设置调用者持有的命令对象
     * @param command 命令对象
     */
    public void setCommand(Command command) {
        this.command = command;
    }
    /**
     * 示意方法,要求命令执行请求
     */
    public void runCommand() {
        //调用命令对象的执行方法
        command.execute();
    }
}

命令接口实现对象

/**
 * 具体的命令实现对象
 */
public class ConcreteCommand implements Command {
    /**
     * 持有相应的接受者对象
     */
    private Receiver receiver = null;
    /**
     * 示意,命令对象可以有自己的状态
     */
    private String state;
    /**
     * 构造方法,传入相应的接受者对象
     * @param receiver 相应的接受者对象
     */
    public ConcreteCommand(Receiver receiver){
        this.receiver = receiver;
    }

    public void execute() {
        //通常会转调接受者对象的相应方法,让接受者来真正执行功能
        receiver.action();
    }

}

接收者,真正执行对象

/**
 * 接收者对象
 */
public class Receiver {
    /**
     * 示意方法,真正执行命令相应的操作
     */
    public void action(){
        //真正执行命令操作的功能代码
    }
}

装配者

public class Client {
    /**
     * 示意,负责创建命令对象,并设定它的接受者
     */
    public void assemble(){
        //创建接受者
        Receiver receiver = new Receiver();
        //创建命令对象,设定它的接收者
        Command command = new ConcreteCommand(receiver);
        //创建Invoker,把命令对象设置进去
        Invoker invoker = new Invoker();
        invoker.setCommand(command);
    }
}

实例

这里写图片描述

BOX是调用者,持有命令对象,转调命令对象的执行方法

OpenCommand是命令接口实例对象,持有接受者,转调接收者对象的执行方法

MainBoardApi是接收者父类,其子类真正实现功能

Client进行命令装配等等工作


调用者,命令程序入口

/**
 * 机箱对象,本身有按钮,持有按钮对应的命令对象
 */
public class Box {
    /**
     * 开机命令对象
     */
    private Command openCommand;
    /**
     * 设置开机命令对象
     * @param command 开机命令对象
     */
    public void setOpenCommand(Command command){
        this.openCommand = command;
    }
    /**
     * 提供给客户使用,接受并相应用户请求,相当于按钮被按下触发的方法
     */
    public void openButtonPressed(){
        //按下按钮,执行命令
        openCommand.execute();
    }
}

命令接口

/**
 * 命令接口,声明执行的操作
 */
public interface Command {
    /**
     * 执行命令对应的操作
     */
    public void execute();
}

开机命令实例

/**
 * 开机命令的实现,实现Command接口,
 * 持有开机命令的真正实现,通过调用接收者的方法来实现命令
 */
public class OpenCommand implements Command{
    /**
     * 持有真正实现命令的接收者——主板对象
     */
    private MainBoardApi mainBoard = null;
    /**
     * 构造方法,传入主板对象
     * @param mainBoard 主板对象
     */
    public OpenCommand(MainBoardApi mainBoard) {
        this.mainBoard = mainBoard;
    }

    public void execute() {
        //对于命令对象,根本不知道如何开机,会转调主板对象
        //让主板去完成开机的功能
        this.mainBoard.open();
    }
}

接收者父类

/**
 * 主板的接口
 */
public interface MainBoardApi {
    /**
     * 主板具有能开机的功能
     */
    public void open();
}
/**
 * 微星主板类,开机命令的真正实现者,在Command模式中充当Receiver
 */
public class MsiMainBoard implements MainBoardApi{
    /**
     * 真正的开机命令的实现
     */
    public void open(){
        System.out.println("微星主板现在正在开机,请等候");
        System.out.println("接通电源......");
        System.out.println("设备检查......");
        System.out.println("装载系统......");
        System.out.println("机器正常运转起来......");
        System.out.println("机器已经正常打开,请操作");
    }
}
/**
 * 技嘉主板类,开机命令的真正实现者,在Command模式中充当Receiver
 */
public class GigaMainBoard implements MainBoardApi{
    /**
     * 真正的开机命令的实现
     */
    public void open(){
        System.out.println("技嘉主板现在正在开机,请等候");
        System.out.println("接通电源......");
        System.out.println("设备检查......");
        System.out.println("装载系统......");
        System.out.println("机器正常运转起来......");
        System.out.println("机器已经正常打开,请操作");
    }
}

装配者

public class Client {
    public static void main(String[] args) {
        //1:把命令和真正的实现组合起来,相当于在组装机器,
        //把机箱上按钮的连接线插接到主板上。
        MainBoardApi mainBoard = new MsiMainBoard();
        OpenCommand openCommand = new OpenCommand(mainBoard);


        //真正的客户端测试

        //2:为机箱上的按钮设置对应的命令,让按钮知道该干什么
        Box box = new Box();
        box.setOpenCommand(openCommand);

        //3:然后模拟按下机箱上的按钮
        box.openButtonPressed();
    }
}

可撤销命令实例

可撤销操作的意思就是:放弃该操作,回到未执行该操作前的状态
可撤销模式分为补偿式,存储恢复式
补偿式:又叫反操作式,如果被撤销的操作是加的功能,那么撤销的实现就是减的功能
存储恢复式:把操作前的状态记录下来,然后要撤退操作时直接恢复回去。

补偿式:

一个加减的计算器,由撤销到上一层,也有撤销恢复的功能

调用者,程序入口

/**
 * 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮
 */
public class Calculator {
    /**
     * 命令的操作的历史记录,在撤销时候用
     */
    private List<Command> undoCmds = new ArrayList<Command>();
    /**
     * 命令被撤销的历史记录,在恢复时候用
     */
    private List<Command> redoCmds = new ArrayList<Command>();

    private Command addCmd = null;
    private Command substractCmd = null;
    public void setAddCmd(Command addCmd) {
        this.addCmd = addCmd;
    }
    public void setSubstractCmd(Command substractCmd) {
        this.substractCmd = substractCmd;
    }   
    public void addPressed(){
        this.addCmd.execute();
        //把操作记录到历史记录里面
        undoCmds.add(this.addCmd);
    }
    public void substractPressed(){
        this.substractCmd.execute();
        //把操作记录到历史记录里面
        undoCmds.add(this.substractCmd);
    }
    public void undoPressed(){
        if(this.undoCmds.size()>0){
            //取出最后一个命令来撤销
            Command cmd = this.undoCmds.get(this.undoCmds.size()-1);
            cmd.undo();
            //如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面
            this.redoCmds.add(cmd );
            //然后把最后一个命令删除掉,
            this.undoCmds.remove(cmd);
        }else{
            System.out.println("很抱歉,没有可撤销的命令");
        }
    }
    public void redoPressed(){
        if(this.redoCmds.size()>0){
            //取出最后一个命令来重做
            Command cmd = this.redoCmds.get(this.redoCmds.size()-1);
            cmd.execute();      
            //把这个命令记录到可撤销的历史记录里面
            this.undoCmds.add(cmd);
            //然后把最后一个命令删除掉
            this.redoCmds.remove(cmd);
        }else{
            System.out.println("很抱歉,没有可恢复的命令");
        }
    }
}

命令接口

/**
 * 命令接口,声明执行的操作,支持可撤销操作
 */
public interface Command {
    /**
     * 执行命令对应的操作
     */
    public void execute();
    /**
     * 执行撤销命令对应的操作
     */
    public void undo();
}
/**
 * 具体的加法命令实现对象
 */
public class AddCommand implements Command{
    /**
     * 持有具体执行计算的对象
     */
    private OperationApi operation = null;
    /**
     * 操作的数据,也就是要加上的数据
     */
    private int opeNum;
    /**
     * 构造方法,传入具体执行计算的对象
     * @param operation 具体执行计算的对象
     * @param opeNum 要加上的数据
     */
    public AddCommand(OperationApi operation,int opeNum){
        this.operation = operation;
        this.opeNum = opeNum;
    }

    public void execute() {
        //转调接收者去真正执行功能,这个命令是做加法
        this.operation.add(opeNum);
    }

    public void undo() {
        //转调接收者去真正执行功能
        //命令本身是做加法,那么撤销的时候就是做减法了
        this.operation.substract(opeNum);
    }
}
/**
 * 具体的减法命令实现对象
 */
public class SubstractCommand implements Command{
    /**
     * 持有具体执行计算的对象
     */
    private OperationApi operation = null;
    /**
     * 操作的数据,也就是要减去的数据
     */
    private int opeNum;
    /**
     * 构造方法,传入具体执行计算的对象
     * @param operation 具体执行计算的对象
     * @param opeNum 要减去的数据
     */
    public SubstractCommand(OperationApi operation,int opeNum){
        this.operation = operation;
        this.opeNum = opeNum;
    }   

    public void execute() {
        //转调接收者去真正执行功能,这个命令是做减法
        this.operation.substract(opeNum);
    }

    public void undo() {
        //转调接收者去真正执行功能
        //命令本身是做减法,那么撤销的时候就是做加法了
        this.operation.add(opeNum);
    }
}

接收者

/**
 * 操作运算的接口
 */
public interface OperationApi {
    /**
     * 获取计算完成后的结果
     * @return 计算完成后的结果
     */
    public int getResult();
    /**
     * 设置计算开始的初始值
     * @param result 计算开始的初始值
     */
    public void setResult(int result);
    /**
     * 执行加法
     * @param num 需要加的数
     */
    public void add(int num);
    /**
     * 执行减法
     * @param num 需要减的数
     */
    public void substract(int num);
}
/**
 * 操作运算的接口
 */
public interface OperationApi {
    /**
     * 获取计算完成后的结果
     * @return 计算完成后的结果
     */
    public int getResult();
    /**
     * 设置计算开始的初始值
     * @param result 计算开始的初始值
     */
    public void setResult(int result);
    /**
     * 执行加法
     * @param num 需要加的数
     */
    public void add(int num);
    /**
     * 执行减法
     * @param num 需要减的数
     */
    public void substract(int num);
}

装配者

public class Client {
    public static void main(String[] args) {
        //1:组装命令和接收者
        //创建接收者
        OperationApi operation = new Operation();
        //创建命令对象,并组装命令和接收者
        AddCommand addCmd = new AddCommand(operation,5);
        SubstractCommand substractCmd = new SubstractCommand(operation,3);

        //2:把命令设置到持有者,就是计算器里面
        Calculator calculator = new Calculator();
        calculator.setAddCmd(addCmd);
        calculator.setSubstractCmd(substractCmd);

        //3:模拟按下按钮,测试一下
        calculator.addPressed();
        System.out.println("一次加法运算后的结果为:"+operation.getResult());
        calculator.substractPressed();
        System.out.println("一次减法运算后的结果为:"+operation.getResult());

        //测试撤消
        calculator.undoPressed();
        System.out.println("撤销一次后的结果为:"+operation.getResult());
        calculator.undoPressed();
        System.out.println("再撤销一次后的结果为:"+operation.getResult());

        //测试恢复
        calculator.redoPressed();
        System.out.println("恢复操作一次后的结果为:"+operation.getResult());
        calculator.redoPressed();
        System.out.println("再恢复操作一次后的结果为:"+operation.getResult());
    }
}

宏命令实例

宏命令就是包含多个命令的命令,即一个命令的组合

把一堆命令当做菜单,菜单的每个菜就是一个命令。同时把菜单抽象成一个大命令。当用户点餐完,就触发大命令,执行菜单这个命令即可。

宏命令对象

/**
 * 菜单对象,是个宏命令对象
 */
public class MenuCommand implements Command {
    /**
     * 用来记录组合本菜单的多道菜品,也就是多个命令对象
     */
    private Collection<Command> col = new ArrayList<Command>();
    /**
     * 点菜,把菜品加入到菜单中
     * @param cmd 客户点的菜
     */
    public void addCommand(Command cmd){
        col.add(cmd);
    }

    public void execute() {
        //执行菜单其实就是循环执行菜单里面的每个菜
        for(Command cmd : col){
            cmd.execute();
        }
    }
}

装配者+调用者

/**
 * 服务员,负责组合菜单,负责组装每个菜和具体的实现者,
 * 还负责执行调用,相当于标准Command模式的Client+Invoker
 */
public class Waiter {
    /**
     * 持有一个宏命令对象——菜单
     */
    private MenuCommand menuCommand = new MenuCommand();
    /**
     * 客户点菜
     * @param cmd 客户点的菜,每道菜是一个命令对象
     */
    public void orderDish(Command cmd){
        //客户传过来的命令对象是没有和接收者组装的
        //在这里组装吧
        CookApi hotCook = new HotCook();
        CookApi coolCook = new CoolCook();
        //判读到底是组合凉菜师傅还是热菜师傅
        //简单点根据命令的原始对象的类型来判断
        if(cmd instanceof DuckCommand){
            ((DuckCommand)cmd).setCookApi(hotCook);
        }else if(cmd instanceof ChopCommand){
            ((ChopCommand)cmd).setCookApi(hotCook);
        }else if(cmd instanceof PorkCommand){
            //这是个凉菜,所以要组合凉菜的师傅
            ((PorkCommand)cmd).setCookApi(coolCook);
        }
        //添加到菜单中
        menuCommand.addCommand(cmd);
    }
    /**
     * 客户点菜完毕,表示要执行命令了,这里就是执行菜单这个组合命令
     */
    public void orderOver(){
        this.menuCommand.execute();
    }
}

命令接口

/**
 * 命令接口,声明执行的操作
 */
public interface Command {
    /**
     * 执行命令对应的操作
     */
    public void execute();
}
/**
 * 命令对象,北京烤鸭
 */
public class DuckCommand implements Command{
    private CookApi cookApi = null;
    public void setCookApi(CookApi cookApi) {
        this.cookApi = cookApi;
    }

    public void execute() {
        this.cookApi.cook("北京烤鸭");
    }
}
/**
 * 命令对象,蒜泥白肉
 */
public class PorkCommand implements Command {
    private CookApi cookApi = null;

    public void setCookApi(CookApi cookApi) {
        this.cookApi = cookApi;
    }


    public void execute() {
        this.cookApi.cook("蒜泥白肉");
    }
}
/**
 * 命令对象,绿豆排骨煲
 */
public class ChopCommand implements Command{
    /**
     * 持有具体做菜的厨师的对象
     */
    private CookApi cookApi = null;
    /**
     * 设置具体做菜的厨师的对象
     * @param cookApi 具体做菜的厨师的对象
     */
    public void setCookApi(CookApi cookApi) {
        this.cookApi = cookApi;
    }

    public void execute() {
        this.cookApi.cook("绿豆排骨煲");
    }
}

接收者

/**
 * 厨师的接口
 */
public interface CookApi {
    /**
     * 示意,做菜的方法
     * @param name 菜名
     */
    public void cook(String name);
}
/**
 * 厨师对象,做凉菜
 */
public class CoolCook implements CookApi {
    public void cook(String name) {
        System.out.println("凉菜"+name+"已经做好,本厨师正在装盘。" );
    }
}
/**
 * 厨师对象,做热菜
 */
public class HotCook implements CookApi{

    public void cook(String name) {
        System.out.println("本厨师正在做:"+name);
    }
}

测试类

public class Client {
    public static void main(String[] args) {
        //只是负责向服务员点菜就好了
        //创建服务员
        Waiter waiter = new Waiter();

        //创建命令对象,就是要点的菜
        Command chop = new ChopCommand();
        Command duck = new DuckCommand();
        Command pork = new PorkCommand();

        //点菜,就是把这些菜让服务员记录下来
        waiter.orderDish(chop);
        waiter.orderDish(duck);
        waiter.orderDish(pork);

        //点菜完毕
        waiter.orderOver();
    }
}

命令模式的相关信息

1.存在退化的命令模式,也可以叫超级智能,不需要接收者,也不需要组装者,全部功能由命令接口实例对象来完成。真正的用户,通过invoker来触发命令即可。所以退化的命令模式,只有三个角色,“命令接口”,“命令实例对象”,“调用者invoker”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值