为了防止被“杀”了祭天,学点设计模式,并总结下还是有必要的。
一:理解
- 命令模式作用于调用者和被调用者之间,新建命令对象。调用者无需直接持有被调用者对象,实现两者之间的解耦。
- 例如在计算机网络中,客户端向服务器发送请求/命令,客户端不持有服务器对象,而是向服务器发一个包,包中含有服务器地址,以及命令的具体内容。这和命令模式类似,抽取了命令对象。
二:例子
你是个富二代,家里有很多仆人。
为了方便,这里只列出程序员(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();
}
}
你觉得这个代码还行,就原谅了小菜。
一段时间后,你发现和程序员玩耍真的是太没意思了,不如和你的宠物狗玩耍。
于是,你就命令小菜修改这个玩耍程序。
小菜思考了一下:
- 富二代的喜好经常变化,如果只去掉富二代类的程序员属性,加一个狗属性,那下次仍旧需要修改。
- 富二代其实只需要有两个“按键”,按下去就可以找到对象进行玩耍,而不固定和谁玩耍。
- 真正应该属于富二代的属性应该是两个命令,而不是两个陪他玩耍的“人”。
小菜决定使用命令模式。
他先新建了一个命令接口,申明了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斤的孩子。
三:再理解
- 命令模式可以做到调用者和被调用者之间的解耦。
- 对修改关闭,对增加开放,只需增加命令类,而无需修改调用者代码。
- 就像家里的灯,你真正持有的是开灯的那个开关。灯坏了可以直接换,甚至可以把灯换成电扇。有了开关,你无需学习如何操控更换后的电器。