源码参考:https://gitee.com/constfafa/designpattern_demo/tree/master/src/main/java/com/hfi/command/demo03
1. Invoker类 Waiter
//服务员
class Waiter {
Logger logger = LoggerFactory.getLogger(Waiter.class);
private List<Command> orders = new ArrayList<>();
//设置订单
public void SetOrder(Command command) {
if (StringUtils.equals(command.toString(), "烤鸡翅")) {
logger.info("服务员:鸡翅没有了,请点别的烧烤。");
} else {
orders.add(command);
logger.info("增加订单:" + command.toString() + " 时间:" + new Date().toString());
}
}
public void CancelOrder(Command command) {
if (command.CancelCommand().isSuccess()) {
orders.remove(command);
}
logger.info(command.CancelCommand().getMsg() + " 时间:" + new Date().toString());
}
//通知执行
public void Notify() {
for (Command cmd : orders
) {
cmd.ExcuteCommand();
}
}
}
2. 抽象命令类 Command
是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令
//抽象命令
abstract class Command {
protected Barbecuer receiver;
public Command() {
}
public Command(Barbecuer receiver) {
this.receiver = receiver;
}
//执行命令
abstract public void ExcuteCommand();
abstract public CancelResult CancelCommand();
}
3. 具体命令类BakeChickenWingCommand,BakeMuttonCommand CancelResult类
class CancelResult {
private boolean isSuccess;
private String msg;
public CancelResult(boolean isSuccess, String msg) {
this.isSuccess = isSuccess;
this.msg = msg;
}
public boolean isSuccess() {
return isSuccess;
}
public String getMsg() {
return msg;
}
}
//烤羊肉串命令
class BakeMuttonCommand extends Command {
public BakeMuttonCommand() {
}
public BakeMuttonCommand(Barbecuer receiver) {
super(receiver);
}
@Override
public void ExcuteCommand() {
receiver.BakeMutton();
}
@Override
public CancelResult CancelCommand() {
return new CancelResult(false, "已经在烤了,不能取消烤羊肉串");
}
@Override
public String toString() {
return "烤羊肉串";
}
}
//烤鸡翅命令
class BakeChickenWingCommand extends Command {
public BakeChickenWingCommand() {
}
public BakeChickenWingCommand(Barbecuer receiver) {
super(receiver);
}
@Override
public void ExcuteCommand() {
receiver.BakeChickenWing();
}
@Override
public CancelResult CancelCommand() {
return new CancelResult(true, "取消烤鸡翅");
}
@Override
public String toString() {
return "烤鸡翅";
}
}
4. Receiver类 Barbecuer
class Barbecuer {
Logger logger = LoggerFactory.getLogger(Barbecuer.class);
public void BakeMutton() {
logger.info("烤羊肉串!");
}
public void BakeChickenWing() {
logger.info("烤鸡翅!");
}
}
5. Test类(Client)
public class Test {
@org.junit.Test
public void test() {
//开店前的准备
//首先调用Receiver
Barbecuer boy = new Barbecuer();
Command bakeMuttonCommand1 = new BakeMuttonCommand(boy);
Command bakeMuttonCommand2 = new BakeMuttonCommand(boy);
Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy);
Waiter girl = new Waiter();
//开门营业 顾客点菜
girl.SetOrder(bakeMuttonCommand1);
girl.SetOrder(bakeMuttonCommand2);
girl.SetOrder(bakeChickenWingCommand1);
//点菜完闭,通知厨房
girl.Notify();
girl.CancelOrder(bakeMuttonCommand1);
}
}
6. 类图
7. 执行结果
8. 总结
以上的例子主要实现的功能就是动作请求者与动作执行者进行了解耦。我们可以看到,当我们调用时,执行的时序首先是Invoker类,然后是Concrete Command类,最后是Receiver类。也就是说一条命令的执行被分成了三步,它的耦合度要比把所有的操作都封装到一个类中要低的多,而这也正是命令模式的精髓所在:把命令的调用者与执行者分开,使双方不必关心对方是如何操作的。
8.1 命令模式的优缺点
1. 命令模式的封装性很好:每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。比如有一组文件操作的命令:新建文件、复制文件、删除文件。如果把这三个操作都封装成一个命令类,客户端只需要知道有这三个命令类即可,至于命令类中封装好的逻辑,客户端则无需知道。
2. 命令模式的扩展性很好,在命令模式中,在接收者类中一般会对操作进行最基本的封装,命令类则通过对这些基本的操作进行二次封装,当增加新命令的时候,对命令类的编写一般不是从零开始的,有大量的接收者类可供调用,也有大量的命令类可供调用,代码的复用性很好。比如,文件的操作中,我们需要增加一个剪切文件的命令,则只需要把复制文件和删除文件这两个命令组合一下就行了,非常方便。
3. 允许接收请求的一方决定是否要否决请求。
4. 可以容易地实现对请求的撤销和重做。
5. 最关键的优点是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
最后说一下命令模式的缺点,那就是命令如果很多,开发起来就要头疼了。特别是很多简单的命令,实现起来就几行代码的事,而使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装。
8.2 命令模式的适用场景
对于大多数请求-响应模式的功能,比较适合使用命令模式,正如命令模式定义说的那样,命令模式对实现记录日志、撤销操作等功能比较方便。
对于一个场合到底用不用模式,这对所有的开发人员来说都是一个很纠结的问题。有时候,因为预见到需求上会发生的某些变化,为了系统的灵活性和可扩展性而使用了某种设计模式,但这个预见的需求偏偏没有,相反,没预见到的需求倒是来了不少,导致在修改代码的时候,使用的设计模式反而起了相反的作用,以至于整个项目组怨声载道。这样的例子,我相信每个程序设计者都遇到过。所以,基于敏捷开发的原则,我们在设计程序的时候,如果按照目前的需求,不使用某种模式也能很好地解决,那么我们就不要引入它,因为要引入一种设计模式并不困难,我们大可以在真正需要用到的时候再对系统进行一下,引入这个设计模式。
拿命令模式来说吧,我们开发中,请求-响应模式的功能非常常见,一般来说,我们会把对请求的响应操作封装到一个方法中,这个封装的方法可以称之为命令,但不是命令模式。到底要不要把这种设计上升到模式的高度就要另行考虑了,因为,如果使用命令模式,就要引入调用者、接收者两个角色,原本放在一处的逻辑分散到了三个类中,设计时,必须考虑这样的代价是否值得。
9. 实际中命令模式的使用
比如我们自己创建Thread对象来执行Runnable,就是没有使用命令模式
在java多线程中,可以使用Executor来代替显式地创建Thread对象,这就是使用了命令模式
LiftOff对象知道如何运行具体的任务,与命令设计模式一样,它暴露了要执行的单一方法。
ExecutorService 知道如何构建恰当的上下文来执行Runnable对象
ExecutorService调用execute方法,发出执行的命令,比如说执行LifeoffRunnable
由Worker thread来实际进行执行