这是我看Head first设计模式书籍之后想要总结的知识点,一方面是对自己学习的东西总结和提炼加强自己的理解和记忆,另一方面是给大家简化这本书,方便大家快速了解各种设计模式。
我想提醒大家的是,设计模式只是前人总结的一些经验套路,实际上还是要在开发项目中慢慢体会,不可成为设计模式的中毒患者,强行照搬设计模式的一些规则。
这次,我们把方法调用封装起来,调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形
的方法来完成它就可以。通过封装方法调用,可以用来记录日志;或者重复使用这些封装来实现撤销(undo).
这次我们要讲解的设计模式是命令模式。
我们举个栗子:
我们需要创建一组控制遥控器的API,遥控器的每个插槽都能够控制一个或一组装置。请注意,能够控制目前的装置和任何
未来可能出现的装置,这一点是很重要的。
遥控器有七个插槽,可以安装不同的设备,然后可以使用遥控器上的每个插槽对应的开关按钮控制它。
最后遥控器上有一个撤销的命令,可以撤销最后一次操作,回到倒数第二次操作。
思路:
我们遥控器不需要知道各种设备的具体实现细节,只需要知道按钮被按下发出正确的请求。
因此需要想办法将"动作的请求者"(遥控器)从"动作的执行者"(各种装置)中解耦.
在设计中采用"命令对象"就可以办到。利用命令对象,把请求(电灯开灯)封装成一个特定对象。当按钮被按下的时候,
就可以请命令对象做相关的工作,遥控器并不需要知道工作内容是什么.这样就可以实现遥控器和装置设备解耦。
直接看代码部分:
首先,让所有的命令行对象实现相同的包含一个方法的接口。
//让所有的命令对象实现相同的包含一个方法的接口
public interface Command{
public void execute();
}
实现一个打开电灯的命令 假设厂商提供的类Light类有两个方法:on()和off()
//实现一个打开电灯的命令
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on();
}
}
//实现一个关闭电灯的命令
public class LightOffCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.off();
}
}
假设我们有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置
//使用命令对象
public class SimpleRemoteControl{
//有一个插槽持有命令,而这个命令控制着一个装置
Command slot;
public SimpleRemoteControl(){}
public vyoid setCommand(Command command){
slot = command;
}
public void buttonWasPressed(){
slot.execute();
}
}
简单测试
//测试代码
public class RemoteControllTest{
public static void main(String args[]){
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
LightOnCommand lightOn = new LightOnCommand(light);
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}
下面是我们的命令模式出厂了:
命令模式: 将"请求"封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
命令对象通过在特定接受者上绑定一组动作来封装一个请求。它只暴露出一个execute()方法,当此方法被调用
的时候,接受者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接受者进行了哪些动作,只知道如果调用execute()方
法,请求的目的就能达到。
下面是类图
下面我们实现要求的遥控器
//实现遥控器
public class RemoteControl{
Command[] onCommands;
Command[] offCommands;
public RemoteControl(){
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for(int i = 0; i < onCommands.length; i++){
//预先初始化,赋值为NoCommand类型对象,execute()方法是空的,也就表示没有插入装置
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand){
onCommands[slot] = onCommand;
offCommands[slot] = offComand;
}
public void onButtonWasPressed(int slot){
onCommands[slot].execute();
}
public void onButtonWasPushed(int slot){
offCommands[slot].execute();
}
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();
}
}
测试代码:
//测试代码
/*假设我们厂商已经提供了Light CeilingFan GarageDoor Stereo这些设备类*/
public class RemoteLoader{
public void main(String args[]){
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Ligh("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(libingRoomLight);
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 stereOnWithCD = new StereoOnWithCDCommand(stereo);
StereoOffCommand stereOff = new StereoOffCommand(stereo);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
remoteControl.setCommand(3, stereoOnWithCD, stereoOff);
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);
}
}
为了实现撤销的功能 我们需要在Command接口中加入undo()抽象方法,并且在各种实现子类中实现undo()方法
public interface Command{
public void execute();
public void undo();
}
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on();
}
public void undo(){
light.off();
}
}
//修改遥控器的类
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 < onCommands.length; i++){
//预先初始化,赋值为NoCommand类型对象,execute()方法是空的,也就表示没有插入装置
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand){
onCommands[slot] = onCommand;
offCommands[slot] = offComand;
}
public void onButtonWasPressed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void onButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
//加入撤销命令的方法
public void undoButtonWasPushed(){
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");
}
return stringBuff.toString();
}
}
我们还可以将执行一种命令,可以起到执行很多命令的作用
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(); //每个命令对象都实现了execute()方法
}
}
}
命令模式还可以用于队列请求(线程从队列中取到命令对象,然后调用命令对象的execute()方法),或者日志请求(在命令接口Command加入store()和load()接口,每当命令执行时,我们就将执行内容存储在磁盘中,如果要撤销,只需要加载以及重新撤销操作即可)。具体的还是看看其他的文章。
下一次我们将要讲适配器模式和外观模式,这个用的可是很多的啊。