设计模式之命令模式

命令模式(Command Pattern)

1. 定义

官方定义:命令模式:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

核心在于:
将不同请求封装成不同的命令对象,不同的命令对象包含着处理对应请求的接收者对象;
增加一个调用者角色Invoker,将命令对象传递给调用者对象,由调用者对象去通知接收者处理请求;
命令模式是一种对象行为型模式

2. 组成部分

由上分析,命令模式有三个重要组成部分:

  1. Command:请求封装成的对象。也就是说将请求方法封装成一个命令对象,通过操作命令对象来操作请求方法。
  2. Receiver:命令接收对象。Receiver对象的主要作用就是受到命令后执行对应的操作。
  3. Invoker:命令的使用者就是Invoker对象。Invoker对象负责要求命令对象执行请求,通常会持有很多命令对象。

3. UML类图

命令模式结构

Command:用来声明执行操作的接口;
ConcreteCommand: 具体命令类;
Invoker: 调用者;
Receiver: 接收者;
Client: 客户类;

4. 案例讲解

假设有一个餐厅,有一个厨师,他会做两道菜:酸菜鱼和辣子鸡。当客户点餐,餐厅服务员会记录客户要求的辣度、份量等信息。然后,服务员会将订单交给后厨,厨师开始做饭。

在这个例子中,有三个主要人物:客户、服务员、厨师。如果使用命令模式,则可以理解为:
客户为请求的发送者,将用户的不同请求封装成不同的命令对象;
厨师为命令的接受者,他要根据客户的喜好准备餐点;
服务员为命令的使用者。

我们先设计一个命令接口,为声明执行操作的接口,execute()方法的参数为客户的餐品要求(辣度、份量等)。

public interface Command {
    Object execute(String[] args);
}

再声明一个接收者的接口,以及它的抽象实现类;
抽象类里action()方法可定义接受者的通用操作,具有特色的功能由子类实现;这里就不对抽象类做实现。

public interface Receiver {
    Object action(String[] args);
}
public abstract class AbstractReceiver implements Receiver {
    @Override
    public Object action(String[] args) {
        return null;
    }
}

然后设计一个命令的抽象类,用于获取命令的实际接收者,并调用接受者(厨师)的action()方法。

// 抽象命令类
public abstract class AbstractCommand implements Command {

    private AbstractReceiver receiver;

    public AbstractCommand(AbstractReceiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public Object execute(String[] args) {
        receiver.action(args);
        return null;
    }
}

接下来是谁接收命令呢,当然是厨师,因此我们设计一个命令的接收类Chef,重写抽象父类的方法。

public class Chef extends AbstractReceiver {
    @Override
    public Object action(String[] args) {
        System.out.println("\t订单信息:" + Arrays.toString(args));
        return null;
    }
}

现在厨师有了,我们将客户的请求封装成命令对象,即设计两个菜品命令类;
有多少个菜品就设计多少个ConcreteCommand,这里就只写以下两个。

// 辣子鸡命令类对象
public class ChickenCommand extends AbstractCommand {
    public ChickenCommand(Chef chef) {
        super(chef);
    }

    @Override
    public Object execute(String[] args) {
        System.out.print("客户点了:辣子鸡");
        return super.execute(args);
    }
}
// 酸菜鱼命令类对象
public class FishCommand extends AbstractCommand {
    public FishCommand(Chef chef) {
        super(chef);
    }

    @Override
    public Object execute(String[] args) {
        System.out.print("客户点了:酸菜鱼");
        return super.execute(args);
    }
}

到此为止,我们的命令类和接收类都设计好了。
之后就是Invoker,即命令的使用者。因为不同的命令对象ConcreteCommand拥有共同的抽象父类,因此我们很容易的将这些命令放入一个数据结构(如数组)中记录起来。这里就体现出了命令模式的请求参数化,以达到命令的撤销恢复

public class CommandInvoker {
    List<History> historyCommand = new ArrayList<>();

    public void invoke(Command command, String[] args) {
        historyCommand.add(new History(command, args)); // 记录历史命令信息
        command.execute(args);
    }
	
	// 客户不满意,上一份订单重做一次
    public void back() {
        if (historyCommand.isEmpty()) {
            return;
        }
        int size = historyCommand.size();
        int preIndex = size == 1 ? 0 : size - 2;
        History preHistory = historyCommand.get(preIndex);
        invoke(preHistory.getCommand(), preHistory.getArgs());
    }
}
// 记录订单的信息,包括菜品命令类与客户的其他要求
public class History {
    private Command command;
    private String[] args;

    public History(Command command, String[] args) {
        this.command = command;
        this.args = args;
    }

    public void setArgs(String[] args) {
        this.args = args;
    }

    public String[] getArgs() {
        return args;
    }

    public void setCommand(Command command) {
        this.command = command;
    }

    public Command getCommand() {
        return command;
    }
}

剩下就是Client,负责创建ConcreteCommand,并设置接收者Receiver:

public class Client {

    public static void main(String[] args) {
        CommandInvoker commandInvoker = new CommandInvoker();
        Chef chef = new Chef();
        commandInvoker.invoke(new ChickenCommand(chef), new String[]{"小红", "小份", "微辣"});
        commandInvoker.invoke(new ChickenCommand(chef), new String[]{"小蓝", "大份", "中辣"});
        commandInvoker.invoke(new FishCommand(chef), new String[]{"小黄", "中份", "不辣"});
        commandInvoker.back(); // 上份订单重做一次
    }
}

运行结果:
运行结果
通过命令模式完成上述案例,实现了客户和厨师之间彻底解耦。
并且程序扩展性良好,便于之后增加厨师或者菜品。

5. 优点

  1. 命令模式可以让发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
  2. 易于扩展,新的命令可以很容易地加入到系统中。
  3. 可以容易地设计一个命令队列和宏命令(宏命令,就是包含多个命令的命令,即一个组合命令)。
  4. 可以方便地实现对请求的撤销(Undo)和恢复(Redo)。

6. 适用场景

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  2. 系统需要在不同的时间指定请求、将请求排队和执行请求。
  3. 系统需要支持命令的Undo操作和恢复Redo操作。
  4. 系统需要将一组操作组合在一起,即支持宏命令。

7. 总结

命令模式包含四个重要角色:

  1. Command中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作;
  2. 具体命令类:实现了在Command中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;
  3. 调用者:即请求的发送者,它通过命令对象来执行请求;
  4. 接收者:执行与请求相关的操作,它具体实现对请求的业务处理。

命令模式的缺点在于:由于每一个命令都需要设计一个具体命令类,类数量随命令数量增长而增长。可能造成类数量过多。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值