1.1 吃羊肉串!
烧烤摊旁边等着拿肉串的人七嘴八舌地叫开了。场面有些混乱,由于人实在太多,烤羊肉串的老板已经分不清谁是谁,造成分发错误,收钱错误,烤肉质量不过关等。
外面打游击烤羊肉串和这种开门店做烤肉,哪个更赚钱?
这很难讲,毕竟各有各的好,在外面打游击,好处是不用租房,不用上税,最多就是交点'保护费',但下雨天不行、大白天不行、太晚也不行,一般都是傍晚做几个钟头,顾客也不固定,像刚才那个,由于人多造成混乱,于是就放跑了我们这两条大鱼,其实他的生意是不稳定的。大白天,城管没下班呢,怎能容忍他如此安逸。超过晚上11点,夜深人静,谁还愿意站在路边吃烤肉。但开门店就不一样了,不管什么时间都可以做生意,由于环境相对好,所以固定客户就多,看似好像房租交出去了,但其实由于顾客多,而且是正经做生意,所以最终可以赚到大钱。
1.2 烧烤摊vs.烧烤店
"因为要吃烤肉的人太多,都希望能最快吃到肉串,烤肉老板一个人,所以有些混乱。"
"还不止这些,老板一个人,来的人一多,他就未必记得住谁交没交过钱,要几串,需不需要放辣等。"
"是呀,大家都站在那里,没什么事,于是都盯着烤肉去了,哪一串多、哪一串少、哪一串烤得好、哪一串烤得焦看得清清楚楚,于是'挑剔'也就接踵而至。"
"这其实就是我们在编程中常说的什么?"
"我想想,你是想说'紧耦合'?"
"哈,不错,不枉我的精心栽培。"
"由于客户和烤羊肉串老板的'紧耦合',所以容易出错,也容易产生挑剔。"
"说得对,这其实就是'行为请求者'与'行为实现者'的紧耦合。我们需要记录哪个人要几串羊肉串,有没有特殊要求(放辣不放辣),付没付过钱,谁先谁后,这其实都相当于对请求做什么?"
"对请求做记录,啊,应该是做日志。"
"很好,那么如果有人需要退回请求,或者要求烤肉重烤,这其实就是?"
"就相当于撤销和重做吧。"
"OK,所以对请求排队或记录请求日志,以及支持可撤销的操作等行为时,'行为请求者'与'行为实现者'的紧耦合是不太适合的。你说怎么办?"
"哈,这是最终结果,不是这个意思,我们是烤肉请求者,烤肉的师傅是烤肉的实现者,对于开门店来说,我们用得着去看着烤肉的实现过程吗?现实是怎么做的呢?"
"哦,我明白你的意思了,我们不用去认识烤肉者是谁,连他的面都不用见到,我们只需要给接待我们的服务员说我们要什么就可以了。他可以记录我们的请求,然后再由他去通知烤肉师傅做。"
"而且,由于我们所做的请求,其实也就是我们点肉的订单,上面有很详细的我们的要求,所有的客户都有这一份订单,烤肉师傅可以按先后顺序操作,不会混乱,也不会遗忘了。"
"收钱的时候,也不会多收或少收。"
"优点还不止这里,比如说,""服务员,我们那十串羊肉串太多了,改成六串就可以了。"
"好的!"服务员答道。
"你注意看他接着做了什么?"
"他好像在一个小本子上划了一下,然后去通知烤肉师傅了。"
"这其实是在做撤销行为的操作。由于有了记录,所以最终算账是不会错的。"
"对对对,这种利用一个服务员来解耦客户和烤肉师傅的处理好处真的很多。"
1.3 紧耦合设计
代码结构图
package code.chapter23.command1;
public class Test {
public static void main(String[] args) {
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
Barbecuer boy = new Barbecuer();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeChickenWing();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeChickenWing();
System.out.println();
System.out.println("**********************************************");
}
}
//烤肉串者
class Barbecuer{
//烤羊肉
public void bakeMutton(){
System.out.println("烤羊肉串!");
}
//烤鸡翅
public void bakeChickenWing(){
System.out.println("烤鸡翅!");
}
}
"很好,这就是路边烤肉的对应,如果用户多了,请求多了,就容易乱了。那你再尝试用门店的方式来实现它。"
"我知道一定需要增加服务员类,但怎么做有些不明白。"
"嗯,这里的确是难点,要知道,不管是烤羊肉串,还是烤鸡翅,还是其他烧烤,这些都是'烤肉串者类'的行为,也就是他的方法,具体怎么做都是由方法内部来实现,我们不用去管它。但是对于'服务员'类来说,他其实就是根据用户的需要,发个命令,说:'有人要十个羊肉串,有人要两个鸡翅',这些都是命令……"
"我明白了,你的意思是,把'烤肉串者'类当中的方法,分别写成多个命令类,那么它们就可以被'服务员'来请求了?"
"是的,说得没错,这些命令其实差不多都是同一个样式,于是你就可以泛化出一个抽象类,让'服务员'只管对抽象的'命令'发号施令就可以了。具体是什么命令,即烤什么,由客户来决定吧。"
1.4 命令模式
命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。[DP]
命令模式(Command)结构图
package code.chapter23.command0;
import java.util.ArrayList;
import java.util.Date;
import java.text.SimpleDateFormat;
public class Test {
public static void main(String[] args) {
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand();
System.out.println();
System.out.println("**********************************************");
}
}
//抽象命令类
abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver){
this.receiver = receiver;
}
//执行命令
public abstract void excuteCommand();
}
//具体命令类
class ConcreteCommand extends Command{
public ConcreteCommand(Receiver receiver){
super(receiver);
}
public void excuteCommand(){
receiver.action();
}
}
class Invoker{
private Command command;
public void setCommand(Command command){
this.command = command;
}
public void executeCommand(){
command.excuteCommand();
}
}
class Receiver{
public void action(){
System.out.println("执行请求!");
}
}
Command类,用来声明执行操作的接口。
ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现executeCommand。
Invoker类,要求该命令执行这个请求。
Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能作为一个接收者。
客户端代码,创建一个具体命令对象并设定它的接收者。
1.5 松耦合设计
代码结构图
package code.chapter23.command2;
public class Test {
public static void main(String[] args) {
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
//开店前的准备
Barbecuer boy = new Barbecuer();//烤肉厨师
Command bakeMuttonCommand1 = new BakeMuttonCommand(boy); //烤羊肉串
Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy); //烤鸡翅
Waiter girl = new Waiter(); //服务员
//开门营业
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.notifyCommand(); //通知厨师烤肉
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.notifyCommand(); //通知厨师烤肉
girl.setOrder(bakeChickenWingCommand1); //下单烤鸡翅
girl.notifyCommand(); //通知厨师烤肉
System.out.println();
System.out.println("**********************************************");
}
}
//抽象命令类
abstract class Command {
protected Barbecuer receiver;
public Command(Barbecuer receiver){
this.receiver = receiver;
}
//执行命令
public abstract void excuteCommand();
}
//烤羊肉命令类
class BakeMuttonCommand extends Command{
public BakeMuttonCommand(Barbecuer receiver){
super(receiver);
}
public void excuteCommand(){
receiver.bakeMutton();
}
}
//烤鸡翅命令类
class BakeChickenWingCommand extends Command{
public BakeChickenWingCommand(Barbecuer receiver){
super(receiver);
}
public void excuteCommand(){
receiver.bakeChickenWing();
}
}
//服务员类
class Waiter{
private Command command;
//设置订单
public void setOrder(Command command){
this.command = command;
}
//通知执行
public void notifyCommand(){
command.excuteCommand();
}
}
//烤肉串者
class Barbecuer{
//烤羊肉
public void bakeMutton(){
System.out.println("烤羊肉串!");
}
//烤鸡翅
public void bakeChickenWing(){
System.out.println("烤鸡翅!");
}
}
"很好很好,基本都把代码实现了。但没有体现出命令模式的作用。比如下面几个问题:第一,真实的情况其实并不是用户点一个菜,服务员就通知厨房去做一个,那样不科学,应该是点完烧烤后,服务员一次通知制作;第二,如果此时鸡翅没了,不应该是客户来判断是否还有,客户哪知道有没有呀,应该是服务员或烤肉串者来否决这个请求;第三,客户到底点了哪些烧烤或饮料,这是需要记录日志的,以备收费,也包括后期的统计;第四,客户完全有可能因为点的肉串太多而考虑取消一些还没有制作的肉串。这些问题都需要得到解决。"
"这,这怎么办到呀?"
"重构一下服务员Waiter类,尝试改一下。将private Command command;改成一个ArrayList,就能解决了。"
1.6 进一步改进命令模式
package code.chapter23.command3;
import java.util.ArrayList;
import java.util.Date;
import java.text.SimpleDateFormat;
public class Test {
public static void main(String[] args) {
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
//开店前的准备
Barbecuer boy = new Barbecuer();//烤肉厨师
Command bakeMuttonCommand1 = new BakeMuttonCommand(boy); //烤羊肉串
Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy); //烤鸡翅
Waiter girl = new Waiter(); //服务员
System.out.println("开门营业,顾客点菜");
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.setOrder(bakeMuttonCommand1); //下单烤羊肉串
girl.cancelOrder(bakeMuttonCommand1); //取消一串羊肉串订单
girl.setOrder(bakeChickenWingCommand1); //下单烤鸡翅
System.out.println("点菜完毕,通知厨房烧菜");
girl.notifyCommand(); //通知厨师
System.out.println();
System.out.println("**********************************************");
}
}
//抽象命令类
abstract class Command {
protected Barbecuer receiver;
public Command(Barbecuer receiver){
this.receiver = receiver;
}
//执行命令
public abstract void excuteCommand();
}
//烤羊肉命令类
class BakeMuttonCommand extends Command{
public BakeMuttonCommand(Barbecuer receiver){
super(receiver);
}
public void excuteCommand(){
receiver.bakeMutton();
}
}
//烤鸡翅命令类
class BakeChickenWingCommand extends Command{
public BakeChickenWingCommand(Barbecuer receiver){
super(receiver);
}
public void excuteCommand(){
receiver.bakeChickenWing();
}
}
//服务员类
class Waiter{
private ArrayList<Command> orders = new ArrayList<Command>();
//设置订单
public void setOrder(Command command){
String className=command.getClass().getSimpleName();
if (className.equals("BakeChickenWingCommand")){
System.out.println("服务员:鸡翅没有了,请点别的烧烤。");
}
else{
this.orders.add(command);
System.out.println("增加订单:"+className+" 时间:"+getNowTime());
}
}
//取消订单
public void cancelOrder(Command command){
String className=command.getClass().getSimpleName();
orders.remove(command);
System.out.println("取消订单:"+className+" 时间:"+getNowTime());
}
//通知执行
public void notifyCommand(){
for(Command command : orders)
command.excuteCommand();
}
private String getNowTime(){
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
return formatter.format(new Date()).toString();
}
}
//烤肉串者
class Barbecuer{
//烤羊肉
public void bakeMutton(){
System.out.println("烤羊肉串!");
}
//烤鸡翅
public void bakeChickenWing(){
System.out.println("烤鸡翅!");
}
}
1.7 命令模式的作用
"我觉得第一,它能较容易地设计一个命令队列;第二,在需要的情况下,可以较容易地将命令记入日志;第三,允许接收请求的一方决定是否要否决请求。"
"还有就是第四,可以容易地实现对请求的撤销和重做;第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。其实还有最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。[DP]
"但是否是碰到类似情况就一定要实现命令模式呢?"
"这就不一定了,比如命令模式支持撤销/恢复操作功能,但你还不清楚是否需要这个功能时,你要不要实现命令模式?"
"要,万一以后需要就不好办了。"
"其实应该是不要实现。敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。[R2P]"