[把你的理性思维慢慢变成条件反射]
本文,我们讲介绍命令模式,文章主题结构与上文一致。惯例,先来看看我们示例工程的环境:
操作系统:win7 x64
其他软件:eclipse mars,jdk8
-------------------------------------------------------------------------------------------------------------------------------------
经典问题:
(发布与订阅功能的)转发器,功能开关,等等。
思路分析:
要点一:转发器负责命令的接受与发布,并保证命令被调用。
要点二:转发器解耦双方责任关系。具有较高的灵活性。
示例工程:
错误写法:
创建CommandExecute.java文件,具体内容如下:
package com.csdn.ingo.gof_Command;
public class CommandExecute {
public void commandA(){
System.out.println("command A was executed");
}
public void commandB(){
System.out.println("command B was executed");
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Command;
public class Window {
public static void main(String[] args) {
CommandExecute cmd = new CommandExecute();
cmd.commandA();
cmd.commandB();
}
}
错误原因:
客户端与被调用方的代码过于耦合,对于后期的维护与扩展极为不利。并且,客户端无法灵活的执行相关功能,即无转接器的功能。
推荐写法:
创建Command.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver){
this.receiver = receiver;
}
public abstract void excuteCommand();
}
创建ConcreteCommandA.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public class ConcreteCommandA extends Command{
public ConcreteCommandA(Receiver receiver) {
super(receiver);
}
@Override
public void excuteCommand() {
receiver.commandA();
}
}
创建ConcreteCommandB.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public class ConcreteCommandB extends Command{
public ConcreteCommandB(Receiver receiver) {
super(receiver);
}
@Override
public void excuteCommand() {
receiver.commandB();
}
}
创建Invoker.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public class Invoker {
private Command command;
public void setOrder(Command command){
this.command = command;
}
public void notifya(){
command.excuteCommand();
}
}
创建Receiver.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public class Receiver {
public void commandA(){
System.out.println("command A was executed");
}
public void commandB(){
System.out.println("command B was executed");
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.one;
public class Window {
public static void main(String[] args) {
Receiver boy = new Receiver();
Command cmdA = new ConcreteCommandA(boy);
Command cmdB = new ConcreteCommandB(boy);
Invoker ivk = new Invoker();
ivk.setOrder(cmdA);
ivk.notifya();
ivk.setOrder(cmdB);
ivk.notifya();
}
}
推荐原因:
上文的推荐代码将客户端的命令在调度真正的receiver之间进行了封装。由此将命令的发起与执行进行分割。使得客户端不必知道命令的实际接口,及接口内的执行细节。每一个命令类对应一个命令处理器,对于客户端通过注入的命令的参数不同,其对应的实现亦不同。从而实现“封装”。
扩展实现:
创建Command.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
public abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver){
this.receiver = receiver;
}
public abstract void excuteCommand();
}
创建ConcreteCommandA.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
public class ConcreteCommandA extends Command{
public ConcreteCommandA(Receiver receiver) {
super(receiver);
}
@Override
public void excuteCommand() {
receiver.commandA();
}
}
创建ConcreteCommandB.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
public class ConcreteCommandB extends Command{
public ConcreteCommandB(Receiver receiver) {
super(receiver);
}
@Override
public void excuteCommand() {
receiver.commandB();
}
}
创建Invoker.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Invoker {
private List<Command> orders = new ArrayList<Command>();
private Command command;
public void setOrder(Command command){
if(command.getClass().getName().equals(ConcreteCommandA.class.getName())){
System.out.println("Command A is closed");
}else{
orders.add(command);
System.out.println("ADD Command B:"+command.getClass().getName()+",Time:"+new Date());
}
}
public void cancelOrder(Command command){
orders.remove(command);
System.out.println("REMOVE Command:"+command.toString()+",Time:"+new Date());
}
public void notifya(){
for(Command c:orders){
c.excuteCommand();
}
}
}
创建Receiver.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
public class Receiver {
public void commandA(){
System.out.println("Command A was executed");
}
public void commandB(){
System.out.println("Command B was executed");
}
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_Command.two;
public class Window {
public static void main(String[] args) {
Receiver boy = new Receiver();
Command cmdA = new ConcreteCommandA(boy);
Command cmdB = new ConcreteCommandB(boy);
Invoker ivk = new Invoker();
ivk.setOrder(cmdA);
ivk.setOrder(cmdA);
ivk.setOrder(cmdB);
ivk.notifya();
}
}
特别提醒:
上文设计实现的undo操作,只能实现撤销命令,而不能实现有序撤销。举个栗子:如果客户端为计算器,那么此实现不能实现撤销运算,而是仅代表了正式提交前的删除某个子运算而已。
模式的再扩展:
在操作系统中的日志文件,当前流行的内存数据库,MQ组件,CQRS系统设计中等等,其都提供了快照备份的能力,此处功能实现方式有将每次的命令变化都写入备份文件,后续恢复时,再根据该命令的操作历史恢复到之前的状态。关于此功能的实现代码,请各位看官在参考如Redis日志文件的生成策略之后,结合实际需要,自行学习。
模式总结:
命令模式结构图:
命令模式:
讲一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。
组成部分:
Invoker(调用者):客户端通过该对象来调用命令。其在设计时,不需要确定命令接收方,而只需要与Command抽象命令类之间关联即可。在程序运行时,在调用具体的注入的对象的方法。
Command(抽象命令类):Command类一般是一个抽象类或者接口,在其中声明了用于执行请求的execute()。这些方法将实际效用接收方的相关方法。
Receiver(接收者):命令的实际执行者,负责具体业务处理操作等。
ConcreteCommand(具体命令类):其实现了Command类中声明的方法,其对应一个具体的接受方对象,并且其中包含了对接收方方法的调用过程。
反思:
应用场景:
- 需要将命令发起方与接收方进行关系解耦。使其具有封装性,隔离性。
- 需要保持Invoker始终处于生存状态,即,命令的发起方可以随时结束生命周期,而Invoker调用者,始终保持活动。
- 在扩展模式下,需要记录系统命令操作日志。
优点:
- 有效降低了命令客户端与命令接收方的耦合度。使得双方相互不存在直接关联,方便后续的维护与扩展。
- 对于新的命令的加入对旧的命令实现过程不存在任何影响。满足了“开闭原则”。
- 通过记录命令的调用过程,可以实现系统的撤销与恢复功能。
缺点:
- 具体的命令类需要对应一个具体的命令接收方,因此,在实际使用时,需要考虑业务复杂度,以免造成ConcreteCommand类的泛滥。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被说了很多遍的设计模式---命令模式 结束
参考资料:
图书:《大话设计模式》
其他博文:http://blog.csdn.NET/lovelion/article/details/7563445