引入
客户端只是想要发出命令或者请求,不关心请求的真正接收者是谁,也不关心具体如何实现,而且同一个请求的动作可以有不同的请求内容,当然具体的处理功能也不一样。
命令模式(Command)
本质:封装请求
命令模式又称为行动(Action)模式或交易(Transaction)模式。
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化;对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。( Command模式可以和备忘录(Memento)模式结合起来,支持Undo的操作。)
命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。
共三大角色:
Ivoker、命令发出者、执行者。
三者之间“不越级交流”
将请求的接收者(Receiver)放到Command的具体子类ConcreteCommand中,当请求到来时(Invoker发出Invoke消息激活Command对象),ConcreteCommand将处理请求交给Receiver对象进行处理。
Command模式将调用操作的对象和知道如何实现该操作的对象解耦。
Invoker对象根本就不知道具体的是哪个对象在处理Excute操作。
在Command下要增加新的处理操作对象很容易,我们可以通过创建新的继承自Command的子类来实现这一点。——符合开放封闭的原则
示意性代码
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
abstract public void Execute();
}
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver):base(receiver) { }
public override void Execute()
{
receiver.Action();
}
}
class Invoker
{
private Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
class Program
{
static void Main(string[] args)
{
Receiver r = new Receiver();
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
Console.Read();
}
}
电脑开机的例子:
- 机箱上的按钮就相当于是命令对象
- 机箱相当于是Invoker
- 主板相当于接收者对象
- 命令对象持有一个接收者对象,就相当于是给机箱的按钮连上了一根连接线。当机箱上的按钮被按下的时候,机箱就把这个命令通过连接线发送出去。
接受者
public interface MainBoardApi
{
public void open();
}
public class GigaMainBoard implements MainBoardApi
{
public void open()
{
System.out.println("技嘉主板现在正在开机,请等候");
System.out.println("接通电源......");
System.out.println("设备检查......");
System.out.println("装载系统......");
System.out.println("机器正常运转起来......");
System.out.println("机器已经正常打开,请操作");
}
}
命令者
public interface Command
{
public void execute();
}
public class OpenCommand implements Command
{
private MainBoardApi mainBoard = null;
public OpenCommand(MainBoardApi mainBoard)
{
this.mainBoard = mainBoard;
}
public void execute()
{
this.mainBoard.open();
}
}
发起者
public class Box
{
private Command openCommand;
public void setOpenCommand(Command command)
{
this.openCommand = command;
}
public void openButtonPressed()
{
//按下按钮,执行命令
openCommand.execute();
}
}
用户
public class Client
{
public static void main(String[] args)
{
MainBoardApi mainBoard = new GigaMainBoard();
OpenCommand openCommand = new OpenCommand(mainBoard);
Box box = new Box();
box.setOpenCommand(openCommand);
box.openButtonPressed();
}
}
使用场景
Command模式可以和备忘录(Memento)模式结合起来,支持Undo的操作
- 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式,把这些请求封装成为命令对象,然后实现把请求队列化
- 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易的实现命令的恢复和重做的功能
- 如果需要支持当系统崩溃时,能把对系统的操作功能重新执行一遍,可以选用命令模式,把这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复回来后,通过日志获取命令列表,从而重新执行一遍功能
- 在需要事务的系统中,可以选用命令模式,命令模式提供了对事务进行建模的方法,命令模式有一个别名就是Transaction。
优缺点
命令允许请求的一方和接收请求的一方能够独立演化,从而有以下的优点:
- 命令模式使新的命令很容易地被加入到系统里。
- 允许接收请求的一方决定是否要否决(Veto)请求。
- 能较容易地设计一个命令队列。
- 可以容易地实现对请求的Undo和Redo。
- 在需要的情况下,可以较容易地将命令记入日志。
- 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
- 命令类与其他任何别的类一样,可以修改和推广。
利用Command模式实现Undo/Redo
简单计算器,允许redo和undo
package command;
public abstract class Command {
abstract public void Execute();
abstract public void UnExecute();
}
package command;
public class Calculator {
private int total = 0;
public void Operation(char op, int operand) {
switch (op) {
case '+':
total += operand;
break;
case '-':
total -= operand;
break;
case '*':
total *= operand;
break;
case '/':
total /= operand;
break;
}
System.out.println("Total=" + total +"("+ op + " "+operand+")");
}
}
package command;
public class CalculatorCommand extends Command {
char op;
int operand;
Calculator calculator;
public CalculatorCommand(Calculator calculator, char op, int operand) {
this.calculator = calculator;
this.op = op;
this.operand = operand;
}
public char getOp() {
return op;
}
public void setOp(char op) {
this.op = op;
}
public int getOperand() {
return operand;
}
public void setOperand(int operand) {
this.operand = operand;
}
public Calculator getCalculator() {
return calculator;
}
public void setCalculator(Calculator calculator) {
this.calculator = calculator;
}
@Override
public void Execute() {
calculator.Operation(op, operand);
}
@Override
public void UnExecute() {
calculator.Operation(Undo(op), operand);
}
private char Undo(char op) {
char undo = ' ';
switch (op) {
case '+':
undo = '-';
break;
case '-':
undo = '+';
break;
case '*':
undo = '/';
break;
case '/':
undo = '*';
break;
}
return undo;
}
}
package command;
import java.util.ArrayList;
public class User {
private Calculator calculator = new Calculator();
private ArrayList<Command> commands = new ArrayList<Command>();
private int current = 0;
public void Redo(int levels) {
System.out.println("Redo" + levels);
for (int i = 0; i < levels; i++)
if (current < commands.size() - 1)
((Command) commands.get(current++)).Execute();
}
public void Undo(int levels) {
System.out.println("Undo" + levels);
for (int i = 0; i < levels; i++)
if (current > 0)
((Command) commands.get(--current)).UnExecute();
}
public void Compute(char op, int operand) {
Command command = new CalculatorCommand(calculator, op, operand);
command.Execute();
commands.add(command);
current++;
}
}
package command;
public class Main {
public static void main(String[] args) {
User user = new User();
user.Compute('+', 100);
user.Compute('-', 50);
user.Compute('*', 10);
user.Compute('/', 2);
user.Undo(4);
user.Redo(3);
}
}