美文网首页程序员
学好设计模式防被祭天:命令模式

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

作者: 阿菜的博客 | 来源:发表于2017-08-29 23:06 被阅读94次
命令模式

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

一:理解

  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. 就像家里的灯,你真正持有的是开灯的那个开关。灯坏了可以直接换,甚至可以把灯换成电扇。有了开关,你无需学习如何操控更换后的电器。

相关文章

网友评论

    本文标题:学好设计模式防被祭天:命令模式

    本文链接:https://www.haomeiwen.com/subject/rnwadxtx.html