命令模式介绍:命令模式相对于其它模式来说并没有那么多的条条框框,不算是一个很“规矩”的模式,不过,就是基于这一点,命令模式相对于其它的设计模式更为灵活,我们接触比较多的命令模式无非就是菜单命令,如在操作系统中,我们点击“关机”命令,系统会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机等,而我闪的命令模式也与之相似。将一系列的方法调用封装,用户只需要调用一个方法执行,那么所有这些被封装的方法就会被挨个执行调用
命令模式定义:将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,对请求排队或者记录请求日志操作
命令模式使用场景:需要抽象出待执行的动作,然后以参数的形式提供出来 -- 类似于面向过程中的回调,而命令模式正是回调的面向对象的实现。
命令模式主要是命令的请求者与命令的执行者之间的关系,其中命令的里面保存了一个命令执行者的引用
下面以小时候玩的“俄罗斯方块”游戏为例子,讲解命令模式的用法。
俄罗斯方块游戏中有4个按钮,两个左右的,一个快速落下的,还有一个是变换方块的形状的,一个玩游戏的人就相当于我们的客户端,而游戏上4个按钮就相当于4个请求者或者称为调用者,执行具体按钮命令的逻辑方法可以看作是命令角色。
下面以代码说明。首先是命令的接收者
1 /** 2 * 命令接收者,俄罗斯方块游戏 3 * 4 */ 5 public class TerisMachine { 6 7 //真正处理"向左"操作的逻辑代码 8 public void toLeft(){ 9 System.out.println("向左"); 10 } 11 12 //真正处理"向右"操作的逻辑代码 13 public void toRight(){ 14 System.out.println("向右"); 15 } 16 17 //真正处理"快速向下"操作的逻辑代码 18 public void fastToBottom(){ 19 System.out.println("快速向下"); 20 } 21 22 //真正处理"改变形状"操作的逻辑代码 23 public void transform(){ 24 System.out.println("改变形状"); 25 } 26 }
TetrisMachine类是整个命令模式中唯一处理具体代码逻辑的地方,其他的类都是直接或者间接地调用到该类的方法,这就是接收者的角色,处理具体的逻辑。
接下来我们定义一个接口命令来作为命令角色的抽象
1 /** 2 * 命令角色的抽象 3 */ 4 public interface Command { 5 //命令执行的方法 6 void execute(); 7 }
下面分别实现4个命令
1 /** 2 * 向左移动的命令 3 */ 4 public class LeftCommand implements Command{ 5 //持有一个命令执行者的引用 6 TerisMachine terisMachine; 7 8 public void setTerisMachine(TerisMachine terisMachine){ 9 this.terisMachine = terisMachine; 10 } 11 12 @Override 13 public void execute() { 14 //调用游戏机里面的方法具体执行 15 terisMachine.toLeft(); 16 } 17 }
1 /** 2 * 向右移动的命令 3 */ 4 public class RightCommand implements Command{ 5 //持有一个命令执行者的引用 6 TerisMachine terisMachine; 7 8 public void setTerisMachine(TerisMachine terisMachine){ 9 this.terisMachine = terisMachine; 10 } 11 12 @Override 13 public void execute() { 14 //调用游戏机里面的方法具体执行 15 terisMachine.toRight(); 16 } 17 }
1 /** 2 * 快速向下的命令 3 */ 4 public class FastToBottomCommand implements Command{ 5 //持有一个命令执行者的引用 6 TerisMachine terisMachine; 7 8 public void setTerisMachine(TerisMachine terisMachine){ 9 this.terisMachine = terisMachine; 10 } 11 12 @Override 13 public void execute() { 14 //调用游戏机里面的方法具体执行 15 terisMachine.fastToBottom(); 16 } 17 }
1 /** 2 * 改变形状的命令 3 */ 4 public class TransformCommand implements Command{ 5 //持有一个命令执行者的引用 6 TerisMachine terisMachine; 7 8 public void setTerisMachine(TerisMachine terisMachine){ 9 this.terisMachine = terisMachine; 10 } 11 12 @Override 13 public void execute() { 14 //调用游戏机里面的方法具体执行 15 terisMachine.transform(); 16 } 17 }
上面有个相应的4个命令了。下面再实现一个命令的请求者
1 /** 2 * 3 * 命令的请求者 4 */ 5 public class Buttons { 6 //向左移动的命令 7 private LeftCommand leftCommand; 8 //向右移动的命令 9 private RightCommand rightCommand; 10 //快速萍的命令 11 private FastToBottomCommand fastToBottomCommand; 12 //改变形状的命令 13 private TransformCommand transformCommand; 14 15 //设置向左移动的命令 16 public void setLeftCommand(LeftCommand leftCommand) { 17 this.leftCommand = leftCommand; 18 } 19 20 //设置向右移动的命令 21 public void setRightCommand(RightCommand rightCommand) { 22 this.rightCommand = rightCommand; 23 } 24 25 //设置快速落下的命令 26 public void setFastToBottomCommand(FastToBottomCommand fastToBottomCommand) { 27 this.fastToBottomCommand = fastToBottomCommand; 28 } 29 30 //设置改变形状的命令 31 public void setTransformCommand(TransformCommand transformCommand) { 32 this.transformCommand = transformCommand; 33 } 34 35 //按下按钮向左移动 36 public void toLeft(){ 37 leftCommand.execute(); 38 } 39 40 //按下按钮向右移动 41 public void toRight(){ 42 rightCommand.execute(); 43 } 44 45 //按下按钮快速落下 46 public void fall(){ 47 fastToBottomCommand.execute(); 48 } 49 50 //按下按钮改变形状 51 public void transform(){ 52 transformCommand.execute(); 53 } 54 55 }
最后由客户端来决定如何使用
客户端类:
1 /** 2 * 玩家,也是客户端 3 */ 4 public class Player { 5 public static void main(String[] args){ 6 test1(); 7 } 8 9 //测试命令模式 10 private static void test1() { 11 //俄罗斯方块游戏 12 TerisMachine terisMachine = new TerisMachine(); 13 14 //根据游戏我们造4种命令 15 LeftCommand leftCommand = new LeftCommand(); 16 RightCommand rightCommand = new RightCommand(); 17 FastToBottomCommand fastToBottomCommand = new FastToBottomCommand(); 18 TransformCommand transformCommand = new TransformCommand(); 19 20 //引用一个命令的具体执行者 21 leftCommand.setTerisMachine(terisMachine); 22 rightCommand.setTerisMachine(terisMachine); 23 fastToBottomCommand.setTerisMachine(terisMachine); 24 transformCommand.setTerisMachine(terisMachine); 25 26 //按钮可以执行不同的命令 27 Buttons buttons = new Buttons(); 28 buttons.setLeftCommand(leftCommand); 29 buttons.setRightCommand(rightCommand); 30 buttons.setFastToBottomCommand(fastToBottomCommand); 31 buttons.setTransformCommand(transformCommand); 32 33 //具体按下哪个键玩家说了算 34 buttons.toLeft(); 35 buttons.toRight(); 36 buttons.fall(); 37 buttons.transform(); 38 } 39 40 //对于大部分开发者来说很容易接受下面的代码 41 private static void test2(){ 42 //俄罗斯方块游戏 43 TerisMachine machine = new TerisMachine(); 44 45 //要实现怎样的控制,直接调用相应的函数就行 46 machine.toLeft(); 47 machine.toRight(); 48 machine.fastToBottom(); 49 machine.transform(); 50 } 51 52 53 }
或者大家在看了这一长篇代码后心存疑惑,明明就是一个很简单的问题,为什么要做的如此复杂呢?对于大部分的开发者来说更愿意接受上面的 test2()中的代码,也就是:
//对于大部分开发者来说很容易接受下面的代码
41 private static void test2(){
42 //俄罗斯方块游戏
43 TerisMachine machine = new TerisMachine();
44
45 //要实现怎样的控制,直接调用相应的函数就行
46 machine.toLeft();
47 machine.toRight();
48 machine.fastToBottom();
49 machine.transform();
50 }
调用逻辑做得如此复杂,这是因为开发起来方便,每次我们增加或者修改游戏功能只需要修改 TetrisMachine类就行了,然后对应的改一改Player类,一切都很方便。但是,对开发者自己来说是方便了,那么,如果有一天开发者不再负责这个项目了呢?这样的逻辑留给后来者,没有人会觉得方便。设计模式有一条很重要的原则:对修改关闭,对扩展开放。大家可以细细体会。