Head First Design Mode(8)-命令模式

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 

命令模式:    

    封装调用——本节把封装带到了一个全新的境界:把方法调用(method invocation)封装起来;

    通过封装方法调用,我们可以将功能模块包装成型,调用功能的模块不需要关心事情如何进行,只要知道如何使用包装成型的方法来完成功能即可;

 

家电自动化遥控器API示例:

    七个可编程卡槽,对应七种家电的控制开关;

    有一个整体的控制撤销的按钮;

    针对每一种家电我们都有各自开发的JavaAPI来进行控制;

 

我们想要的是创建一组控制遥控器的API,让每个插槽对相应的电器进行控制,最主要的是可以支持未来可能出现的设备的控制;

    我们可以在Machine插槽上接上不同的装置,然后既可以控制它了;

    撤销按钮是整体公用的,会撤销最后一个按钮的动作;

Head First Design Mode(8)-命令模式

 

不同的设备厂商为设备提供了接口类:

    包括TV、Light等;而且这样的类还不少(很多家电),接口各异;

Head First Design Mode(8)-命令模式

遥控器按下按钮执行动作,但不一定要知道具体家电实现方法的细节;

 

命令模式可以将“动作的请求者”从“动作的执行者”对象中解耦:

    请求者是遥控器;

    执行者是具体家电类的一个实例(一个电视机或是一盏灯);

 

设计中使用“命令对象”:

    利用命令对象,把请求封装成一个特定对象;

    当按下开关打开灯时,就可以请命令对象做相关的工作;遥控器并不知道具体做什么,只要命令对象能和正确的对象沟通即可;

    这样,遥控器就和灯解耦了;

 

餐厅场景举例——命令模式的简单介绍:

    顾客把订单给到招待,招待再把订单拿到柜台,并通知厨师订单来了,赶快准备,厨师根据订单制作菜品;

Head First Design Mode(8)-命令模式

餐厅的角色和职责:

    一张订单封装了准备点餐的请求;

    订单对象可以被传递,且只有一个方法:orderUp();他封装了准备餐点的动作;

    订单内有一个执行“准备餐点动作”的对象——厨师;

 

    招待并不需要知道订单内容是什么,也不需要知道谁来准备餐点,他的工作是接受订单,然后调用订单的orderUp()方法;

    这样,只要是顾客createOrder()的招待都可以takeOrder(),然后调用所有订单都需要有的orderUp();

    招待不是让厨师准备餐点,而是调用订单的方法提醒厨师订单需要被准备了;即招待和厨师解耦了;

 

类似的场景对应一种设计模式——允许将“发出请求的对象”与“接受与执行请求的对象”分隔开;

    招待发出准备餐点的请求——>【订单】——>厨师准备餐点

    人操作家电的请求——>【遥控器】——>家电执行动作

        遥控器不知道有哪些对象,但当按钮按下,调用该对象的orderUp()方法,灯就开了;

 

从餐厅到命令模式:

Head First Design Mode(8)-命令模式

 

回到我们的遥控器——使用命令对象:

    实现命令接口Command:所有命令对象实现相同的包含一个方法的接口;一般惯用名称execute();

    命令的执行对象Light:如灯,可以打开或关闭;

    实现具体的命令LightOnCommand:如开灯命令;

    使用命令类SimpleControl:    假设一个只有一个按钮的遥控器;

Head First Design Mode(8)-命令模式

log:

bogon:遥控器简单测试 huaqiang$ javac *.java

bogon:遥控器简单测试 huaqiang$ java SimpleControlDriver

Light on! For - LightA

 

此例中,遥控器就是调用者,会传入命令对象,可以用发请求;

 

定义命令模式:

    命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象;

    命令模式也支持可撤销的操作;

 

重点理解:

    一个命令对象通过在特定接收者上绑定一组动作来来封装一个请求;

    命令对象将接收者和动作包进对象中,只暴露一个execute()方法,此方法调用时,接收者就会执行这些动作;

    对外,其他对象并不知道谁做了那些事,只知道,如果调用execute()方法,请求的目的就能达到;

 

命令模式类图:

    客户构建命令;

    让调用者(Invoker)持有请求(Command),用请求(ConcreteCommand)去通知命令的执行者(Receiver);

    完成调用者和执行者之间的解耦;

Head First Design Mode(8)-命令模式

 

完成7个插槽的遥控器:

    类似简单遥控器所做的,我们需要提供一个方法,将命令指定到插槽;

    实际上,我们将有7个插槽,每个插槽具备开关按钮;

 

1.实现遥控器

2.实现命令

3.测试遥控器

Head First Design Mode(8)-命令模式

关键点:

    NoCommand对象是一个空对象,是为了确定每个插槽永远都有命令;

    我们看到execute()方法可以封装一组命令;

        命令对象持有对一个厂商类的实例的引用,并实现一个execute()方法;

        这个方法会调用厂商类实例的一个或多个方法,完成特定的行为;

    加入撤销操作也很简单,只需要把execute之前执行的动作倒过来,如打开命令的撤销就是关闭;

 

宏命令-命令的“Party模式”:

    比如在关灯的同时,关掉音响,关掉电视等;

    我们可以通过一个MacroCommand来实现一个新命令,用来执行一组命令;

Head First Design Mode(8)-命令模式

 

如果想实现多层次的撤销操作:

    只需要使用一个堆栈记录操作的过程的每一个命令;不管什么时候按下了撤销按钮,都可以从堆栈中取出最上层的命令,然后调用他的undo()方法;

 

命令模式的更多用途:队列请求

    命令可以将运算打包,一个接收者和一组动作;命令对象被调用之后,运算就可以被执行;

    甚至是在不同的线程之中被调用;也衍生出一些应用:日程安排(Scheduler)、线程池、工作队列;

 

想象一个队列,你在某一端添加命令,然后另一端则是线程;

    线程从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令丢弃,再取出下一个命令;

    这样能有效地把运算限制在固定数目的线程中进行;

    不同的命令对象可以封装不同的功能,但只要实现了命令模式,就都可以加入队列中;

 

命令模式的更多请求:日志请求

    某些应用需要将所有的动作都记录在日志中,并能在系统死机之后,调用这些动作恢复到之前的状态;

    可以用命令模式支持,将历史命令存储到磁盘,需要时,重新加载,批量依次调用这些对象的execute()方法;

    对于更高级的应用,这些技巧可以被扩展到事务处理中;

 

总结:

1.命令模式将发出请求的对象和执行请求的对象解构;

2.在被解耦的两者之间是通过命令对象进行沟通的,命令对象封装了接收者和一个或一组动作;

3.调用者通过调用命令对象的execute()发出请求,这会使得接收者的动作被调用;

4.调用者可以接受命令当做参数,甚至在运行时动态地进行;

5.命令可以支持撤销,做法是实现一个undo()方法来回倒execute()被执行前的状态;

6.宏命令是命令的一种简单延伸,允许调用多个命令;宏命令也支持撤销;

7.命令也可以用来实现日志和事务系统;

 

OO基础:

    抽象;

    封装

    继承;

    多态;

OO原则:

    封装变化

    多用组合,少用继承

    针对接口编程,不针对实现编程

    为交互对象之间的松耦合设计而努力;

    类应该对扩展开放,对修改关闭;

    依赖抽象,不要依赖具体类;

OO模式:

    策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;

    观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;

    装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;

    简单工厂模式;

    工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;

    抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;

    单件模式:确保一个类只有一个实例,并提供全局访问点;

    ——命令模式:将请求封装成对象,这可以让你使用不同的请求,队列或者日志请求来参数化其他对象;命令模式也支持撤销操作;