学好设计模式防被祭天:命令模式

命令模式

为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。

一:理解

  1. 命令模式作用于调用者和被调用者之间,新建命令对象。调用者无需直接持有被调用者对象,实现两者之间的解耦。
  2. 例如在计算机网络中,客户端向服务器发送请求/命令,客户端不持有服务器对象,而是向服务器发一个包,包中含有服务器地址,以及命令的具体内容。这和命令模式类似,抽取了命令对象。


二:例子

你是个富二代,家里有很多仆人。

为了方便,这里只列出程序员(Programmer)和女仆(Maidservant)。

当你无聊的时候,你就会找你的仆人去玩耍。

你希望可以设计一个优雅的系统来解决这个玩耍问题,也好在二代圈子里炫耀一番。

于是你叫来了程序员小菜帮你完成这个玩耍程序。

小菜上来就是一顿敲,新建了程序员类和女仆类。

程序员有两个方法,洗键盘和敲代码。

女仆也有两个方法,洗澡和papapa。

// 程序员类
public class Programmer {
    public void washKeyboard() {
        System.out.println("洗键盘!");
    }

    public void program() {
        System.out.println("表演敲代码!");
    }
}

// 女仆类
public class Maidservant {
    public void wash() {
        System.out.println("洗澡!");
    }

    public void papapa() {
        System.out.println("如果你追到我,我就和你嘿嘿嘿!");
    }
}

同时,他也为你设计了一个富二代(FuErDai)类,包括两个属性,分别是程序员和女仆,同时包含两个方法,分别是和程序员玩,和女仆玩。

程序员比较无聊,和程序员玩就是直接让程序员洗键盘和敲代码。

和女仆玩就比较有意思,可以先洗澡,然后再papapa。

@Data
public class FuErDai {
    private Programmer programmer;
    private Maidservant maidservant;

    public void playWithProgrammer() {
        programmer.washKeyboard();
        programmer.program();
    }

    public void playWithServant() {
        maidservant.wash();
        maidservant.papapa();
    }
}

同时附上一个测试代码:

public class ClientV1 {
    public static void main(String[] args) {
        FuErDai fuErDai = new FuErDai();
        fuErDai.setProgrammer(new Programmer());
        fuErDai.setMaidservant(new Maidservant());
        fuErDai.playWithProgrammer();
        fuErDai.playWithServant();
    }
}

输入/输出:

洗键盘!
表演敲代码!
洗澡!
如果你追到我,我就和你嘿嘿嘿!

你觉得这个代码挺有意思的,就拿到二代圈子去炫耀。

他们真的很严格

没想到你的朋友真的很严格,对这个程序各种挑毛病。你炫耀不成,反倒被嘲讽了一番。

你的朋友发现,你只需要和你的仆人玩耍而已,为什么需要知道他们具体怎么玩呢,例如在和女仆玩的时候,你需要知道她有wash和papapa方法。

如果哪天女仆不想在papapa之前洗澡了,去掉了自己的wash方法,但你并没有引用女仆提供的最新jar包,当你想和女仆玩耍时,就出错了。

和女仆之间有太多的关系,不好不好。

你灰头土脸地回到家,气得直拍桌子,并拿出藏在地窖深处的鹤顶红,准备杀了这位程序员祭天。

拍桌子

吓得小菜急忙连夜修改代码。

他提取了一个仆人(Servant)接口,声明了playWithFuErDai方法。

程序员和女仆同时实现这个接口,对外提供playWithFuErDai方法。

public interface Servant {
    void playWithFuErDai();
}

// 程序员类
public class Programmer implements Servant{
    private void washKeyboard() {
        System.out.println("洗键盘!");
    }

    private void program() {
        System.out.println("表演敲代码!");
    }

    @Override
    public void playWithFuErDai() {
        washKeyboard();
        program();
    }
}

// 女仆类
public class Maidservant implements Servant{
    private void wash() {
        System.out.println("洗澡!");
    }

    private void papapa() {
        System.out.println("如果你追到我,我就和你嘿嘿嘿!");
    }

    @Override
    public void playWithFuErDai() {
        wash();
        papapa();
    }
}

修改之后的富二代代码,只需直接调用仆人的playWithFuErDai方法,而不用关心仆人到底是怎么玩的。

@Data
public class FuErDai {
    private Programmer programmer;
    private Maidservant maidservant;

    public void playWithProgrammer() {
        programmer.playWithFuErDai();
    }

    public void playWithServant() {
        maidservant.playWithFuErDai();
    }
}

你觉得这个代码还行,就原谅了小菜。

一段时间后,你发现和程序员玩耍真的是太没意思了,不如和你的宠物狗玩耍。

于是,你就命令小菜修改这个玩耍程序。

小菜思考了一下:

  1. 富二代的喜好经常变化,如果只去掉富二代类的程序员属性,加一个狗属性,那下次仍旧需要修改。
  2. 富二代其实只需要有两个“按键”,按下去就可以找到对象进行玩耍,而不固定和谁玩耍。
  3. 真正应该属于富二代的属性应该是两个命令,而不是两个陪他玩耍的“人”。

小菜决定使用命令模式

他先新建了一个命令接口,申明了execute方法,命令就是用来被执行的。

public interface Command {
    void execute();
}

他还新建了两个具体的命令类,分别是和仆人玩耍命令以及和狗玩耍命令。

和仆人玩耍命令类包含了一个仆人属性,在execute方法中,直接调用仆人的playWithFuErDai方法即可。

和狗玩耍的命令类似。

// 和程序员玩耍命令
@Data
public class PlayWithServantCommand implements Command {
    private Servant servant;

    @Override
    public void execute() {
        servant.playWithFuErDai();
    }
}

// 和狗玩耍命令
@Data
public class PlayWithDogCommand implements Command {
    Dog dog;

    @Override
    public void execute() {
        dog.playWithFuErDai();
    }
}

同时,小菜也重构了富二代类,包含两个命令属性,以及两个用于触发命令的playOne和playTwo方法。

@Data
public class FuErDai {
    private Command commandOne;
    private Command commandTwo;

    public void playOne() {
        commandOne.execute();
    }

    public void playTwo() {
        commandTwo.execute();
    }
}

测试代码:

public class ClientV2 {
    public static void main(String[] args) {
        PlayWithServantCommand playWithServantCommand = new PlayWithServantCommand();
        Maidservant maidservant = new Maidservant();
        playWithServantCommand.setServant(maidservant);
        PlayWithDogCommand playWithDogCommand = new PlayWithDogCommand();
        Dog dog = new Dog();
        playWithDogCommand.setDog(dog);
        FuErDai fuErDai = new FuErDai();
        fuErDai.setCommandOne(playWithServantCommand);
        fuErDai.setCommandTwo(playWithDogCommand);
        fuErDai.playOne();
        fuErDai.playTwo();
    }
}

输入/输出:

洗澡!
如果你追到我,我就和你嘿嘿嘿!
汪汪汪!

这就是命令模式,作为富二代的你,只需关心发出命令,可以随时修改命令的执行者,并且不用关心其如何执行。

使用命令模式重构之后,就再也不用担心富二代喜新厌旧了。

哪天你想要和你的基友玩耍了,不用修改你本身的代码,只需新建一个PlayWithJiYouCommand类,新建对象并set进富二代对象的其中一个command属性中即可。

你按下其中一个按钮,就可以和基友疯狂地玩耍了。

当然,如果高富帅有很多仆人,可以在FuErDai中添加一个数据结构用于保存所有的命令,而不必替换。

你感觉命令模式很好用,并且这个命名很符合你高富帅的身份,你笑得像个200斤的孩子。

笑得像个200斤的孩子


三:再理解

  1. 命令模式可以做到调用者和被调用者之间的解耦。
  2. 对修改关闭,对增加开放,只需增加命令类,而无需修改调用者代码。
  3. 就像家里的灯,你真正持有的是开灯的那个开关。灯坏了可以直接换,甚至可以把灯换成电扇。有了开关,你无需学习如何操控更换后的电器。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容