java/android 设计模式学习笔记(16)---命令模式

   这篇博客我们来介绍一下命令模式(Command Pattern),它是行为型设计模式之一。命令模式相对于其他的设计模式更为灵活多变,我们接触比较多的命令模式个例无非就是程序菜单命令,如在操作系统中,我们点击关机命令,系统就会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机等,对于这一系列的命令,用户不用去管,用户只需点击系统的关机按钮即可完成如上一系列的命令。而我们的命令模式其实也与之相同,将一系列的方法调用封装,用户只需调用一个方法执行,那么所有的这些被封装的方法就会被挨个执行调用。

  转载请注明出处:http://blog.csdn.net/self_study/article/details/52091539

  PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

  java/android 设计模式学习笔记目录

特点

  将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

  命令模式的使用场景:

  • 需要抽象出待执行的动作,然后以参数的形式提供出来—类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品;
  • 在不同的时刻指定、排列和执行请求,一个命令对象可以有与初始请求无关的生存期;
  • 需要支持取消操作;
  • 支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍;
  • 需要支持事务操作;
  • 系统需要将一组操作组合在一起,即支持宏命令。

  wiki 上列出的具体使用场景:

  • GUI buttons and menu items
  • 在 Java Swing 和 Delphi 语言中,一个 Action 是一个命令,除了执行预定的命令之外,一个 Action 可能会有一个相关联的图标,键盘快捷键,气泡提示文本等等。一个工具栏按钮或者菜单的元素可能完全用一个 Action 对象进行初始化。

  • Macro recording
  • 如果所有的用户动作都代表了一个个命令对象,那么一个程序就能够轻易的保存一系列的命令对象,之后能够将这一系列的命令重新执行一遍来实现一个回播的动作。如果程序中嵌入了脚本引擎,那么每个命令对象都能够实现 toScript() 方法,用户的动作也能够被轻松的保存为脚本对象。

  • Mobile Code
  • 类似于 Java 这种能够通过 URLClassloders 将代码变成流从一个地方传输到另一个地方的语言,并且代码库中的命令使新的行为能够被传递到远程位置(EJB Command,Master Worker模式)。

  • Multi-level undo
  • 如果程序中所有的用户动作都被实现成了命令对象,程序就能够保存最近被执行的命令对象,当用户想要撤销某些命令时,程序就可以简单的 pop 出最近的命令并且执行他的 undo 方法。

  • Networking
  • 能够将所有的命令对象通过网络传输到另外一台设备上去执行,比如端游中的用户操作等。

  • Parallel Processing
  • 所有的命令都被写成了共享资源中的任务并且并发的被很多线程同时执行(在远程机器上这种变体可能这个会被称为 Master/Worker 模式)。

  • Progress bars
  • 我们假定程序有一系列需要按顺序执行的命令,如果每个命令对象都有一个 getEstimatedDuration() 方法,那么程序就可以轻松估算出整体的执行时间,然后展示一个有意义的进度条来反应当前所有任务的执行程度。

  • Thread pools
  • 一个具有代表性的线程池可能会有一个 public 的 addTask() 方法用来添加一个工作任务到内部的等待队列中,这个线程池中会有一系列的线程用来执行队列中的一系列命令对象。普遍的,这些对象会实现一个通用的接口,比如 Runnable 等,用来允许线程池执行这些命令,虽然这个线程池类并不了解它会被用来处理具体的什么任务。

  • Transactional behavior
  • 类似于 undo 操作,一个数据库引擎或者软件安装器可能会维护一个已经执行或者将要执行的操作列表,如果其中的一个失败了,所有其他的都会被还原或者抛弃(通常被称为 rollback,回滚)。举个例子,如果相关联的两个数据库表必须要更新,并且第二个更新失败,这个事务将会被回滚,所以第一个表的更新也会被抛弃。

  • Wizards
  • 向导页是用户在点击最后一页”结束”按钮时候弹出来的几页配置页(配置用户的使用习惯等),在这种情况下,一个通常分离用户操作代码和程序代码的方法就是使用命令对象实现向导功能。这个命令对象会在向导页第一次展示的时候被创建,每个向导页将它们自己的 GUI 变化保存在一个命令对象中,所以这个对象被定位为用户的进一步操作。“结束”动作简单的触发了一个 excete() 动作,这样一来,这个命令类将会达到预期的效果。

UML类图

  我们来看看命令模式的 uml 类图:

  

命令模式的角色介绍:

  • Receiver:接收者角色
  • 该类负责具体实施或执行一个请求,说的通俗一点就是,执行具体逻辑的角色,以上面说到的“关机”操作命令为例,其接收者角色就是真正执行各项关机逻辑的底层代码。任何一个类都能成为一个接收者,而接收者类中封装具体操作逻辑的方法我们则称为行动方法;

  • Command :命令接口
  • 定义所有具体命令类基本行为的抽象接口;

  • ConreteCommand:具体命令角色
  • 该类实现了 Command 接口,在 execute 方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦合。而 execute 则通常称为执行方法,如上面提到的“关机”操作实现,具体可能还包含很多相关的操作,比如保存数据、关闭文件、结束进程等,如果将这一些列的具体逻辑处理看作接收者,那么调用这些具体逻辑的方法就可以看作是执行方法;

  • Invoker :请求者角色
  • 该类的职责就是调用命令对象执行具体的请求,相关的方法我们称为行动方法,“关机”命令为例,关机这个命令一般就对应着一个关机方法,执行关机命令就相当于由这个关机方法去执行具体的逻辑,这个关机方法就可以看作是请求者;

  • Client:客户端角色

  由此我们可以写出命令模式的通用代码:

Receiver.class 具体逻辑执行者

public class Receiver {
    public void action() {
        System.out.print("执行具体的操作");
    }
}

Command.class 抽象命令类

public interface Command {
    void execute();
}

ConcreteCommand.clas 具体命令类

public class ConcreteCommand implements Command {

    private Receiver receiver;

    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }

    @Override
    public void execute() {
        receiver.action();
    }
}

Invoker.class 请求者

public class Invoker {

    private Command command;

    public Invoker(Command command) {
        this.command = command;
    }

    public void action() {
        command.execute();
    }
}

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();
    }
}

最后也能通过 Invoker 类调用到真正的 Receiver 执行逻辑了。

示例与源码

  这就以一个简单的控制电灯亮灭和门开关的情景为例:

Light.class 和 Door.class 实际控制类

public class Light {
    public void lightOn() {
        System.out.print("light on\n");
    }

    public void lightOff() {
        System.out.print("light off\n");
    }
}
public class Door {
    public void doorOpen() {
        System.out.print("door open\n");
    }

    public void doorClose() {
        System.out.print("door close\n");
    }
}

然后是电灯的控制类:

LightOnCommand.class 和 LightOffCommand.class

public class LightOnCommand  implements Command{
    public Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.lightOn();
    }
}
public class LightOffCommand implements Command{
    public Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.lightOff();
    }
}

门的相关控制类:

DoorOpenCommand.class 和 DoorCloseCommand.class

public class DoorOpenCommand implements Command{

    public Door door;

    public DoorOpenCommand(Door door) {
        this.door = door;
    }

    @Override
    public void execute() {
        door.doorOpen();
    }
}
public class DoorCloseCommand implements Command{

    public Door door;

    public DoorCloseCommand(Door door) {
        this.door = door;
    }

    @Override
    public void execute() {
        door.doorClose();
    }
}

然后是一个无操作默认命令类:

NoCommand.class

public class NoCommand implements Command{
    @Override
    public void execute() {
    }
}

最后是控制类:

Controller.class

public class Controller {
    private Command[] onCommands;
    private Command[] offCommands;

    public Controller() {
        onCommands = new Command[2];
        offCommands = new Command[2];

        Command noCommand = new NoCommand();
        onCommands[0] = noCommand;
        onCommands[1] = noCommand;
        offCommands[0] = noCommand;
        offCommands[1] = noCommand;
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void onCommand(int slot) {
        onCommands[slot].execute();
    }

    public void offCommand(int slot) {
        offCommands[slot].execute();
    }
}

测试代码

Light light = new Light();
Door door = new Door();

LightOnCommand lightOnCommand = new LightOnCommand(light);
LightOffCommand lightOffCommand = new LightOffCommand(light);

DoorOpenCommand doorOpenCommand = new DoorOpenCommand(door);
DoorCloseCommand doorCloseCommand = new DoorCloseCommand(door);

Controller controller = new Controller();
controller.setCommand(0, lightOnCommand, lightOffCommand);
controller.setCommand(1, doorOpenCommand, doorCloseCommand);

controller.onCommand(0);
controller.offCommand(0);
controller.onCommand(1);
controller.offCommand(1);

结果:

这样就实现了对 Light 和 Door 的控制了,其实这个例子只是实现了对命令模式的基本框架而已,命令模式的用处其实在于它的日志和回滚撤销功能,每次执行命令的时候都打印出相应的关键日志,或者每次执行后都将这个命令保存进列表中并且每个命令实现一个 undo 方法,以便可以进行回滚。另外,也可以构造一个 MacroCommand 宏命令类用来按次序先后执行几条相关联命令。当然可以发散的空间很多很多,感兴趣的可以自己去实现,原理都是一样的。

总结

  命令模式将发出请求的对象和执行请求的对象解耦,被解耦的两者之间通过命令对象进行沟通,调用者通过命令对象的 execute 放出请求,这会使得接收者的动作被调用,调用者可以接受命令当作参数,甚至在运行时动态地进行,命令可以支持撤销,做法是实现一个 undo 方法来回到 execute 被执行前的状态。MacroCommand 宏命令类是命令的一种简单的延伸,允许调用多个命令,宏方法也可以支持撤销。日志系统和事务系统可以用命令模式来实现。

  命令模式的优点很明显,调用者和执行者之间的解耦,更灵活的控制性,以及更好的扩展性等。缺点更明显,就是类的爆炸,大量衍生类的创建,这也是大部分设计模式的“通病”,是一个没有办法避免的问题。

源码下载

  https://github.com/zhaozepeng/Design-Patterns/tree/master/CommandPattern

引用

https://en.wikipedia.org/wiki/Command_pattern

http://blog.csdn.net/jason0539/article/details/45110355

时间: 2024-05-07 00:15:47

java/android 设计模式学习笔记(16)---命令模式的相关文章

java/android 设计模式学习笔记(10)---建造者模式

这篇博客我们来介绍一下建造者模式(Builder Pattern),建造者模式又被称为生成器模式,是创造性模式之一,与工厂方法模式和抽象工厂模式不同,后两者的目的是为了实现多态性,而 Builder 模式的目的则是为了将对象的构建与展示分离.Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程.一个复杂的对象有大量的组成部分,比如汽车它有车轮.方向盘.发动机.以及各种各样的小零件,要将这些部件装配成一辆汽车,这个装配过

java/android 设计模式学习笔记(14)---外观模式

这篇博客来介绍外观模式(Facade Pattern),外观模式也称为门面模式,它在开发过程中运用频率非常高,尤其是第三方 SDK 基本很大概率都会使用外观模式.通过一个外观类使得整个子系统只有一个统一的高层的接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节.当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块.ImageLoader 模块等.其实我们在开发过程中可能已经使用过很多次外观模式,只是没有从理论层面去了解它. 转载请注明出处:http://bl

java/android 设计模式学习笔记(7)---装饰者模式

这篇将会介绍装饰者模式(Decorator Pattern),装饰者模式也称为包装模式(Wrapper Pattern),结构型模式之一,其使用一种对客户端透明的方式来动态的扩展对象的功能,同时它也是继承关系的一种替代方案之一,但比继承更加灵活.在现实生活中也可以看到很多装饰者模式的例子,或者可以大胆的说装饰者模式无处不在,就拿一件东西来说,可以给它披上无数层不一样的外壳,但是这件东西还是这件东西,外壳不过是用来扩展这个东西的功能而已,这就是装饰者模式,装饰者的这个角色也许各不相同但是被装饰的对

java/android 设计模式学习笔记(13)---享元模式

这篇我们来介绍一下享元模式(Flyweight Pattern),Flyweight 代表轻量级的意思,享元模式是对象池的一种实现.享元模式用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,缓存可共享的对象,来达到对象共享和避免创建过多对象的效果,这样一来就可以提升性能,避免内存移除和频繁 GC 等. 享元模式的一个经典使用案例是文本系统中图形显示所用的数据结构,一个文本系统能够显示的字符种类就是那么几十上百个,那么就定义这么些基础字符对象,存储每个字符的显示外形和其他的格式化数据

java/android 设计模式学习笔记(12)---组合模式

这篇我们来介绍一下组合模式(Composite Pattern),它也称为部分整体模式(Part-Whole Pattern),结构型模式之一.组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别.这个最典型的例子就是数据结构中的树了,如果一个节点有子节点,那么它就是枝干节点,如果没有子节点,那么它就是叶子节点,那么怎么把枝干节点和叶子节点统一当作一种对象处理呢?这就需要用到组合模式了. 转

java/android 设计模式学习笔记(9)---代理模式

这篇博客我们来介绍一下代理模式(Proxy Pattern),代理模式也成为委托模式,是一个非常重要的设计模式,不少设计模式也都会有代理模式的影子.代理在我们日常生活中也很常见,比如上网时连接的代理服务器地址,更比如我们平时租房子,将找房子的过程代理给中介等等,都是代理模式在日常生活中的使用例子. 代理模式中的代理对象能够连接任何事物:一个网络连接,一个占用很多内存的大对象,一个文件,或者是一些复制起来代价很高甚至根本不可能复制的一些资源.总之,代理是一个由客户端调用去访问幕后真正服务的包装对象

java/android 设计模式学习笔记(3)---工厂方法模式

这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或取得对象的初始设置:选择生成哪个子对象实例:或在生成你需要的对象之前必须先生成一些辅助功能的对象,这个时候就需要了解该对象创建的细节,也就是说使用的地方与该对象的实现耦合在了一起,不利于扩展,为了解决这个问题就需要用到我们的工厂方法模式,它适合那些创建复杂的对象的场景,工厂方法模式也是一个使用频率很

java/android 设计模式学习笔记(5)---对象池模式

这次要介绍一下对象池模式(Object Pool Pattern),这个模式为常见 23 种设计模式之外的设计模式,介绍的初衷主要是在平时的 android 开发中经常会看到,比如 ThreadPool 和 MessagePool 等. 在 java 中,所有对象的内存由虚拟机管理,所以在某些情况下,需要频繁创建一些生命周期很短使用完之后就可以立即销毁,但是数量很大的对象集合,那么此时 GC 的次数必然会增加,这时候为了减小系统 GC 的压力,对象池模式就很适用了.对象池模式也是创建型模式之一,

java/android 设计模式学习笔记(4)---抽象工厂模式

再来介绍一下抽象工厂模式(Abstact Factory Pattern),也是创建型模式之一,上篇博客主要介绍了工厂方法模式.抽象工厂模式和工厂方法模式稍有区别.工厂方法模式中工厂类生产出来的产品都是具体的,也就是说每个工厂都会生产某一种具体的产品,但是如果工厂类中所生产出来的产品是多种多样的,工厂方法模式也就不再适用了,就要使用抽象工厂模式了. 抽象工厂模式的起源或者最早的应用,是对不同操作系统的图形化解决方案,比如在不同操作系统中的按钮和文字框的不同处理,展示效果也不一样,对于每一个操作系