命令模式(Command Pattern)——将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持科撤销的操作。
命令模式适用于“请求-响应”模式的功能,将用户的请求封装成对象(命令),用户需要执行什么样的操作,就调用什么样的命令,而无需知道命令的执行逻辑是什么。
命令模式主要包含以下几个概念:
1、Command:所有命令的抽象类,一般需要对外公开一个执行命令的方法execute,如有需要还需提供一个命令的撤销方法undo。
2、ConcreteCommand:命令的实现类。
3、Invoker:调用者,负责命令的调度。
4、Reveiver:接收者,负责命令的接收和执行。
5、Client:客户端,命令的发起者。
比如,一个有存取款功能的ATM机,它可以向某个银行的卡里存款,也可以从任何支持银联接口的银行卡里取款。不同的银行系统对存款、取款功能的实现均有不同,我们不关心银行怎么实现,只要点击ATM上的按钮就行了。这个例子中,银行的存款和取款分别为两个具体的命令实现类(ConcreteCommand);ATM机充当调用者(Invoker),负责调用银行存款或取款的命令;银行的系统为接收者(Reveiver),处理存款和取款的业务逻辑;使用ATM机的人就是客户端。
首先定义一个命令的抽象类,只有两个方法,执行和撤销:
public interface Command {
public void execute();
public void undo();
}
模拟两个银行的系统,建行(CCB)和招行(CMB),简单一点,只有存款和取款的功能。为了模拟银行系统实现的差异,将存款、取款方法取成不同的名字。
建行:
public class Ccb {
public void cunqian(long amount) {
System.out.println("向建设银行存入金额:" + amount);
}
public void quqian(long amount) {
System.out.println("从建设银行取出金额:" + amount);
}
}
招行:
public class Cmb{
public void saveMoney(long amount) {
System.out.println("向招商银行存入金额:" + amount);
}
public void getMoney(long amount) {
System.out.println("从招商银行取出金额:" + amount);
}
}
将银行的存款、取款动作封装成命令对象。为了避免太复杂,存款命令的撤销就当再取出来,取款命令的撤销就再存回去。
建行的存款命令:
public class CcbDepositCommand implements Command {
private Ccb ccb = new Ccb();
@Override
public void execute() {
ccb.cunqian(100);
}
@Override
public void undo() {
ccb.quqian(100);
}
}
建行的取款命令:
public class CcbWithdrawCommand implements Command {
private Ccb ccb = new Ccb();
@Override
public void execute() {
ccb.quqian(100);
}
@Override
public void undo() {
ccb.cunqian(100);
}
}
招行的存款命令:
public class CmbDepositCommand implements Command {
private Cmb cmb = new Cmb();
@Override
public void execute() {
cmb.saveMoney(100);
}
@Override
public void undo() {
cmb.getMoney(100);
}
}
招行的取款命令:
public class CmbWithdrawCommand implements Command {
private Cmb cmb = new Cmb();
@Override
public void execute() {
cmb.getMoney(100);
}
@Override
public void undo() {
cmb.saveMoney(100);
}
}
ATM机刚出厂时可能还没设置任何命令,所以为每个按钮预置一个什么都不做的命令:
public class NoCommand implements Command {
@Override
public void execute() { }
@Override
public void undo() { }
}
现在来实现一个刚出厂的ATM机,它可能完成任何银行的存取款命令,这些命令由负责购买ATM机的银行日后自行规划:
public class Atm {
private Command[] command;
public Atm(){
this.command = new Command[]{new NoCommand()};
}
//设置一组要执行的命令
public void setCommand(Command command[]) {
this.command = command;
}
//执行命令的方法
public void action(int i) {
this.command[i].execute();
}
//撤销命令的方法
public void cancel(int i) {
this.command[i].undo();
}
}
现在假设要实现一个建行的ATM机,只有三个功能:1是向建行存款;2是从建行取款;3从招行取款。只要为ATM设置上相应的命令就可以了。测试类:
public class Test {
/**
* 命令模式——将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
* 命令模式也支持科撤销的操作。
*
* @param args
*/
public static void main(String[] args) {
//调用者来执行命令
Atm atm = new Atm();
Command[] commands = new Command[3];
commands[0] = new CcbDepositCommand();
commands[1] = new CcbWithdrawCommand();
commands[2] = new CmbWithdrawCommand();
atm.setCommand(commands);
atm.action(0);
atm.cancel(0);
atm.action(1);
atm.cancel(1);
atm.action(2);
atm.cancel(2);
}
}
当然,如果我们不适用ATM机,直接到银行的窗口,营业员也可以直接调用系统相应的命令:
//直接执行具体命令
Command command = new CcbDepositCommand();
command.execute();
command.undo();
命令模式的扩展性、封装性很好,可以很好的将用户请求与请求的实现解耦,对需求的变化也更容易扩展。但一个很简单的请求都需要封装为一个命令,也会导致类的膨胀,因此开发时需根据实际需要判断是否使用命令模式。