从饭店聊命令模式

来源:互联网 发布:解放号 知乎 编辑:程序博客网 时间:2024/04/27 18:03

    本文虽为原创,但大量参考了<<大话设计模式>>一书中命令模式一章.特此声明.

    诸位看官,请将下面的事例转换成代码.
    "我去餐厅吃饭,点了一碗面条,三只鸡翅"
    怎么样,够简单的吧,知道你们都比较懒,不想编码,看看下面这个是不是和你想的一样
package com.command;    public class NoodlesReceiver {    String name;    public NoodlesReceiver(String name){        this.name=name;    }    @Override    public void doSomeThing(int table_num,int food_count) {        // TODO Auto-generated method stub        System.out.println(name+"给"+table_num+"号桌下"+food_count+"碗面条");    }}package com.command;public class BakeChickenWingReceiver {    String name;    public BakeChickenWingReceiver(String name){           this.name=name;    }            public void doSomeThing(int table_num,int food_count) {        // TODO Auto-generated method stub        System.out.println(name+"给"+table_num+"号桌来"+food_count+"个烤鸡翅");    }}package com.command;public class client {    public static void main(String[] args) {        //初始化两个厨师          NoodlesReceiver noodlesReceiver=                new NoodlesReceiver("下面条的李师傅");        BakeChickenWingReceiver bakeChickenWingReceiver=                new BakeChickenWingReceiver("烤鸡翅的张师傅");                noodlesReceiver.doSomeThing(10, 1);        bakeChickenWingReceiver.doSomeThing(10, 3);    }}


结果如下
下面条的李师傅给10号桌下1碗面条
烤鸡翅的张师傅给10号桌来3个烤鸡翅

怎么样,和你想的一样吗?
可是看上面的例子,如果再返回现实,那就是你去了饭店,直接对烤肉师傅和面条师傅下命令!开玩笑,咱都是有身份证的人,能亲自跑到满是油烟的厨房去?咋能没有服务员呢?(况且从软件设计的角度来讲,命令的发出者与实行者最好解耦).

第一次重构

两个厨师不变.加一个服务员.
package com.command;public class Waiter2 {    NoodlesReceiver noodlesReceiver;    BakeChickenWingReceiver bakeChickenWingReceiver;    String name;    public Waiter2(String name){        this.name=name;        //初始化两个厨师  一个服务员         noodlesReceiver=new NoodlesReceiver("下面条的李师傅");         bakeChickenWingReceiver=new BakeChickenWingReceiver("烤鸡翅的张师傅");    }        public void order(int table_num,int food_count,String food){            switch (food) {        case "鸡翅":            bakeChickenWingReceiver.doSomeThing(table_num, food_count);            break;        case "面条":            noodlesReceiver.doSomeThing(table_num, food_count);            break;        default:            System.out.println("sorry 小店没有您要点的食物");            break;        }    }}package com.command;public class client2 {    public static void main(String[] args) {                Waiter2 waiter2=new Waiter2("小美");        waiter2.order(10, 1, "面条");        waiter2.order(10, 3, "鸡翅");        waiter2.order(10, 3, "馒头");    }}



结果如下:
下面条的李师傅给10号桌下1碗面条
烤鸡翅的张师傅给10号桌来3个烤鸡翅
sorry 小店没有您要点的食物

看看client2 似乎还蛮不错的,给服务员说几号桌子点什么东西点几份,都很好呀,很符合实际.嗯,确实和很符合实际.问题是
1:大家看看服务员的类.服务员类里面包含两个厨师类,咱们暂且不论如果以后厨师发生变动,还需要改服务员的类,就是现实世界中也不不是这样呀!现实世界是:服务员是服务员,厨师是厨师,这俩没关系!
2:你去饭店吃饭,你是点完一个菜,就让服务员通知一次厨师?你要敢这样,服务员肯定心里骂你:nnd,你丫能不能一次说完,让我少跑几趟.
3:我刚点完1碗面条就不能更改了,我后悔了不想吃了,都不行.这也不符合实际.只有你的面条还没下锅,我就能取消.


我得告诉大家这次重构很遗憾,失败了!那咋办?回想实际情况.
实际情况并不是服务员给厨师下命令,也不是你点一道菜服务员就跑一次.而是服务员带着要给小本,上面可以写一条条的命令.等你点了十道八道菜后,有后悔了,推掉两道菜后,说ok了,服务员才将命令整体发送.对吧,实际难道不是这样?
命令,命令!!! 在这个问题里,命令是根本.各位看官请注意,不要钻牛角尖,大家可以这样想,这里的命令不是动词,不是人的一个方法,而是名词,是你写出来的一个"实体".且命令中包含接受命令的人.简单的说,命令本身知道,自己要传递给谁.(万物皆对象,且都有自己的属性,方法)

第二次重构

2.1次重构.

既然现在有两种厨师,那就先设置两种命令.而且要知道自己是传递给谁的,为了统一,给那两个厨师都加要给父类(接口).
public interface IReceiver {    public void doSomeThing(int table_num,int food_count);}


让两个厨师都实现IReceiver.代码不再赘述.

同理我们得让两个命令也都有个父类(接口).
package com.command;public interface ICommand {    public void action();}package com.command;public class BakeChickenWingCommand implements ICommand{    int  count;    int  num;    IReceiver receiver;    public BakeChickenWingCommand(IReceiver receive,int count,int num){        this.receiver=receive;        this.count=count;        this.num=num;    }    @Override    public void action() {        // TODO Auto-generated method stub        receiver.doSomeThing(num, count);        }}


面条的命令不再赘述,和鸡翅基本一样,不对,我看看,我擦 完全一样!
咋办?既然都一样,按就不分父类子类了,直接就是下面的样子

2.2次重构


public class Command {    int  count;    int  num;    IReceiver receiver;    public Command(IReceiver receive,int count,int num){        this.receiver=receive;        this.count=count;        this.num=num;    }        public void action() {        // TODO Auto-generated method stub        receiver.doSomeThing(num, count);        }}


诸位看官以为如何. 别着急发表意见 想想再说.先看看命令模式的类图.


                                                                                                                                                                                                 (上图来自<<大话设计模式>>)

其实在去饭店吃饭的例子里,是可以的,因为不管厨师是做面条还是鸡翅,都可以用一个doSomeThing来代替.可换一个场景呢?古代皇帝,让臣下打仗,镇守地方,升官,贬职你还都用一个doSomeThing搞定吗?所以必须分层必须用接口.
所以很遗憾,2.2次重构又失败了.但是就像哪个谁谁谁说的"没有经历过苦痛的顿悟是轻佻的",我们只有错过很多次才能真正的"对".

2.3次重构

越过上面的岔路,下面就是服务员了,刚才已经说了,服务员至少得提升两点,一能存储命令,打包发送,二就是能在提交所以命令前,撤销一部分命令,代码如下
package com.command;import java.util.ArrayList;public class Waiter {    String name;    ArrayList<ICommand> list;        public Waiter(String name){        this.name=name;    }        public void addCommand(ICommand command){        if(list==null)            list=new ArrayList<ICommand>();        list.add(command);            }        public void removeCommand(ICommand command){        System.out.println("顾客取消订单");        list.remove(command);            }        public void submit(){        int n=list.size();        for(int i=0;i<n;i++)            list.get(i).action();    }}


再来看看客户端的调用

package com.command;public class Client {    public static void main(String[] args) {        //初始化两个厨师  一个服务员(可以理解为饭店聘请了这三个人)        NoodlesReceiver noodlesReceiver=                new NoodlesReceiver("下面条的李师傅");        BakeChickenWingReceiver bakeChickenWingReceiver=                new BakeChickenWingReceiver("烤鸡翅的张师傅");        Waiter waiter=new Waiter("小美");                //顾客发出三个命令        NoodelsCommand noodelsCommand1=                new NoodelsCommand(noodlesReceiver, 6, 3);        NoodelsCommand noodelsCommand2=                new NoodelsCommand(noodlesReceiver, 30, 2);                BakeChickenWingCommand bakeChickenWingCommand=                new BakeChickenWingCommand(bakeChickenWingReceiver, 4, 8);                //服务员记录三个命令后 又撤销一个        waiter.addCommand(noodelsCommand1);        waiter.addCommand(noodelsCommand2);        waiter.addCommand(bakeChickenWingCommand);        waiter.removeCommand(noodelsCommand2);                //服务员将菜单上交  厨师做菜        waiter.submit();                    }}


结果如下

顾客取消订单
下面条的李师傅给3号桌下6碗面条
烤鸡翅的张师傅给8号桌来4个烤鸡翅


好了大功告成
现在总结一下命令模式的好处
1:它实现了命令队列;
2:把命令看成一个个实体,使得每做一次命令都能记下日志;
3:命令可撤销 这一点在某些有这样需求的系统中很有用;
4:也是最重要的一点 它让一个任务的请求者和实现者分离;
5:新加入的命令只要实现ICommand接口就好,方便扩展.


另外,本人的一点心得,设计模式有很多问题,例如因为xxx所以xxx,在初学者看来是很模糊的,为什么?这个所以和因为有关系吗?遇到这个问题,只能说内力还不够,继续编码吧.

0 0
原创粉丝点击