命令模式 Command 行为型 设计模式(十八)
命令模式(Command)
请分析上图中这条命令的涉及到的角色以及执行过程,一种可能的理解方式是这样子的:
涉及角色为:大狗子和大狗子他妈
过程为:大狗子他妈角色 调用 大狗子的“回家吃饭”方法
定义一个接受者和行为之间的弱耦合关系,实现execute()方法
负责调用命令接受者的响相应操作 请求者角色Invoker 负责调用命令对象执行命令,相关的方法叫做行动action方法 接受者角色Receiver 负责具体实施和执行一个请求,任何一个类都可以成为接收者 Command角色封装了命令接收者并且内部的执行方法调用命令接收者的方法 也就是一般形如: Command(Receiver receiver){ ...... execute(){ receiver.action(); ... 而Invoker角色接收Command,调用Command的execute方法 通过将“命令”这一行为抽象封装,命令的执行不再是请求者调用被请求者的方法这种强关联 ,而是可以进行分离 分离后,这一命令就可以像普通的对象一样进行参数传递等 commands = new ArrayList();
这就形成了一个队列
你可以动态的向队列中增加命令,也可以从队列中移除命令
你还可以将这个队列保存起来,批处理的执行或者定时每天的去执行
你还可以将这些命令请求持久化到文件中,因为这些命令、请求 也不过就是一个个的对象而已
引子
package command.origin; public class BigDog { public void goHomeForDinner() { System.out.println("回家吃饭"); } }
package command.origin; public class BigDogMother { public static void main(String[] args) { BigDog bigDog = new BigDog(); bigDog.goHomeForDinner(); } }BigDog类拥有回家吃饭方法goHomeForDinner BigDogMother作为客户端调用BigDog的回家吃饭方法,完成了“大狗子回家吃饭”这个请求 上面的示例中,通过对命令执行者的方法调用,完成了命令的下发,命令调用者与命令执行者之间是紧密耦合的 我们是否可以考虑换一种思维方式,将“你妈喊你回家吃饭”这一命令封装成为一个对象? 不再是大狗子他妈调用大狗子的回家吃饭方法 而是大狗子他妈下发了一个命令,命令的内容是“大狗子回家吃饭” 接下来是命令的执行 这样的话,“命令”就不再是一种方法调用了,在大狗子妈和大狗子之间多了一个环节---“命令” 看下代码演变 BigDog 没有变化 新增加了命令类Command 使用对象的接受者BigDog 进行初始化 命令的execute方法内部调用接受者BigDog的方法 BigDogMother中下发了三个命令 然后逐个执行这三个命令
package command.origin; public class BigDog { public void goHomeForDinner() { System.out.println("回家吃饭"); } }
package command.origin; public class Command { private BigDog bigDog; Command(BigDog bigDog) { this.bigDog = bigDog; } public void execute() { bigDog.goHomeForDinner(); } }
package command.origin; public class BigDogMother { public static void main(String[] args) { BigDog bigDog = new BigDog(); Command command1 = new Command(bigDog); Command command2 = new Command(bigDog); Command command3 = new Command(bigDog); command1.execute(); command2.execute(); command3.execute(); } }从上面的代码示例中看到,通过对“请求”也就是“方法调用”的封装,将请求转变成了一个个的命令对象 命令对象本身内部封装了一个命令的执行者 好处是:命令可以进行保存传递了,命令发出者与命令执行者之间完成了解耦,命令发出者甚至不知道具体的执行者到底是谁 而且执行的过程也更加清晰了
意图
将一个请求封装为一个对象,从而使可用不同的请求对客户进行参数化; 对请求排队或者记录请求日志,以及支持可撤销的操作。 别名 行为Action或者事物Transaction 命令模式就是将方法调用这种命令行为或者说请求 进一步的抽象,封装为一个对象结构
上面的“大狗子你妈喊你回家吃饭”的例子只是展示了对于“命令”的一个封装。只是命令模式的一部分。 下面看下命令模式完整的结构 命令角色Command 声明了一个给所有具体命令类的抽象接口 做为抽象角色,通常是接口或者实现类 具体命令角色ConcreteCommand定义一个接受者和行为之间的弱耦合关系,实现execute()方法
负责调用命令接受者的响相应操作 请求者角色Invoker 负责调用命令对象执行命令,相关的方法叫做行动action方法 接受者角色Receiver 负责具体实施和执行一个请求,任何一个类都可以成为接收者 Command角色封装了命令接收者并且内部的执行方法调用命令接收者的方法 也就是一般形如: Command(Receiver receiver){ ...... execute(){ receiver.action(); ... 而Invoker角色接收Command,调用Command的execute方法 通过将“命令”这一行为抽象封装,命令的执行不再是请求者调用被请求者的方法这种强关联 ,而是可以进行分离 分离后,这一命令就可以像普通的对象一样进行参数传递等
结构代码示例
command角色package command; public interface Command { void execute(); }
ConcreateCommand角色
内部拥有命令接收者,内部拥有execute方法
package command; public class ConcreateCommand implements Command { private Receiver receiver; ConcreateCommand(Receiver receiver) { this.receiver = receiver; } @Override public void execute() { receiver.action(); } }Receiver命令接收者,实际执行命令的角色
package command; public class Receiver { public void action(){ System.out.println("command receiver do sth...."); } }命令请求角色Invoker 用于处理命令,调用命令角色执行命令
package command; public class Invoker { private Command command; Invoker(Command command){ this.command = command; } void action(){ command.execute(); } }客户端角色
package command; public class Client { public static void main(String[] args){ Receiver receiver = new Receiver(); Command command = new ConcreateCommand(receiver); Invoker invoker = new Invoker(command); invoker.action(); } }在客户端角色的测试代码中,我们创建了一个命令,指定了接收者(实际执行者) 然后将命令传递给命令请求调用者 虽然最终命令的接收者为receiver,但是很明显如果这个Command是作为参数传递进来的 Client照样能够运行,他只需要借助于Invoker执行命令即可 命令模式关键在于:引入命令类对方法调用这一行为进行封装 命令类使的命令发送者与接收者解耦,命令请求者通过命令类来执行命令接收者的方法 而不在是直接请求命名接收者
代码示例
假设电视机只有三个操作:开机open 关机close和换台change channel。 用户通过遥控器对电视机进行操作。 电视机本身是命令接收者 Receiver 遥控器是请求者角色Invoker 用户是客户端角色Client 需要将用户通过遥控器下发命令的行为抽象为命令类Command Command有开机命令 关机命令和换台命令 命令的执行需要借助于命令接收者 Invoker 调用Command的开机命令 关机命令和换台命令 电视类 Tvpackage command.tv; public class Tv { public void turnOn(){ System.out.println("打开电视"); } public void turnOff(){ System.out.println("关闭电视"); } public void changeChannel(){ System.out.println("换台了"); } }Command接口
package command.tv; public interface Command { void execute(); }三个具体的命令类 内部都保留着执行者,execute方法调用他们的对应方法
package command.tv; public class OpenCommand implements Command { private Tv myTv; OpenCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.turnOn(); } }
package command.tv; public class CloseCommand implements Command { private Tv myTv; CloseCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.turnOff(); } }
package command.tv; public class ChangeChannelCommand implements Command { private Tv myTv; ChangeChannelCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.changeChannel(); } }遥控器Controller 拥有三个命令
package command.tv; public class Controller { private Command openCommand = null; private Command closeCommand = null; private Command changeChannelCommand = null; public Controller(Command on, Command off, Command change) { openCommand = on; closeCommand = off; changeChannelCommand = change; } public void turnOn() { openCommand.execute(); } public void turnOff() { closeCommand.execute(); } public void changeChannel() { changeChannelCommand.execute(); } }用户类User
package command.tv; public class User { public static void main(String[] args) { Tv myTv = new Tv(); OpenCommand openCommand = new OpenCommand(myTv); CloseCommand closeCommand = new CloseCommand(myTv); ChangeChannelCommand changeChannelCommand = new ChangeChannelCommand(myTv); Controller controller = new Controller(openCommand, closeCommand, changeChannelCommand); controller.turnOn(); controller.turnOff(); controller.changeChannel(); } }以上示例将电视机的三种功能开机、关机、换台 抽象为三种命令 一个遥控器在初始化之后,就可以拥有开机、关机、换台的功能,但是却完全不知道底层的实际工作的电视。
命令请求记录
一旦将“发起请求”这一行为进行抽象封装为命令对象 那么“命令”也就具有了一般对象的基本特性,比如,作为参数传递 比如使用容器存放进行存放 比如定义一个ArrayList 用于保存命令 ArrayList请求命令队列
既然可以使用容器存放命令对象,我们可以实现一个命令队列,对命令进行批处理 新增加一个CommandQueue类,内部使用ArrayList存储命令 execute()方法,将内部的请求命令队列全部执行package command; import java.util.ArrayList; public class CommandQueue { private ArrayList同时调整Invoker角色,使之可以获得请求命令队列,并且执行命令请求队列的方法commands = new ArrayList (); public void addCommand(Command command) { commands.add(command); } public void removeCommand(Command command) { commands.remove(command); } //执行队列内所有命令 public void execute() { for (Object command : commands) { ((Command) command).execute(); } } }
package command; public class Invoker { private Command command; Invoker(Command command) { this.command = command; } void action() { command.execute(); } //新增加命令队列 private CommandQueue commandQueue; public Invoker(CommandQueue commandQueue) { this.commandQueue = commandQueue; } /* * 新增加队列批处理方法*/ public void batchAction() { commandQueue.execute(); } }从上面的示意代码可以看得出来,请求队列的关键就是命令类 一旦创建了命令类,就解除了命令请求者与命令接收者之间耦合,就可以把命令当做一个普通对象进行处理,调用他们的execute()执行方法 所谓请求队列不就是使用容器把命令对象保存起来,然后调用他们的execute方法嘛 所以说,命令请求的对象化,可以实现对请求排队或者记录请求日志的目的,就是命令对象的队列