命令模式【command】
- 什么是命令模式
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。稍后我会以代码加文字的形式给大家解释清楚。别名(动作( Action ),事务(Transation )) - 什么场景会用到命令模式
我们模拟一下这个场景。用户去一个餐厅,可以点菜,并且可以取消之前点过的菜,原型图如下:
流程如下,用户下达指定,告诉服务员需要做什么菜,然后服务员再去通知相应的厨子做菜。我们从程序角度怎么去设计呢,如果大家运用到设计模式,肯定是要出于对设计上的考虑,所以第一我们要降低各个对象之间的耦合度,从上面的图我们不难看出,我们有三个对象需要分开来,第一是客户,是下达指定的人,第二就是服务员,是传递指令的人,第三是厨师,是执行指令的人。第二,我们需要考虑到扩展性,比如厨房现在推出了新的菜系,怎么在不修改原有代码的情况下进行扩展。命令模式通过将请求本身变成一个对象来使发出命令的对象可以向未指定的应用对象提出请求。顾名思义,命令模式,主要是请求对象,所以我们主要是在服务员上做思考。代码如下:
我们先定义后厨:
public interface Cook {
void makeTomatoEggSoup();
void makeNaanBread();
}
public class Chef implements Cook {
@Override
public void makeTomatoEggSoup() {
System.out.println("尝尝大厨做的西红柿蛋汤");
}
@Override
public void makeNaanBread() {
System.out.println("尝尝大厨做的印度飞饼");
}
}
public class RookieChef implements Cook {
@Override
public void makeTomatoEggSoup() {
System.out.println("尝尝新来的厨师做的西红柿蛋汤");
}
@Override
public void makeNaanBread() {
System.out.println("尝尝新来的厨师做的印度飞饼");
}
}
定义菜谱
public abstract class Food {
protected Cook cook;
public Food(Cook cook) {
this.cook = cook;
}
public abstract void make();
}
public class NaanBread extends Food {
public NaanBread(Cook cook) {
super(cook);
}
@Override
public void make() {
this.cook.makeNaanBread();
}
@Override
public String toString() {
return "NaanBread";
}
}
public class TomatoEggSoup extends Food {
public TomatoEggSoup(Cook cook) {
super(cook);
}
@Override
public void make() {
this.cook.makeTomatoEggSoup();
}
@Override
public String toString() {
return "TomatoEggSoup";
}
}
定义服务员
public class Waiter {
private List<Food> foods = new ArrayList<>();
public synchronized void add(Food food) {
foods.add(food);
System.out.println(String.format("您好,您确认点这个%s吗,我帮您记录下来.", food));
}
public synchronized void cancel(Food food) {
foods.remove(food);
System.out.println(String.format("您好,您确认取消这个%s吗,我帮您记录下来.", food));
}
public void execute() {
foods.forEach(Food::make);
}
}
测试方法
/**
* 我需要大厨帮我做一份印度飞饼
* 然后我听说最近有个新来的厨师,做的西红柿蛋汤味道不错,也给我来一份
* 算了,最近西红柿过敏,我还是不要了
* 今天我就吃印度飞饼吧
*/
public static void main(String[] args) {
Waiter waiter = new Waiter();
Food naanBread = new NaanBread(new Chef());
Food tomatoEggSoup = new TomatoEggSoup(new RookieChef());
waiter.add(naanBread);
waiter.add(tomatoEggSoup);
waiter.cancel(tomatoEggSoup);
waiter.execute();
}
我们不难发现,运用这个程序可以很好的去实现用户点餐,比如我点餐和取消,都是通过服务员去处理的,厨师只管去处理菜单上面有的菜,并且如果我后面添加了其他的菜,也只用去添加新的方法,不需要去改动现有的逻辑。
看了这段代码,是不是对命令模式有了一个很清晰的认识了
- 适用性
当你有如下需求时,可使用Command模式:
• 在不同的时刻指定、排列和执行请求。一个 Command对象可以有一个与初始请求无关
的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负
责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
• 支持取消操作。Command的Execute操作可在实施操作前将状态存储起来,在取消操作时
这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作
取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和
向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重
做”。
• 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添
加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过
程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
• 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务(transation)
的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事
务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有
的事务。同时使用该模式也易于添加新事务以扩展系统。
相关参考:
<大话设计模式>,<设计模式,可复用面向对象软件的基础>
源码在此