例子
假设有一个快餐店,而我是该餐厅的点餐服务员,那么我一天的工作应该是这样的:当某位客人点餐或者打开订餐电话后,我会把他的需求写在清单上,然后交给厨房,客人不用关心是哪些厨师帮他炒菜。我们餐厅还可以满足客人需要的定时服务,比如客人可能当前正在回家的路上,要求1小时后才开始炒他的才,只要订单还在,厨师就不会忘记,客人也可以很方便地撤销订单,另外如果有太多的客人点餐,厨房就可以按照订单的顺序排队炒菜。
这些记录着订餐信息的清单,便是命令模式中的命令对象。
模式动机
1.将请求发送者和接收者完全解耦
2.发送者与接收者之间没有直接引用关系
3.发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求
模式结构
Command(抽象命令类):抽象命令类一般是一个抽象类接口,在其中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作。
ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中,具体命令类在实现execute()方法时将调用接收者对象的相关操作(Action)
Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求,一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系,在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的excute()方法,从而实现简介调用请求接收者的相关操作
Receiver(接收者):接收者执行与请求相关的操作,具体实现对请求的业务处理
如何实现
命令模式的本质是对请求进行封装
一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收,操作是否被执行,何时被执行,以及怎么被实现
分析问题
客人即调用者,厨房即接收者。我们需要将二者解耦即在加入服务员一角色,当客人点餐时将客人发出的点餐命令交给厨房即可。同时服务员可以负责订单的日志记录、撤回等功能。
代码
饭店后厨类
public class Cook {
//厨师自身的方法
public void makefanqie(String table,String num){
System.out.println("张师傅为"+table+"号桌做"+num+"份番茄炒蛋");
}
public void makechaofan(String table,String num){
System.out.println("王师傅为"+table+"号桌做"+num+"份扬州炒饭");
}
}
抽象命令类
//抽象命令类
public interface Command {
public String getTable();
public String getNum();
public String getName();
void execute();
}
具体命令类
//制作扬州炒饭的命令
public class ChaofanCommand implements Command{
private Cook cook;
String num,table;
public ChaofanCommand(Cook cook, String num, String table){
this.cook=cook;
this.table=table;
this.num=num;
}
@Override
public void execute() {
cook.makechaofan(table,num);
}
public String getName(){
return "扬州炒饭";
}
public String getTable() {
return table;
}
public String getNum() {
return num;
}
}
//制作番茄炒蛋的命令
public class FanqieCommand implements Command{
private Cook cook;
String num,table;
//下单
public FanqieCommand(Cook cook,String num,String table){
this.cook=cook;
this.table=table;
this.num=num;
}
//执行命令
@Override
public void execute() {
cook.makefanqie(table,num);
}
public String getName(){
return "番茄炒蛋";
}
public String getTable() {
return table;
}
public String getNum() {
return num;
}
}
服务员类
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public class Waiter {
//持有命令对象
private List<Command> commandList=new ArrayList<Command>();
//设置订单
public void SetCommand(Command command){
commandList.add(command);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(command.getTable()+ "号桌增加订单:"+command.getNum()+"份"+command.getName());
}
//取消订单
public void CancelOrder(Command command){
commandList.remove(command);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(command.getTable()+ "号桌取消订单:"+command.getNum()+"份"+command.getName());
}
//通知后厨
public void Notify(){
for (Command cmd:commandList) {
cmd.execute();
}
}
}
客户端类
//命令模式
public class Main {
public static void main(String[] args) {
//店家开业准备
Cook cook=new Cook();
Waiter waiter=new Waiter();
//顾客点餐
FanqieCommand cmd1=new FanqieCommand(cook,"1","1");
ChaofanCommand cmd2=new ChaofanCommand(cook,"2","1");
waiter.SetCommand(cmd1);
waiter.SetCommand(cmd2);
//点菜完毕
waiter.Notify();
}
}
拓展——宏命令
宏命令(Macro Command)又称为组合命令(Composite Command),它是组合模式和命令模式联用的产物
宏命令是一个具体命令类,它拥有一个集合,在该集合中包含了对其他命令对象的引用
当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法。一个宏命令的成员可以是简单命令,还可以继续是宏命令
执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理
例子及代码
当顾客购买套餐时,我们可以使用宏命令进行批量处理
我们需要更改的类只有四部分
抽象宏命令类
//抽象宏命令类
public interface MacroCommand extends Command{
void addCommand(Command command);
void removeCommand(Command command);
}
具体宏命令类
import java.util.ArrayList;
import java.util.List;
//具体宏命令类
public class TaocanMacroCommand implements MacroCommand{
private Cook cook;
String table;
//将需要批量处理的命令寄存
private List<Command> commandList=new ArrayList<Command>();
public TaocanMacroCommand(Cook cook, String table) {
this.cook = cook;
this.table = table;
}
@Override
public String getTable() {
return table;
}
@Override
public String getNum() {
return null;
}
@Override
public String getName() {
return "套餐1";
}
@Override
public void execute() {
for (Command cmd:commandList) {
cmd.execute();
}
}
@Override
public void addCommand(Command command) {
commandList.add(command);
}
@Override
public void removeCommand(Command command) {
commandList.remove(command);
}
}
服务员类
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public class Waiter {
//持有命令对象
private List<Command> commandList=new ArrayList<Command>();
//设置订单
public void SetCommand(Command command){
commandList.add(command);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(command.getTable()+ "号桌增加订单:"+command.getNum()+"份"+command.getName());
}
//设置套餐
public void SetCTaocan(MacroCommand taocan){
commandList.add(taocan);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(taocan.getTable()+ "号桌增加套餐:"+taocan.getName());
}
//取消订单
public void CancelOrder(Command command){
commandList.remove(command);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(command.getTable()+ "号桌取消订单:"+command.getNum()+"份"+command.getName());
}
//取消套餐
public void CancelTaocan(MacroCommand taocan){
commandList.remove(taocan);
Logger logger=Logger.getLogger(this.getClass().getName());
logger.info(taocan.getTable()+ "号桌取消套餐:"+taocan.getName());
}
//通知后厨
public void Notify(){
for (Command cmd:commandList) {
cmd.execute();
}
}
//处理完一个人的订单后清除笔记本
public List<Command> getCommandList() {
return commandList;
}
}
客户端类
//命令模式
public class Main {
public static void main(String[] args) {
//店家开业准备
Cook cook=new Cook();
Waiter waiter=new Waiter();
//顾客1单点
FanqieCommand cmd1=new FanqieCommand(cook,"1","1");
ChaofanCommand cmd2=new ChaofanCommand(cook,"2","1");
waiter.SetCommand(cmd1);
waiter.SetCommand(cmd2);
//顾客1点菜完毕
waiter.Notify();
waiter.getCommandList().clear();
//顾客2点套餐
FanqieCommand cmd3=new FanqieCommand(cook,"1","2");
ChaofanCommand cmd4=new ChaofanCommand(cook,"1","2");
TaocanMacroCommand tc=new TaocanMacroCommand(cook,"2");
tc.addCommand(cmd3);
tc.addCommand(cmd4);
waiter.SetCTaocan(tc);
//顾客2点菜完毕
waiter.Notify();
waiter.getCommandList().clear();
}
}
模式优点
1.降低系统耦合度
2.新的命令可以很容易的加入到系统中,符合开闭原则
3.可以比较容易的设计一个命令队列或宏命令(组合命令)
4.为请求的撤销和恢复操作提供了一种实现方案
模式缺点
使用命令模式可能会导致某些系统过多的具体命令类(针对每一个对请求接收者的调用操作都需要设计一个具体以命令类,所以在某些系统中可能需要提供大量的命令类,这将影响命令模式的使用)
适用环境
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
2.系统需要在不同的事件指定请求,将请求排队和执行请求
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
4.系统需要将一组操作组合在一起形成宏命令