设计模式——命令模式(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();        forint 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和模型层之间解耦,而命令模式正好解决这个问题。

阅读全文
0 0
原创粉丝点击