1.命令模式
命令模式(Command)属于对象的行为模式。
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
2.命令模式的类图
参与的角色:
- Client:客户角色,创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
- Command:命令角色:抽象命令接口,定义了命令的行为。
- ConcreteCommand:具体命令。定义一个接受者和行为之间的弱耦合;实现执行方法execute,负责调用接收者的相应操作。
- Invoker:请求者角色。负责调用命令对象执行请求,相关方法叫做行动方法action。
- Receiver:接收者角色。负责具体实施和执行一个请求。
3.示例代码
Client(客户端)
public class Client
{
public static void main(String[] args)
{
/**
* 命令使用顺序,创建接收者,如果没有接收者(可以没有接收者),创建命令,请求者,然后调用请求者执行行动.
*/
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker( command );
invoker.action();
}
}
Command(抽象命令)
public interface Command {
void execute();
}
ConcreteCommand(具体命令)
public class ConcreteCommand implements Command
{
public ConcreteCommand(Receiver receiver)
{
this.receiver = receiver;
}
/**
* 执行方法
*/
public void execute()
{
receiver.action();
}
private Receiver receiver;
}
Invoker(请求者)
public class Invoker
{
/**
* 构造
* @param command
*/
public Invoker(Command command)
{
this.command = command;
}
public void action()
{
command.execute();
}
private Command command;
}
Receiver(接收者),命令的具体执行者
public class Receiver
{
public Receiver()
{
}
/**
* 行动方法
*/
public void action()
{
System.out.println("Action has been taken.");
}
}
4.一个例子:操作录音机
系统需求
有一种录音机,录音机有播音(play)、倒带(rewind)和停止(功能)。用Java语言模拟这个系统。
录音机的键盘是请求者(invoker)角色;使用者是客户;而录音机是接收者角色。提炼一个抽象类Command扮演抽命令,此系统有PlayCommand,StopCommand和RewindCommand3个具体命令。
系统类图
如下:
代码实现
Keypad(请求者)
/**
* 接收者角色
*/
public class Keypad
{
private Command playCmd;
private Command rewindCmd;
private Command stopCmd;
/**
* 构造函数,参数是命令
* @param play
* @param stop
* @param rewind
*/
public Keypad(Command play, Command stop, Command rewind)
{
// concrete Command registers itself with the invoker
playCmd = play;
stopCmd = stop;
rewindCmd = rewind;
}
//执行方法
public void play()
{
playCmd.execute();
}
public void stop()
{
stopCmd.execute();
}
public void rewind()
{
rewindCmd.execute();
}
}
Command(抽象命令)
/**
* 抽象命令
*/
public interface Command
{
public abstract void execute ( );
}
PlayCommand(具体命令)
public class PlayCommand implements Command
{
private AudioPlayer myAudio;
public PlayCommand ( AudioPlayer a)
{
myAudio = a;
}
public void execute( )
{
myAudio.play();
}
}
StopCommand(具体命令)
public class StopCommand implements Command
{
private AudioPlayer myAudio;
public StopCommand ( AudioPlayer a)
{
myAudio = a;
}
public void execute( )
{
myAudio.stop();
}
}
RewindCommand(具体命令)
public class RewindCommand implements Command
{
private AudioPlayer myAudio;
public RewindCommand ( AudioPlayer a)
{
myAudio = a;
}
public void execute()
{
myAudio.rewind();
}
}
AudioPlayer(接收者)
public class AudioPlayer
{
public void play( )
{
System.out.println("Playing...");
}
public void rewind( )
{
System.out.println("Rewinding...");
}
public void stop()
{
System.out.println("Stopped.");
}
}
Client(客户端)
/**
* 客户端
*/
public class Client
{
/**
*键盘,持有Invoker角色
*/
private static Keypad keypad ;
/**
*接受者角色
*/
private static AudioPlayer myAudio = new AudioPlayer();
public static void main(String[] args)
{
test1();
}
private static void test1()
{
Command play = new PlayCommand(myAudio);
Command stop = new StopCommand(myAudio);
Command rewind = new RewindCommand(myAudio);
keypad = new Keypad(play, stop, rewind);
keypad.play();
keypad.stop();
keypad.rewind();
keypad.stop();
keypad.play();
keypad.stop();
}
}
运行效果:
Playing...
Stopped.
Rewinding...
Stopped.
Playing...
Stopped.
Stopped.
Rewinding...
Stopped.
Playing...
Stopped.
添加宏命令
设想:添加一个宏命令键,把设置好的命令按顺序执行。只需要增加一个宏命令,而不用修改接收者。增加宏命令,使用组合模式。
MacroCommand(宏命令的接口)继承与Command,增加了操作子命令的接口。
public interface MacroCommand extends Command
{
void execute();
void remove(Command toRemove);
void add(Command toAdd);
}
MacroAudioCommand(宏命令实现)
public class MacroAudioCommand implements MacroCommand
{
private List<Command> commandList = new ArrayList<Command>();
public void add(Command toAdd)
{
commandList.add(toAdd);
}
public void remove(int index)
{
commandList.remove(index);
}
public void remove(Command toRemove)
{
commandList.remove(toRemove);
}
public void execute()
{
Command nextCommand;
/**
* 子命令顺序执行
*/
for (int i=0; i < commandList.size(); i++)
{
nextCommand = commandList.get(i);
nextCommand.execute();
}
}
}
Client(客户端)
public class Client {
/**
* 键盘,持有Invoker角色
*/
private static Keypad keypad;
/**
* 接受者角色
*/
private static AudioPlayer myAudio = new AudioPlayer();
public static void main(String[] args) {
testMacroCommand();
}
private static void test1() {
Command play = new PlayCommand(myAudio);
Command stop = new StopCommand(myAudio);
Command rewind = new RewindCommand(myAudio);
keypad = new Keypad(play, stop, rewind);
keypad.play();
keypad.stop();
keypad.rewind();
keypad.stop();
keypad.play();
keypad.stop();
}
private static void testMacroCommand() {
Command play = new PlayCommand(myAudio);
Command stop = new StopCommand(myAudio);
Command rewind = new RewindCommand(myAudio);
MacroCommand macro = new MacroAudioCommand();
macro.add(play);
macro.add(stop);
macro.add(rewind);
macro.add(play);
macro.add(stop);
macro.add(rewind);
macro.execute();
}
}
运行效果和test1一致。
5.让命令模式支持undo和redo
要让命令模式支持撤销和恢复功能,首先,抽象命令需要增加接口undo
public interface Command
{
public abstract void execute ( );
public abstract void undo ( );
}
然后,具体命令类需要存储状态信息,包括:
(1)接收者对象实际上实施请求所代表的的操作;
(2)对接收者对象所作出的操作所需要的参数;
(3)接收者类的最初的状态。接收者必须提供方法,使命令类可以控制接收者恢复原有状态。
最后,系统需要存储被执行过命令,当undo时,逆序遍历命令,执行undo,当redo时,顺序遍历命令,执行execute。
6.在什么情况下使用命令模式
- 使用命令模式作为callback在面向对象系统中的替代。callback讲的是:在面向过程编程中先将一个函数登记上,然后在以后调用此函数。
- 需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求者可以有不同的生命周期。
- 系统需要支持命令的撤销。
- 系统需要支持命令的redo,一个系统将所有的数据更新写到日志里,当系统崩溃时,可以读取日志,添加命令,然后redo,恢复数据。
7.命令模式的优点和缺点
优点:
- 命令模式把请求一个操作的对象(客户端)与知道怎么执行一个操作的对象(接收者)分开。
- 命令类容易修改和扩展。
- 容易实现合成命令
缺点:具体命令类可能有很多。