设计模式——命令模式(Command Pattern)
来源:互联网 发布:手机全屏时钟软件 编辑:程序博客网 时间:2024/06/05 14:36
一、命令模式的定义
将“请求”封装成对象,以便使用不同的请求,队列或者日志来参数化其他对象。命令模式也支持可撤销。
命令接口–ICommand
public interface ICommand { public void execute(); public void undo();}
定义统一的接口,所有的命令类都需要实现该接口。
命令类–ConcreteCommand
public class ConcreteCommand implements ICommand { private Reciever reciever; public ConcreteCommand(Reciever receiver) { this.reciever = receiver; } public void execute() { reciever.action(); } public void undo() { reciever.undo(); }}
将某一个具体的“请求”封装成具体的命令类,实现ICommand接口。命令模式的核心目的是将发出请求的对象和执行请求的对象解耦,被解耦的两者之间是通过命令对象进行沟通的,命令对象封装了接受者及其一个或多个动作。比如:开灯命令,可以是LightOnCommand;
命令执行者—Reciever
public class Reciever { private String recieverName; pubic Reciever(String name) { this.recieverName = name; } public void action() { System.out.println("do action..."); } public void undoAction() { System.out.println("undo action ..."); }}
该类是真正执行命令的类,实质上它根本不知道ICommand和Command的存在,所以它只要纯粹完成自己的逻辑。比如一个电灯类(Light),就只需要实现开关电灯的逻辑,并暴露出调用接口就行了。
调用者–Invoker
public class Invoker { private ICommand command; public Invoker() {} public void setCommand(ICommand command) { this.command = command; } // 调用创建时就设置好的命令对象 public invokeCommand() { this.command.execute(); } // 动态调用通过参数传过来的命令对象 public invokeCommand(ICommand command) { command.execute(); } public invokeUndo() { this.command.undo(); }}
命令的调用者,它持有一个或者多个命令对象,在某个时间点调用命令对象的execute方法,将请求付诸执行。
客户端–Client
public class Client { Receiver reciever = new Reciever(); ICommand c = new ConcreteCommand(reciever); Invoker invoker = new Invoker(); invoker.setCommand(c); invoker.invokeCommand(); }
负责创建具体的命令对象,并设置命令的执行者。
宏命令
public class MacroCommand implements ICommand { ICommands[] commands; public MacroCommand(ICommand[] commands) { this.commands = commands; } public void execute() { for (int i=0; i<commands.length; i++) { commands[i].execute(); } } public void undo() { for (int i=0; i<commands.length; i++) { commands[i].undo(); } }}
宏命令的目的是,制造一种新的命令来执行其他一堆命令。这样这个宏命令也是一个实现了ICommand的命令对象,因此可以普通的命令对象一样被执行。
二、一个具体的例子
实现一个控制电灯开关的遥控器,有三个功能:开、关、撤销操作。
/** * 命令真正执行者,对应类图中的Reciever */public class Light { public void onLight() { System.out.println("light on..."); } public void offLight() { System.out.println("light off..."); } public void undo() { System.out.println("light undo...") }}/** * 开灯命令,对应类图中的ConcreteCommand */public class LightOnCommand implements ICommand{ private Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.onLight(); } public void undo() { light.undo(); }}/** * 关灯命令,对应类图中的ConcreteCommand */public class LightOffCommand implements ICommand{ private Light light; public LightOffCommand(Light light) { this.light = light; } public void execute() { light.offLight(); } public void undo() { light.undo(); }}/** * 定义一个空命令对象,它是一个空对象,不做任何事情。可能遥控器在出厂时并没有 * 设置真正有效的命令对象,就可以用空对象代替,以便后续在设置真正的命令对象。 */public class NoCommand implements ICommand { public void execute(){} public void undo(){}}/** * 命令调用者,对应类图中的Invoker */public class LightController { // 持有多个命令对象 private ICommand[] commands; // 记录前一个命令对象,用于“撤销”操作 private ICommand undoCommand; public LightController() { // 用于保存开和关两个命令对象 this.commands = new ICommand[2]; // 初始化 ICommand noCommand = new NoCommand(); for(int i=0; i<2; i++){ command[i] = noCommand; } undoCommand = noCommand; } public void setCommands(ICommand onCommand, ICommand offCommand) { // 第一个元素被设为开命令对象 commands[0] = onCommand; commands[1] = offCommand; } // 按下打开电灯的按钮(请求打开电灯) public void onButtonPushed() { commands[0].execute(); undoCommand = commands[0]; } // 按下打关电灯的按钮(请求关掉电灯) public void offButtonPushed() { commands[1].execute(); undoCommand = commands[1]; } // 按下撤销按钮,由对应的命令对象执行其撤销操作 pubic void undoButtonPushed() { undoCommand.undo(); } // ...其他代码}/** * 创建者,对应类图中的Client类 */public class LightControllerTest { public void static main(String[] args) { // 创建命令对象及其执行者 Light light = new Light(); LightOnCommand onCommand = new LightOnCommand(light); LightOffCommand offCommand = new LightOffCommand(light); LightController lightController = new LightController(); lightController.setCommands(onCommand, offCommand); // 测试 lightController.onButtonPushed(); lightController.offButtonPushed(); lightController.undoButtonPushed(); }}
三、优缺点
优点
- 将发出请求的对象与执行请求的对象解耦。
- 调用者可以执行通过参数传过来的命令对象,因而可以在运行时动态执行命令。
- 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。
- 可以使用宏命令任意组合一些命令,比如实现“一键式家居控制”的需求,就可以将各种家电的命令对象组合成一组。
- 因为将“请求”封装成了对象,使得命令可以被传递,被延时执行。比如,将命令对象发送到远程服务进行执行;也可以将命令对象扔到队列中,由专门的执行命令线程进行执行,执行命令的线程不需要知道具体命令内容,只要是实现了ICommand接口的对象,它都可以执行。
- 因为将“请求”封装成了对象,从而使得可以对请求的执行过程进行一些控制。比如“撤销”操作可以让命令执行失败之后,进行撤销,可用于事务执行。
- 可将命令记录到日志,系统故障后恢复等。
缺点
- 每一个命令都封装为命令对象,导致类太多。
- 对于这一点,个人感觉不需要如此细的粒度,比如LightOnCommand,LightOffCommand,只要设计一个合适的数据结构,通过数据来表达,就可以省掉很多类,代码的灵活和可扩展性也不受影响。
四、应用场景
队列
命令对象扔到队列中,由专门的执行命令线程进行执行,执行命令的线程不需要知道具体命令内容,只要是实现了ICommand接口的对象,它都可执行。因为命令模式下请求者和执行者可以有不同的生命周期,所以命令对象在创建很久以后,仍然可以被执行。另外,可以使用线程池来执行这些命令,只要这些命令实现Runable接口。
日志
因为命令对象可以对命令自身进行管理,比如可以输出命令日志,所以可以用于“容灾”等需要记录所有命令的应用。在这些应用中,命令对象中新增记录日志将命令存储到磁盘中,一旦系统崩溃或死机,重启后可以重新加载命令,再批次执行命令对象的execute方法。比如数据库事务处理,电子表格等应用。
状态条
如果假如系统需要按顺序执行一系列的命令操作,并且需要了解命令执行进度和状态,则可以让每个命令对象都提供一个获取命令执行状态的方法,系统调用该方法显示状态。
需要多级撤销或重做操作
可以在Invoker中用一个栈来存储执行过的命令对象,一旦需要撤销或重做操作,可以通过pop出最近的一个命令对象,并执行其undo方法。
web服务请求处理
将每个web请求封装成一个命令对象。比如struts框架中,就用到了命令模式: Struts框架中,在模型层都要继承一个Action接口,并实现execute方法,其实这个Action就是命令类。为什么Struts会应用命令模式,是因为Struts的核心控制器ActionServlet只有一个,相当于Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的Command。这样,就需要在ActionServlet和模型层之间解耦,而命令模式正好解决这个问题。
- 设计模式——命令模式 (Command Pattern)
- 设计模式(15)——命令模式(Command Pattern)
- 设计模式 —— 命令模式(Command Pattern)
- 设计模式——命令模式(Command Pattern)
- Java设计模式——命令模式(Command Pattern)
- 【设计模式】命令模式(Command Pattern)
- C#设计模式——命令模式(Command Pattern)
- java设计模式——命令模式(Command Pattern)
- 命令设计模式(Command Pattern)
- 【设计模式】行为型模式——命令模式(Command Pattern)
- 我所理解的设计模式(C++实现)——命令模式(Command Pattern)
- 我所理解的设计模式(C++实现)——命令模式(Command Pattern)
- 我所理解的设计模式(C++实现)——命令模式(Command Pattern)
- 设计模式(8)——命令模式(Command Pattern,行为型)
- 我所理解的设计模式(C++实现)——命令模式(Command Pattern)
- 设计模式 - Command Pattern(命令模式)
- 设计模式 - Command Pattern(命令模式)
- 设计模式之命令模式(Command Pattern)
- Tkinter 学习笔记 —— 布局管理
- Golang 死循环的多种写法
- 浅谈JavaScript随机生成数
- linux下IPTABLES配置详解
- linux 不同服务器scp免密远程传输文件实例
- 设计模式——命令模式(Command Pattern)
- 使用Axis2开发webservice服务端接口+axis开发的客户端调用
- Tkinter 学习笔记 —— 标准属性
- C++__自定义数据类型
- 时间合理的分配给自己的提示
- Android Studio中新增整体的activity类文件,重新编译后提示“程序包R不存在”解决
- leetcode 404. Sum of Left Leaves
- Word快捷键大全
- SyncAdapter同步机制