设计模式笔记06-命令模式
1 引言
在本章,我们将把封装带到一个全新的境界:把方法调用(Method Invocation)封装起来。
没错,通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些很聪明的事情,例如记录日志,或者重复使用这些封装来实现撤销(undo)。
2 正文
2.1 命令模式可能行
本章的需求背景是:有一个电器公司要求开发一款遥控器可以控制家庭电器的开关、音量调节等功能。并且提供了一个可编程的遥控器,一共15个按钮:7对on/off按钮,一个全局撤销(undo)按钮。还有很多家庭电器类,而且以后还会增加。
命令模式可将”动作的请求者”和“动作的执行者”对象中解耦。在你们的例子中,请求者可以是遥控器,而执行对象就是厂商类其中之一的实例。
在你的设计中采用“命令对象”。利用命令对象,把请求(例如打开电灯)封装成一个特定对象(例如客厅电灯对象)。所以,如果对每个按钮都存储一个命令对象,那么当按钮按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能和正确的对象沟通,把事情做好就可以了。所以,看吧,遥控器和电灯对象解耦了。
回到餐厅模式,我们都知道餐厅是怎么工作的:
1、你,也就是顾客,把订单交给女招待。
2、女招待拿了订单,放在订单柜台,然后喊了一声“订单来了”。
3、快餐厨师根据订单准备餐点。
把餐厅想成是OO设计模式的一种模型,而这个模型允许将“发出请求的对象”和“接受与执行这些请求的对象”分离开来。比方说,对于遥控器API,我们需要分隔开“发出请求的按钮代码”和“执行请求的厂商特定对象”。万一遥控器的每个插槽都持有一个像餐厅订单那样的对象,会怎么样?那么,当一个按钮被按下,而遥控器不需要知道事情是怎么发生的,也不需要知道涉及哪些对象。
2.2 从餐厅到命令模式
实现命令接口Command
public interface Command {
public void execute();
}
实现一个打开电灯的命令
//这是一个命令,所以需要实现Command接口
public class LightOnCommand implements Command {
Light light;
/*
* 构造器被传入了某个电灯(比方说客厅的电灯),以便这个命令控制。
* 然后记录在实例变量中。一旦弟阿勇execute(),就由这个电灯对象成为接受者,负责接收请求。
*/
public LightOnCommand(Light light) {
this.light = light;
}
//这个execute()方法调用接受对象(我们正在控制的电灯)的on()方法
public void execute() {
light.on();
}
}
使用命令对象,假设我们有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置:
//
// This is the invoker
//
public class SimpleRemoteControl {
//有一个插槽持有命令,而这个命令控制着一个装置
Command slot;
public SimpleRemoteControl() {}
//这个方法用来设置插槽控制的命令。如果这段代码的客户想要改变遥控器按钮的行为,可以多次调用这个方法。
public void setCommand(Command command) {
slot = command;
}
//当按下这个按钮时,这个方法就会被调用,使得当前命令衔接插槽,并调用它的execute()方法
public void buttonWasPressed() {
slot.execute();
}
}
遥控器使用的简单测试
//这是命令模式的客户
public class RemoteControlTest {
public static void main(String[] args) {
//遥控器就是调用者,会传入一个命令对象,可以用来发出请求。
SimpleRemoteControl remote = new SimpleRemoteControl();
//创建一个电灯对象,此对象也就是请求的接受者。
Light light = new Light();
GarageDoor garageDoor = new GarageDoor();
//在这里创建一个命令,然后将接受者传给它
LightOnCommand lightOn = new LightOnCommand(light);
GarageDoorOpenCommand garageOpen =
new GarageDoorOpenCommand(garageDoor);
//把命令传给调用者
remote.setCommand(lightOn);
//按下按钮
remote.buttonWasPressed();
remote.setCommand(garageOpen);
remote.buttonWasPressed();
}
}
2.3 定义命令模式
命令模式,将“请求”封装成对象,以便使用不同的请求、队列、或者日志来参数化其他对象。命令模式也支持可撤销的操作。
实现遥控器
//
// This is the invoker
//
public class RemoteControl {
//这时候,遥控器要处理7个开与关的命令,使用相应数组记录这些命令
Command[] onCommands;
Command[] offCommands;
public RemoteControl() {
//在构造器中,只需要实例化并初始化两个开与关的数组
onCommands = new Command[7];
offCommands = new Command[7];
//NoCommand对象是一个空对象(null obeject)的例子。当你不想返回一个有意义的对象时,空对象就很有用,客户也可以将处理null的责任转义给空对象。举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为替代品,当调用它的execute() //方法时,这种对象什么也不做。
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
/*
* setCommand()方法须有三个参数,分别是插槽的位置、开的命令、关的命令。
* 这些命令将记录在开关数组中对应的插槽位置,以供稍后使用。
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
//当按下开或关的按钮,硬件就会负责调用对应的方法,也就是onButtonWasPushed()或者offButtonWasPushed()。
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
//覆盖toString(),打印出每个插槽和它对应的命令。稍后在测试遥控器的时候,会用到这个方法。
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.toString();
}
}
实现命令
public class StereoOnWithCDCommand implements Command {
Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
//要实现这个请求,需要调用音响的三个方法:首先打开它,然后把它设置成播放CD,最后把音量设置为11。
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
逐步测试遥控器
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
//将所有的装置创建在合适的位置
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
CeilingFan ceilingFan= new CeilingFan("Living Room");
GarageDoor garageDoor = new GarageDoor("");
Stereo stereo = new Stereo("Living Room");
LightOnCommand livingRoomLightOn =
new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff =
new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn =
new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff =
new LightOffCommand(kitchenLight);
CeilingFanOnCommand ceilingFanOn =
new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff =
new CeilingFanOffCommand(ceilingFan);
GarageDoorUpCommand garageDoorUp =
new GarageDoorUpCommand(garageDoor);
GarageDoorDownCommand garageDoorDown =
new GarageDoorDownCommand(garageDoor);
StereoOnWithCDCommand stereoOnWithCD =
new StereoOnWithCDCommand(stereo);
StereoOffCommand stereoOff =
new StereoOffCommand(stereo);
//现在已经有了全部的命令,可以将他们加载到遥控器插槽中。
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
remoteControl.setCommand(3, stereoOnWithCD, stereoOff);
//在这里,使用toString()方法打印出每个遥控器的插槽和它被指定的命令
System.out.println(remoteControl);
//一切就绪,逐步按下每个插槽的开与关按钮。
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.onButtonWasPushed(3);
remoteControl.offButtonWasPushed(3);
}
}
2.4 别忘了撤销
重新设计Command接口
public interface Command {
public void execute();
public void undo();
}
重新设计命令对象
public class LightOnCommand implements Command {
Light light;
int level;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
level = light.getLevel();
light.on();
}
public void undo() {
light.dim(level);
}
}
重新设计遥控器
public class RemoteControlWithUndo {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControlWithUndo() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i=0;i<7;i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
//初始化的时候,并没有所谓的“前一个命令”,所以将它设置为NoCommand对象。
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
/*
* 当按下按钮,我们取得这个命令,并优先执行它然后将它记录在nudoConmmand实例变量中。
* 不管是开或者关命令,我们的处理方法都是一样的。
*/
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed() {
//当按下撤销按钮,我们调用undoConmmand实例变量的Undo()方法,就可以倒转前一个命令。
undoCommand.undo();
}
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n");
return stringBuff.toString();
}
}
2.5 宏命令
public class MacroCommand implements Command {
Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
//当这个宏命令被遥控器执行时,就会一次性执行数组里的每个命令
public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
public void undo() {
for (int i = commands.length -1; i >= 0; i--) {
commands[i].undo();
}
}
}
2.6 命令模式的高级用途
队列请求和日志请求,关于这两个高级用途实例请阅读其他材料,本书中只略微带过,不过这才是命令模式在实际应用中大展拳脚的地方,后面有机会再深入研究。
3 本章小结
玩过游戏的朋友肯定对宏这个东西不陌生,君不见wower必备的焦点宏,喊话宏,其实都是由一个个命令组成。命令模式在实际项目中的运用也是较多,各位coder有哪些实际经历呢?
分享到:
相关推荐
设计模式学习笔记-命令模式
行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式) 2) 学习目标:通过学习,学员...
3. 行为型模式:行为型模式关注对象之间的通信和协作,包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。这些模式可以帮助...
代理模式学习笔记、单例模式学习笔记、命令模式、原型模式、模式特点总结。 为个人对设计模式的理解,如果有理解不一致的不要砸砖啊
代理设计模式、命令模式学习笔记,其中包含对模式的理解以及详细的模式使用示例
设计模式经典样例笔记与代码Swift.zip 基础 [x] 类间的关系 [x] 设计原则 创建型 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定...
Java设计模式之命令模式/Java函数式编程 笔记
设计模式学习笔记(十五)命令模式及在Spring JdbcTemplate 中的实现.doc
3.设计模式Design Pattern:创建型模式(厂模式Factory、抽象工厂模式Abstract Factory、单例模式Singleton、建造者模式Builder、原型模式Prototype和对象池模式Object Pool Pattern)、结构型模式(适配器模式、...
该文档是自己在学习设计模式时整理的常用设计模式pdf文档,包括源码,包括装饰模式,代理模式,责任链模式,命令模式,解释器模式,迭代器模式,备忘录模式,观察者模式,工厂模式,建造者模式,适配器模式,桥梁...
Java最全学习资料+面试题+DOS命令+设计模式+Excel技巧+java学习笔记
设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。工厂模式、抽象工厂模式、...
Java Design PatternsJava 设计模式学习笔记,简单易懂,每个模式都有相应的代码示列,帮助学习理解。在线阅读地址:设计原则创建型模式作用:将创建与使用代码解耦结构型模式作用:将不同的功能代码解耦桥接模式...
Android源码设计模式解析与实战读书笔记源代码 说明: 包名factorypattern.normal表示的是工厂方法模式的普通用法 包名factorypattern.practices表示的是工厂方法模式的常用 包名observerpattern表示的是观察者模式...
describe:设计模式学习笔记 逻辑结构图 代码结构图 设计模式简述 创建型模式,共五种:工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。 结构型模式,共七种:适配器模式,装饰器模式,代理模式,...
{14.2}XML的设计}{205}{section.14.2} {14.3}DTD/Schema}{205}{section.14.3} {14.3.1}SAX应用}{206}{subsection.14.3.1} {14.4}dom4j}{207}{section.14.4} {14.5}XPath}{210}{section.14.5} {14.6}apache....
最详细的SQL注入相关的命令整理 Oracle Oracle中PL/SQL单行函数和组函数详解 mssql+oracle Oracle编程的编码规范及命名规则 Oracle数据库字典介绍 0RACLE的字段类型 事务 CMT DEMO(容器管理事务演示) 事务隔离性的...
第一部分:设计模式 & UML 简单工厂 工厂方法模式 抽象工厂模式 策略模式 责任链模式 命令模式 模板方法模式 适配器模式 代理模式 外观模式 组合模式 装饰模式 享元模式 桥接模式 Builder模式 状态模式 解释器模式 ...
学习笔记及代码 1. 笔记 类型 笔记 Java Java基础笔记Java多...设计模式 design-mode SpringBoot springboot-demo01 [SpringBoot + Mybatis]springboot-demo02 [SpringBoot + Spring Data JPA]springboot-demo03 [Sp
++实现的粒子识别软件,它使用一些众所周知的设计模式来实现可扩展性和代码重用。 该软件基于Mauricio Cerda C的第一个实现和Scott Waitukaitis变体,用于识别智利大学物理系的高密度图像中的粒子。 所使用的算法是...