简说设计模式——命令模式

一、什么是命令模式

  在说命令模式前我们先来说一个小例子。很多人都有吃夜市的经历,对于那些推小车的摊位,通常只有老板一个人,既负责制作也负责收钱,我要两串烤串多放辣,旁边的人要了三串烤面筋不要辣,过了一会儿又来人要烤蔬菜……,当人多的时候记忆力不好的老板肯定就不知道谁要的啥、交没交钱了;而去有店铺的烤肉摊,点单的时候会有服务员来记录我们的菜单,然后再去通知烧烤师傅进行烧烤,这样就不会出现混乱了,当然我们也可以随时对菜单进行修改,此时只需服务员记录后去通知烤肉师傅即可,由于有了记录,最终算账还是不会出错的。

  从这里讲,前者其实就是“行为请求者”和“行为实现者”的紧耦合,对于请求排队或记录请求日志,以及支持可撤销的操作来说,紧耦合是不太合适的,而命令模式恰恰解决了这点问题。

  命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。UML结构图如下:

  其中,Invoker是调用者角色,要求该命令执行这个请求;Command是命令角色,需要执行的所有命令都在这里声明,可以是接口或抽象类;Receiver是接收者角色,知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者;ConcreteCommand将一个接收者对象绑定与一个动作,调用接收者相应的操作,以实现Execute。

  1. Command类

  用来声明执行操作的接口/抽象类。

1 public abstract class Command { 2  3     protected Receiver receiver; 4      5     public Command(Receiver receiver) { 6         this.receiver = receiver; 7     } 8      9     //执行命令的方法10     abstract public void execute();11     12 }

  2. ConcreteCommand类

  具体的Command类,用于构造传递接收者,根据环境需求,具体的命令类也可能有n个。

1 public class ConcreteCommand extends Command { 2  3     //构造传递接收者 4     public ConcreteCommand(Receiver receiver) { 5         super(receiver); 6     } 7  8     //必须实现一个命令 9     @Override10     public void execute() {11         receiver.action();12     }13 14 }

  3. Invoker类

  接收命令,并执行命令。

1 public class Invoker { 2  3     private Command command; 4      5     //接受命令 6     public void setCommand(Command command) { 7         this.command = command; 8     } 9     10     //执行命令11     public void executeCommand() {12         command.execute();13     }14     15 }

  4. Receiver类

  该角色就是干活的角色, 命令传递到这里是应该被执行的。

1 public class Receiver {2     3     public void action() {4         System.out.println("执行请求!");5     }6 7 }

  5. Client类

  首先定义一个接收者,然后定义一个命令用于发送给接收者,之后再声明一个调用者,即可把命令交给调用者执行。

1 public class Client { 2  3     public static void main(String[] args) { 4         //定义接收者 5         Receiver receiver = new Receiver(); 6         //定义一个发送给接收者的命令 7         Command command = new ConcreteCommand(receiver); 8         //声明调用者 9         Invoker invoker = new Invoker();10         11         //把命令交给调用者执行12         invoker.setCommand(command);13         invoker.executeCommand();14     }15     16 }

  运行结果如下:

  

二、命令模式的应用

  1. 何时使用

  • 在某些场合,如要对行为进行“记录、撤销/重做、事务”等处理时

  2. 方法

  • 通过调用者调用接收者执行命令,顺序为调用者→接收者→命令

  3. 优点

  • 类间耦合,调用者角色与接收者角色之间没有任何依赖关系

  • 可扩展性

  • 命令模式结合职责链模式可以实现命令族解析任务;结合模板方法模式可以减少Command子类的膨胀问题

  4. 缺点

  • 可能导致某些系统有过多的具体命令类

  5. 使用场景

  • 认为是命令的地方都可以使用

  • 系统需要支持命令的撤销/恢复操作时

  6. 应用实例

  • GUI中每一个按钮都是一条命令

  • 模拟CMD(DOS命令)

  • 订单的撤销/恢复

  • 触发-反馈机制的处理

三、命令模式的实现

  下面以上面提到的烧烤店模型为例,使用命令模式编写代码实现。类图如下:

  1. 调用者角色

  服务员类为调用者角色,在其中定义一个订单列表用于存储客户订单信息,通过setOrder()方法设置订单、cancelOrder()方法取消订单、notifyExecute()方法下单。

1 public class Waiter { 2  3     private List<Command> orders = new LinkedList<>(); 4      5     //设置订单 6     public void setOrder(Command command) throws Exception { 7         //通过反射获得鸡翅的类 8         String s1 = Class.forName("com.adamjwh.gofex.command.BakeChickenWingCommand").toString().substring(6); 9         //获取command订单中的类10         String s2 = command.toString().substring(0, command.toString().indexOf("@"));11         12         //这里模拟鸡翅卖完的情况,当订单中有鸡翅时,撤销订单13         if(s1.equals(s2)) {14             System.out.println("【服务员:鸡翅没有了,请点别的烧烤】");15             cancelOrder(command);//撤销订单16         } else {17             orders.add(command);18             System.out.println("添加订单:" + command.getBarbecuer() + "\t时间:" + new Date().toString());19         }20     }21     22     //取消订单23     public void cancelOrder(Command command) {24         orders.remove(command);25         System.out.println("取消订单:" + command.getBarbecuer() + "\t时间:" + new Date().toString());26     }27     28     //通知全部执行29     public void notifyExecute() {30         System.out.println("-----------------------订单-----------------------");31         for(Command command : orders) {32             command.excuteCommand();33         }34     }35 }

  2. 命令角色

1 public abstract class Command { 2  3     protected Barbecuer receiver; 4      5     public Command(Barbecuer receiver) { 6         this.receiver = receiver; 7     } 8      9     //执行命令10     abstract public void excuteCommand();11     12     //获取名称13     abstract public String getBarbecuer();14     15 }

  3. 接收者角色

  这里的接收者角色就是烧烤师傅,提供“烤羊肉串”和“烤鸡翅”的操作。

1 public class Barbecuer { 2      3     //烤羊肉 4     public void bakeMutton() { 5         System.out.println("烤羊肉串"); 6     } 7      8     //烤鸡翅 9     public void bakeChickenWing() {10         System.out.println("烤鸡翅");11     }12     13 }

  4. 具体命令

  这里以烤羊肉串类为例,提供了执行命令的方法。烤鸡翅类同理,此处不再赘述。

1 public class BakeMuttonCommand extends Command { 2  3     private String barbecuer; 4      5     public BakeMuttonCommand(Barbecuer receiver) { 6         super(receiver); 7         barbecuer = "烤羊肉串"; 8     } 9 10     @Override11     public void excuteCommand() {12         receiver.bakeMutton();13     }14     15     //获取名称16     public String getBarbecuer() {17         return barbecuer;18     }19     20 }

  5. Client客户端

  开店前准备即初始化烤肉师傅、服务员及命令类,顾客点菜后将菜单信息存入服务员的订单上,假设鸡翅卖完了(参考Waiter类),则将鸡翅项从订单上删除(即“撤销”),然后使用notifyExecute()方法通知烤肉师傅。

1 public class Client { 2  3     public static void main(String[] args) throws Exception { 4         //开店前准备 5         Barbecuer barbecuer = new Barbecuer(); 6         Command bakeMuttonCommand1 = new BakeMuttonCommand(barbecuer); 7         Command bakeMuttonCommand2 = new BakeMuttonCommand(barbecuer); 8         Command bakeChickenWingCommand1 = new BakeChickenWingCommand(barbecuer); 9         Waiter waiter = new Waiter();10         11         //开门营业,顾客点菜12         waiter.setOrder(bakeMuttonCommand1);13         waiter.setOrder(bakeMuttonCommand2);14         //这里假设鸡翅卖完了15         waiter.setOrder(bakeChickenWingCommand1);16         17         //点菜完毕,通知厨房18         waiter.notifyExecute();19     }20     21 }

  运行结果如下:

  

  命令模式其实是把一个操作的对象与知道怎么执行一个操作的对象分隔开。至于命令模式使用时机,敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。

  源码地址:https://gitee.com/adamjiangwh/GoF

(0)

相关推荐

  • 图解Java设计模式之命令模式

    智能生活项目需求 命令模式基本介绍 命令模式的原理类图 命令模式在Spring框架中JdbcTemplate应用源码分析 命令模式的注意事项和细节 智能生活项目需求 看一个具体的需求 我们买类一套智能 ...

  • CommandPattern命令模式

    命令模式 1.定义 将一个请求封装成一个对象,从而可以使用不同的请求来参数化客户端. 命令模式通常有3种角色 Receiver:接收者角色.负责执行命令的角色 抽象接收者:具有所有特性的接收者的集合 ...

  • PHP设计模式之命令模式

    PHP设计模式之命令模式 命令模式,也称为动作或者事务模式,很多教材会用饭馆来举例.作为顾客的我们是命令的下达者,服务员是这个命令的接收者,菜单是这个实际的命令,而厨师是这个命令的执行者.那么,这个模 ...

  • 设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)

    设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)

  • 简说设计模式——迭代器模式

    一.什么是迭代器模式 迭代器这个词在Java中出现过,即Java中使用Iterator迭代器对集合进行遍历,但迭代器模式算是一个没落的模式,基本上没人会单独写一个迭代器,除非是产品性质的开发. 迭代器 ...

  • 简说设计模式——访问者模式

    一.什么是访问者模式 访问者模式是一个相对比较简单,但结构又稍显复杂的模式,它讲的是表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.例如,你在 ...

  • 简说设计模式——解释器模式

    一.什么是解释器模式 解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树.诸如此类 ...

  • 简说设计模式——状态模式

    一.什么是状态模式 状态这个词汇我们并不陌生,在日常生活中,不同时间就有不同的状态,早上起来精神饱满,中文想睡觉,下午又渐渐恢复,晚上可能精神更旺也可能耗费体力只想睡觉,这一天中就对应着不同的状态.或 ...

  • Java设计模式【命令模式】

    命令模式 命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行.这个过程好在,三者相互解耦,任何一方都不 ...

  • [PHP小课堂]PHP设计模式之命令模式

    [PHP小课堂]PHP设计模式之命令模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 【Java设计模式】 Java设计模式之(六)命令模式(Command Pattern)

    命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式.请求以命令的形式包裹在对象中,并传给调用对象.调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该 ...