您的位置:首页 > 其它

被说了很多遍的设计模式---命令模式

2017-02-26 19:34 393 查看
[把你的理性思维慢慢变成条件反射]

本文,我们讲介绍命令模式,文章主题结构与上文一致。惯例,先来看看我们示例工程的环境:

操作系统: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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: