Head First 设计模式之命令模式(Java例子)

前言:

来源于《head first 设计模式》。当作读书笔记了,这次看的是第六章装命令模式。

命令模式的概念

将请求封装成对象,这可以让你使用不同的请求、队列、或者日志请求来参数化其他对象,命令模式也支持撤销操作,当需要将发出请求和执行请求的对象解耦的时候,使用命令模式即可。

命令模式的uml图

在这里插入图片描述
简单介绍一下:

  • 命令(Command):为所有命令声明了一个接口。调用命令对象的 execute()方法,就可以让接收者进行相关的操作。
  • 具体命令(ConcreteCommand):实现命令接口,定义了动作和接收者之间的绑定关系。调用者只要调用 execute() 就可以发出请求,然后由 ConcreteCommand 调用接收者的一个或多个动作。
  • 请求者(Invoker):持有一个命令对象或者多个命令对象,有一个行动方法,在某个时间点调用命令对象的 execute() 方法,将请求付诸实行。
  • 接收者(Receiver):接收者知道如何进行必要的动作,实现这个请求。任何类都可以当接收者。这个可以看作是最终的执行者
  • 客户端(Client):创建一个具体命令(ConcreteCommand)对象并确定其接收者(Receiver),包括把其他角色串连在一起。

看个例子把。

模拟一个遥控器,控制灯的开关.

定义接受者

这是最后执行动作的角色。

public class Light {
	String location = "";

	public Light(String location) {
		this.location = location;
	}

	public void on() {
		System.out.println(location + " light is on");
	}

	public void off() {
		System.out.println(location + " light is off");
	}
}
command接口
public interface Command {
	/**
     * 执行命令
     */
    void execute();

    /**
     * 撤销命令
     */
    void undo();
}
ConcreteCommand具体命令类

具体的两个命令,分别定义其撤销操作
关灯命令

public class LightOffCommand implements Command {
	Light light;
 
	public LightOffCommand(Light light) {
		this.light = light;
	}
 
	public void execute() {
		light.off();
	}
	@Override
    public void undo() {
        light.on();
    }
}

开灯命令

public class LightOnCommand implements Command {

    private Light light;

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

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

    @Override
    public void undo() {
        light.off();
    }
}
请求者(Invoker)

onCommands offCommands用于存储命令对象,通过onButtonWasPushed来调用命令的execute 命令中再把处理交给接受者执行后面的操作

public class RemoteControl {
	
	Command[] onCommands;
	Command[] offCommands;
 	/**
     * 撤销(回退)命令
     */
    private Command undoCommand;
	public RemoteControl() {
		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;
		}
	}
  
	public void setCommand(int slot, Command onCommand, Command offCommand) {
		onCommands[slot] = onCommand;
		offCommands[slot] = offCommand;
	}
 
	public void onButtonWasPushed(int slot) {
		onCommands[slot].execute();
		undoCommand=onCommands[slot];
	}
 
	public void offButtonWasPushed(int slot) {
		offCommands[slot].execute();
		undoCommand=onCommands[slot];
	}
    public void undoButton() {
        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 RemoteLoader {
 
	public static void main(String[] args) {
		RemoteControl remoteControl = new RemoteControl();
 
		Light livingRoomLight = new Light("Living Room");

		LightOnCommand livingRoomLightOn = 
				new LightOnCommand(livingRoomLight);
		LightOffCommand livingRoomLightOff = 
				new LightOffCommand(livingRoomLight);	
		remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
		System.out.println(remoteControl);
		remoteControl.onButtonWasPushed(0);
		remoteControl.offButtonWasPushed(0);
		remoteControl.undoButton();
		}
}

总结

命令模式将发出请求的对象和执行请求的对象解耦,在被解耦的两者之间是通过命令对象进行沟通的。

  • 一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将接收者和动作封装进对象中,这个对象只暴露出一个 execute() 方法,当此方法被调用时,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些操作,只知道如果调用 execute() 方法,请求的目的就可以达到。
  • 当你不想返回一个有意义的对象时,空对象就很有用。这样,我们就可以把处理 null 的责任转移给空对象,甚至有些时候,空对象本身也被视为一种设计模式。例如本例子中的noCommand
    适用场景:
     1、命令的发送者和命令执行者有不同的生命周期,命令发送了并不是立即执行。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在序列化之后传送到另外一台机器上去。
     2、命令需要进行各种管理逻辑,比如:对多个命令的统一控制。
     3、需要支持撤消/重试操作。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用 undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供 redo()方法, 以供客户端在需要时再重新实施命令效果。
命令模式的更多用途

命令模式的关键之处就是把请求封装成为对象,也就是命令对象(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:线程池、工作队列、日志请求等。

队列请求
想象有一个工作队列:你在某一端添加命令,然后另一端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令…
请注意,工作队列和命令对象之间是完全解耦的。此刻线程可能在进行财务运算,下一刻却在读取网络数据。工作队列对象不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute()方法。类似地,它们只要实现命令模式的对象,就可以放入队列里,当线程可用时,就调用此对象的execute()方法。
日志请求
某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章