一、 Command模式定义:
Command(CoR)模式也叫命令模式,是由GoF提出的23种软件设计模式的一种。
Command模式是行为模式之一,Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数。
将一个请求封装为一个对象,从而使你不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
二、 模式解说
Commad模式是一种对象行为模式,它可以对发送者(sender)和接收者(receiver)完全解耦(decoupling)。("发送者" 是请求操作的对象,"接收者" 是接收请求并执行某操作的对象。有了 "解耦",发送者对接收者的接口一无所知。)这里,"请求"(request)这个术语指的是要被执行的命令。Command模式还让我们可以对 "何时" 以及 "如何" 完成请求进行改变。因此,Command模式为我们提供了灵活性和可扩展性。
三、Command模式参与者描述
1、客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
2、命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
3、具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执方法。
4、请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
5、接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
四、怎么使用?
1) 定义一个Command接口,接口中有一个统一的方法,这就是将请求/命令封装为对象。
2) 定义具体不同命令类ConcreteCommand实现Command接口。
3) 定义一个命令的调用角色Invoker。
4) 定义一个命令执行状态的接收者Receiver(非必须)。
五、Command模式的应用场景
在面向对象的程序设计中,一个对象调用另一个对象,一般情况下的调用过程是:创建目标对象实例;设置调用参数;调用目标对象的方法。
但在有些情况下有必要使用一个专门的类对这种调用过程加以封装,我们把这种专门的类称作command类。
- 整个调用过程比较繁杂,或者存在多处这种调用。这时,使用Command类对该调用加以封装,便于功能的再利用。
- 调用前后需要对调用参数进行某些处理。
- 调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等。
一般来说,Command模式通常可应用到以下场景:
Multi-level undo(多级undo操作)
如果系统需要实现多级回退操作,这时如果所有用户的操作都以command对象的形式实现,系统可以简单地用stack来保存最近执行的命令,如果用户需要执行undo操作,系统只需简单地popup一个最近的command对象然后执行它的undo()方法既可。
Transactional behavior(原子事务行为)
借助command模式,可以简单地实现一个具有原子事务的行为。当一个事务失败时,往往需要回退到执行前的状态,可以借助command对象保存这种状态,简单地处理回退操作。
Progress bars(状态条)
假如系统需要按顺序执行一系列的命令操作,如果每个command对象都提供一个getEstimatedDuration()方法,那么系统可以简单地评估执行状态并显示出合适的状态条。
Wizards(导航)
通常一个使用多个wizard页面来共同完成一个简单动作。一个自然的方法是使用一个command对象来封装wizard过程,该command对象在第一个wizard页面显示时被创建,每个wizard页面接收用户输入并设置到该command对象中,当最后一个wizard页面用户按下“Finish”按钮时,可以简单地触发一个事件调用execute()方法执行整个动作。通过这种方法,command类不包含任何跟用户界面有关的代码,可以分离用户界面与具体的处理逻辑。
GUI buttons and menu items(GUI按钮与菜单条等等)
Swing系统里,用户可以通过工具条按钮,菜单按钮执行命令,可以用command对象来封装命令的执行。
Thread pools(线程池)
通常一个典型的线程池实现类可能有一个名为addTask()的public方法,用来添加一项工作任务到任务队列中。该任务队列中的所有任务可以用command对象来封装,通常这些command对象会实现一个通用的接口比如java.lang.Runnable。
Macro recording(宏纪录)
可以用command对象来封装用户的一个操作,这样系统可以简单地通过队列保存一系列的command对象的状态就可以记录用户的连续操作。这样通过执行队列中的command对象,就可以完成“Play back”操作了。
Networking
通过网络发送command命令到其他机器上运行。
Parallel Processing(并发处理)
当一个调用共享某个资源并被多个线程并发处理时。
等等
六:Java实例1
Client :开关灯的人
Invoker :遥控器
Command :电信号
具体命令 :遥控器上的按键对应的不同的电信号(ConcreteCommand)
Receiver :电灯
a. receiver包----接收者包,在这里,light就是接收者,由它执行最后的动作。
Java代码
- package reciever;
-
- public class Light {
-
-
-
- public Light(){}
-
- public void onlight(){
- System.out.println("The light is open!") ;
- }
- public void offlight(){
- System.out.println("The light is close!") ;
- }
- }
b. command包----电信号。
命令对象的execute方法执行了接受者(Light)的一个或者几个action,在由调用者control确定什么时候执行execute。
Java代码
- package command;
-
-
-
-
- public interface ICommand {
- void execute() ;
- void undo() ;
- }
Java代码
- package command;
-
- import reciever.Light;
-
-
-
-
- public class LightOffCommand implements ICommand {
- private Light light ;
-
- public LightOffCommand(Light light){
- this.light = light ;
- }
-
- public void execute() {
- this.light.offlight() ;
- }
-
- public void undo() {
- this.light.onlight() ;
- }
-
- }
Java代码
- package command;
-
- import reciever.Light;
-
-
-
-
- public class LightOnCommand implements ICommand {
-
- private Light light ;
-
- public LightOnCommand(Light light){
- this.light = light ;
- }
-
- public void execute() {
- this.light.onlight() ;
- }
-
- public void undo() {
- this.light.offlight() ;
- }
-
- }
Java代码
- package command;
-
-
-
- public class NoCommand implements ICommand {
-
- public void execute() {
- }
-
- public void undo() {
- }
-
- }
c. requester---指遥控器。
通过setCommand()方法来获取ICommand,再由子类“决定”需要的哪个Command。通过几个Button方法,来决定什么时候执行相应的事件。接受者作出相应地反映(灯亮,灯灭)。这里即可以体会到,遥控器和灯是解耦的。
- package requester;
-
- import command.ICommand;
- import command.NoCommand;
-
-
-
-
- public class RemoteControl {
-
- private ICommand oncommand ,offcommand ,undocommand ;
- public RemoteControl(){
- this.oncommand = new NoCommand() ;
- this.offcommand = new NoCommand() ;
- this.undocommand = new NoCommand() ;
- }
-
- public void setCommand(ICommand oncommand,ICommand offcommand){
- this.oncommand = oncommand ;
- this.offcommand = offcommand ;
- }
-
- public void onButtonWasPressed(){
- oncommand.execute() ;
- this.undocommand = this.oncommand ;
- }
-
- public void offButtonWasPressed(){
- offcommand.execute() ;
- this.undocommand = this.offcommand ;
- }
-
- public void undo(){
- this.undocommand.undo() ;
- }
- }
d. 测试类--人
Java代码
- package test;
-
- import command.ICommand;
- import command.LightOffCommand;
- import command.LightOnCommand;
-
- import reciever.Light;
- import requester.RemoteControl;
-
-
-
-
- public class ControlTest {
- public static void main(String[] args){
-
- Light light = new Light() ;
-
-
- ICommand lightOff = new LightOffCommand(light) ;
- ICommand lightOn = new LightOnCommand(light) ;
-
-
- RemoteControl control = new RemoteControl() ;
- control.setCommand(lightOn , lightOff) ;
- control.onButtonWasPressed() ;
- control.offButtonWasPressed() ;
-
- control.undo() ;
-
-
- }
- }
- e.控制台输出:
The light is open!
The light is close!
The light is open!
Java实例2
使用Command模式实现一个简单的execute/undo操作。
在该范例中,我们有一个简单的操作:对字符串做append操作,这个操作由Receiver类实现;另外,我们需要记录操作历史,并能简单加以回退(undo),所以我们采用Command模式实现。
文件一览:
Client : 测试类
Command : Command抽象类
UndoableCommand: 支持undo操作的Command抽象类,该类是Command类的子类
ConcreteCommand: 具体的UndoableCommand实现类,该类继承UndoableCommand类,所以支持undo操作
CommandManager: Command管理类。该类使用Stack来管理执行过的Command对象,并提供executeCommand()与undoCommand()方法
Receiver: 执行任务的目标类
Invoker: 这个类在我们的例里没有被用到,但我们仍给出了它的一个参考实现,不过用注释表示它不可用
代码:
import java.util.Stack;
public class Client {
/**
* Test Command Pattern
*
*/
public static void main(String[] args) {
CommandManager commandMgr = new CommandManager();
Receiver receiver = new Receiver();
System.out.println("--- execute command ---");
Command commandAaa = new ConcreteCommand(receiver, "aaa");
commandMgr.executeCommand(commandAaa);
Command commandBbb = new ConcreteCommand(receiver, "bbb");
commandMgr.executeCommand(commandBbb);
Command commandCcc = new ConcreteCommand(receiver, "ccc");
commandMgr.executeCommand(commandCcc);
Command commandDdd = new ConcreteCommand(receiver, "ddd");
commandMgr.executeCommand(commandDdd);
System.out.println(receiver.getData());
System.out.println("-- undo ---");
commandMgr.undoCommand();
commandMgr.undoCommand();
System.out.println(receiver.getData());
}
}
/**
* Command
* abstract command class
*
*/
abstract class Command {
protected Receiver receiver;
protected String param;
public Command(Receiver receiver, String expr) {
this.receiver = receiver;
this.param = expr;
}
abstract public void execute();
}
/**
* UndoableCommand
* abstract undo supportable command class which extends from Command class
*
*/
abstract class UndoableCommand extends Command {
public UndoableCommand(Receiver receiver, String expr) {
super(receiver, expr);
}
abstract public void undo();
}
/**
* ConcreteCommand
* concrete command class which extends from UndoableCommand
*
*/
class ConcreteCommand extends UndoableCommand {
private String previousData = null;
public ConcreteCommand(Receiver receiver, String expr) {
super(receiver, expr);
}
@Override
public void execute() {
previousData = receiver.getData();
receiver.append(this.param);
}
@Override
public void undo() {
receiver.setData(previousData);
}
}
/**
* CommandManager
* Command Manager class which stack the exe
*
*/
class CommandManager {
private Stack commandStack = new Stack();
public void executeCommand(Command cmd) {
cmd.execute();
if (cmd instanceof UndoableCommand) {
commandStack.push(cmd);
}
}
public void undoCommand() {
if (commandStack.size() > 0) {
UndoableCommand cmd = (UndoableCommand) commandStack.pop();
cmd.undo();
} else {
throw new UnsupportedOperationException("");
}
}
}
/**
* Receiver
* target object
*
*/
class Receiver {
private String data = "";
public void append(String expr) {
data += expr;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
/**
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
command.execute();
}
}
*/
执行Client,输出结果:
C:\Command>javac *.java
C:\Command>java Client
--- execute command ---
aaabbbcccddd
-- undo ---
aaabbb
C:\Command>
我们可以看到,使用Command模式非常简单地就实现了undo操作。
七: 总结
命令模式的优点:降低Client和命令接受者的耦合,使命令请求和命令执行的对象分割,便于修改和扩张,便于聚合多个命令。
命令模式的缺点:造成出现过多的具体命令类,太多文件。
参考资料:
阅读(923) | 评论(0) | 转发(0) |