关闭

设计模式笔记:命令模式

1251人阅读 评论(1) 收藏 举报

首先看看命令模式的定义:命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持撤销的操作。

所谓参数化,我的理解是实际执行的对象,比如light(电灯)、strereo(音响),他们存在执行动作的方法,并且作为命令对象的成员变量

队列、日志这两样是命令模式的应用直接扩展

为什么需要命令模式:

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

假如有这样一种情景,我现在需要打开风扇,自然我需要一个遥控器,很明显这里的关系是:我——遥控器—>命令——风扇,而遥控器发出命令去控制风扇,其中,遥控器是请求者,风扇是执行者,遥控器不知道怎么让风扇转起来,他只知道发出命令,即上面的动机。

也可以这样子想象:我在餐厅里点菜,服务员他帮我转达请求,厨师帮我炒菜,而服务员是不知道怎么去炒,他仅仅转达请求而已——完全解耦了

先看看类图:


其中,我们可以把invoker看作遥控器,receiver看作动作的执行对象,即light之类,我们现在直接看一个支持撤销命令的代码并不是很难,所以直接上这个,来自《Head First 设计模式》:

这里给出一个接口和两个代表性的实体类(其实实体类有light、fan、cellingfan(这个是实现除开关两种可能性以外的多种可能性的撤销方法)),类里面有并没有罗列出来的类,但其实是差不多的,应该不会造成阅读障碍

先看实体类:他们各自有自己的行为方法,开关、换高低速(更好地表现撤销功能)

public class Light {
	public void On(){
		System.out.println("Light-----On");
	}
	public void Off(){
		System.out.println("Light-----Off");
	}
}

public class Fan {
	public void On(){
		System.out.println("Fan-----On");
	}
	public void Off(){
		System.out.println("Fan-----Off");
	}
}

public class CellingFan{
	public static final int High = 3;
	public static final int MEDIUM = 2;
	public static final int LOW = 1;
	public static final int OFF = 0;
	
	String location;
	int speed;
	
	public CellingFan(String location) {
		this.location = location;
		speed = OFF;
	}
	
	public void high(){
		System.out.println("Speed-----High");
		speed = High;
	}
	
	public void medium(){
		System.out.println("Speed-----Medium");
		speed = MEDIUM;
	}
	
	public void Low(){
		System.out.println("Speed-----Low");
		speed = LOW;
	}
	
	public void Off(){
		System.out.println("Speed-----Off");
		speed = OFF;
	}

	public int getSpeed() {
		return speed;
	}
}

实体类并没有什么特别指出,看上去好轻松,接下来是封装好的命令抽象,这里的命令抽象也很简单,只支持两种情况:开、关,开了撤销就是关,关了撤销就是开

public interface Command {
	public void execute();
	public void undo();
}

public class LightOnCommand implements Command {
	Light light;
	public void setLight(Light light) {this.light = light;}

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

	@Override
	public void undo() {light.Off();}
}

public class LightOffCommand implements Command {
	Light light;
	public void setLight(Light light) {this.light = light;}

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

	@Override
	public void undo() {light.On();}
}

//这里就不列举fan的命令对象了

这里的另外一个命令是支持多种情况的:这里只列举一个高速的命令,其他大同小异

public class CellingFanHighCommand implements Command {
	CellingFan cellingFan;
	int prevSpeed;
	
	public void setCellingFan(CellingFan cellingFan) {
		this.cellingFan = cellingFan;
	}

	@Override
	public void execute() {
		prevSpeed = cellingFan.getSpeed();
		cellingFan.high();
	}

	@Override
	public void undo() {
		switch(prevSpeed){
		case CellingFan.High:cellingFan.high();break;
		case CellingFan.LOW:cellingFan.Low();break;
		case CellingFan.MEDIUM:cellingFan.medium();break;
		case CellingFan.OFF:cellingFan.Off();break;
			default:break;
		}
	}
}
可以看到这里有一个判断:之前的速度是什么,便让执行者执行之前的命令,我们也提到过,执行者就是那堆实体类,他们知道如何执行任务

我们还需要一个空对象来填充那些null的位置

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

	@Override
	public void undo() {}

}

到了这里,我们定义了实体类,把命令抽象了出来,还需要一个遥控器

我们的遥控器根据不同的设备设置开关按钮,还有一个撤销按钮,当按下开关、转速按钮的同时把执行这一动作的命令保存到撤销命令里面,这样想撤销的时候就可以调用undoButtonPushed()方法来执行上一次的命令的undo方法,不过要用之前得设置按钮

public class RemoteControl {
	Command[] onCommands;
	Command[] offCommands;
	Command undoCommand;
	
	//开关灯
	//开关风扇
	//转速高速、关
	//转速中速、关
	//转速低速、关 共10个按钮
	public RemoteControl(){
		onCommands = new Command[5];//负责开的
		offCommands = new Command[5];//负责关的
		
		//引入空对象这一设计理念,关于空对象会在java中的类型转换博客中提及
		Command noCommand = new noCommand();
		for(int i = 0;i<5;i++){
			onCommands[i] = noCommand;
			offCommands[i] = 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();
		undoCommand = onCommands[slot];
	}
	
	public void offButtonWasPushed(int slot){
		offCommands[slot].execute();
		undoCommand = offCommands[slot];
	}
	
	public void undoButtonWasPushed(){
		undoCommand.undo();
	}
}

接下来是客户设置遥控器(其实设置的动作可以给设计框架的人来做,调用者直接调用命令就好了)

public class Remoteoader {
	public static void main(String[] args) {
		RemoteControl control = new RemoteControl();
		
		//为遥控器设定命令
		//构造一个打开电灯的命令
		Light light = new Light();
		LightOnCommand lightOnCommand = new LightOnCommand();
		LightOffCommand lightOffCommand = new LightOffCommand();
		lightOnCommand.setLight(light);
		lightOffCommand.setLight(light);
		control.setCommand(0, lightOnCommand, lightOffCommand);
		
		Fan fan = new Fan();
		FanOnCommand fanOnCommand = new FanOnCommand();
		FanOffCommand fanOffCommand = new FanOffCommand();
		fanOnCommand.setFan(fan);
		fanOffCommand.setFan(fan);
		control.setCommand(1, fanOnCommand, fanOffCommand);
		
		CellingFan cfan = new CellingFan("livingRoom");
		CellingFanHighCommand cfhCommand = new CellingFanHighCommand();
		CellingFanLowCommand cflCommand = new CellingFanLowCommand();
		CellingFanMediumCommand cfmCommand = new CellingFanMediumCommand();
		CellingFanOffCommand cfoCommand = new CellingFanOffCommand();
		
		cfhCommand.setCellingFan(cfan);
		cflCommand.setCellingFan(cfan);
		cfmCommand.setCellingFan(cfan);
		cfoCommand.setCellingFan(cfan);
		
		control.setCommand(2, cfhCommand, cfoCommand);
		control.setCommand(3, cflCommand, cfoCommand);
		control.setCommand(4, cfmCommand, cfoCommand);
		//至此遥控器命令已经设置完毕
		
		System.out.println("我要开电灯!");
		control.onButtonWasPushed(0);
		
		System.out.println("不行,我要撤销啊,好浪费电");
		control.undoButtonWasPushed();
		
		System.out.println("就算浪费电,我也要开风扇");
		control.onButtonWasPushed(3);
		
		System.out.println("换中速,热的受不了");
		control.onButtonWasPushed(4);
		
		System.out.println("高速!");
		control.onButtonWasPushed(2);
		
		System.out.println("撤销吧,钱包没钱了");
		control.undoButtonWasPushed();
		
		//这里在撤销一次是不会返回到更前面的,因为这个简单的demo并没有考虑太多情况
		//可以尝试用一个栈去保存撤销的命令
		//control.undoButtonWasPushed();
		
		//同理,并没有在off按钮上添加条件判断是否为开的状态,如果代码能够受控制并不会有多大问题
		//但是如果代码不在控制范围内,最好加上是否为开状态的判断
		System.out.println("宿舍热得要死,我现在出去买空调!关风扇!");
		control.offButtonWasPushed(4);
	}
}

//输出
//我要开电灯!
//Light-----On
//不行,我要撤销啊,好浪费电
//Light-----Off
//就算浪费电,我也要开风扇
//Speed-----Low
//换中速,热的受不了
//Speed-----Medium
//高速!
//Speed-----High
//撤销吧,钱包没钱了
//Speed-----Medium
//宿舍热得要死,我现在出去买空调!关风扇!
//Speed-----Off
如代码注释所述,代码不支持连续撤销,也并没有在关闭命令之前检查是否曾经开过设备,可以尝试使用一个栈去跟踪保存命令,同时也可以加入一些逻辑判断去判断设备开关状态,当然如果代码是受控制的状态,尽管不太严格但还是可以去接受的

宏命令:如果你需要按下一个按钮所有设备都动起来,不妨设置一个宏命令,他跟上面的命令的不同之处就在于,上面的命令的成员变量是实体类(Light、fan),而这里他把需要的命令组成一个Command数组,他也实现了Command接口,所以支持把他设置进去遥控器按钮的这个功能,execute和undo方法是遍历这些命令,并执行:

public class MacroCommand implements Command{
	Command[] commands;
	public MacroCommand(Command[] commands) {this.commands = commands;}
	@Override
	public void execute() {
		for(int i=0;i<commands.length;i++){commands[i].execute();}
	}
	//当然这里如果像cellingFan一样具有多种情况,undo就可能需要栈了
	@Override
	public void undo() {
		for(int i=0;i<commands.length;i++){commands[i].undo();}
	}
}

笔者偷个懒= =栈的方法就不去尝试了

看看他的优点:

1、降低系统的耦合度。
2、新的命令可以很容易地加入到系统中。
3、可以比较容易地设计一个命令队列和宏命令(组合命令)。
4、可以方便地实现对请求的Undo和Redo。

缺点:

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

这个缺点我们可以从内部类的方面上去改善,虽然java会对内部类也产生.class文件,可读性还是可以改善一下的,例子会在下面给出

适用环境:

1、系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2、系统需要在不同的时间指定请求、将请求排队和执行请求。
3、系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4、系统需要将一组操作组合在一起,即支持宏命令

我们来看看使用内部类的命令模式:例子来自java编程思想,这个我们试试从命令模式的角度思考:

存在一个控制框架,他的工作就是在事件就绪的时候执行事件(本例是基于时间触发的事件)
public abstract class Event {
	private long eventTime;
	protected final long delayTime;
	public Event(long delayTime){
		this.delayTime = delayTime;
		start();
	}
	
	//获取当前时间,并加上一个延迟时间,生成触发器的时间
	//可以通过调用start重新启动计时器,System.nanoTime()是当前的精确时间
	//如果需要重复一个事件,只需简单地在action中调用一下start方法,不过前提是在工作的Controller类的list中存在这个事件
	public void start(){
		eventTime = System.nanoTime()+delayTime;
	}
	
	//当当前时间到达要执行的时间
	public boolean ready(){
		return System.nanoTime()>=eventTime;
	}
	
	public abstract void action();
}


public class Controller {
	private List<Event> eventList = new ArrayList<Event>();
	public void addEvent(Event c){eventList.add(c);}
	
	//遍历eventList,寻找就绪的ready、要运行的event对象
	public void run(){
		while(eventList.size()>0){
			//把eventList复制一份是为了你在访问eventlList的同时修改他
			//有同步的感觉
			for(Event e:new ArrayList<Event>(eventList)){
				if(e.ready()){
					System.out.println(e);
					e.action();
					eventList.remove(e);
				}
			}
		}
	}
}
到了这里控制框架基本完成了,现在我们就用内部类去为他提供具体的实现,他能在单一的类里面产生对同一个基类Event的多种导出版本
假如现在有温室的运作,控制灯光、水、温度、响铃
public class GreenhouseControls extends Controller {
	//灯光
	private boolean light = false;
	public class LightOn extends Event{
		public LightOn(long delayTime) {super(delayTime);}
		@Override
		public void action() {light = true;}
		public String toString(){return "Light is on";}
	}
	public class LightOff extends Event{
		public LightOff(long delayTime) {super(delayTime);}
		@Override
		public void action() {light = true;}
		public String toString(){return "Light is off";}
	}
	//水分
	private boolean water = false;
	public class WaterOn extends Event{
		public WaterOn(long delayTime) {super(delayTime);}
		@Override
		public void action() {water = true;}
		public String toString(){return "Water is on";}
	}
	public class WaterOff extends Event{
		public WaterOff(long delayTime) {super(delayTime);}
		@Override
		public void action() {water = false;}
		public String toString(){return "Water is off";}
	}
	//温度(白天、黑夜温度)
	private String thermostat = "Day";
	public class ThermostatNight extends Event{
		public ThermostatNight(long delayTime) {super(delayTime);}
		@Override
		public void action() {thermostat = "Night";}
		public String toString(){return "thermostat is Night";}
	}
	public class ThermostatDay extends Event{
		public ThermostatDay(long delayTime) {super(delayTime);}
		@Override
		public void action() {thermostat = "Day";}
		public String toString(){return "thermostat is Day";}
	}
	//响铃
	public class Bell extends Event{
		public Bell(long delayTime) {super(delayTime);}
		@Override
		public void action() {
			//在这里不停的addEvent,这个方法是在Controller的run方法里面被调用
			//就是说,执行一次这个方法,就往eventList里面加入一个新的Bell事件
			//注意到run方法的循环条件是当eventList长度不为0的时候一直访问,可以看出这里是无限响铃的
			addEvent(new Bell(delayTime));
		}
		public String toString(){return "Bing";}
	}
	
	//重启系统
	public class Restart extends Event{
		private Event[] eventList;
		public Restart(long delayTime,Event[] eventList) {
			super(delayTime);
			this.eventList = eventList;
			for(Event e:eventList){
				addEvent(e);
			}
		}
		//跟Bell类一样,重启系统会重复把你指定的list加入到eventList里面起,循环访问
		//同时也将本事件加入到里面
		@Override
		public void action() {
			for(Event e:eventList){
				//start是为了让事件有一个新的延迟触发时间
				e.start();
				addEvent(e);
			}
			start();
			addEvent(this);
		}
		public String toString(){return "Restart System";}
	}
	
	//系统的终止方法
	public static class Teeminate extends Event{
		public Teeminate(long delayTime) {super(delayTime);}
		public void action(){System.exit(0);}
		public String toString(){return "Terminating";}
	}
}
我们这main方法:
public class GreenhouseController {
	
	public static void main(String[] args) {
		GreenhouseControls gc = new GreenhouseControls();
		gc.addEvent(gc.new Bell(900));
		
		Event[] eventList = {
				gc.new ThermostatNight(0),
				gc.new LightOn(200),
				gc.new LightOff(400),
				gc.new WaterOn(600),
				gc.new WaterOff(600),
				gc.new ThermostatDay(1400),
		};
		
		gc.addEvent(gc.new Restart(2000, eventList));
		//我们在前面提到过,gc的run是不断地访问的,因为存在restart和bell,所以如果不想循环了,可以提供一个参数执行终止方法
		if(args.length==1){
			gc.addEvent(new GreenhouseControls.Teeminate(new Integer(args[0])));
		}
		gc.run();
	}
}
在这里,我们把实体类、命令类(其实在这里的命令就是事件Event)他们都放在一个GreenhouseControls里面统一管理,而每次需要用到他们时,就gc.new xxxx()这样子的方式去获取实例。这里的遥控器类是Controller,他提供的run方法我们可以想象为一个按钮,当按钮执行,真正去执行的执行者是那些命令指控的实体类(e.action),尽管这个例子有点奇怪,但是在代码的构建上清晰了很多,把实现命令的细节用一个类封装了起来


应用一:队列请求:http://sishuok.com/forum/blogPost/list/100.html(在网上找了很久没有找到关于队列请求的学习素材,谢谢这篇文章,这里仅作一下笔记)

模拟这样一种场景,餐馆中,有多个服务员为多个顾客点菜,其中处理这些菜单的多个厨师

虽然这里有很多个“多个”,先不要方,队列为我们提供了很大帮助。首先,我们理清一下思路:

1、谁是请求者——服务员Waiter类

2、命令是什么——我们这里准备两个命令:青龙过江Ccommand1类,葱爆大肠Command2类,他们共同实现Command抽象,场景是需要宏命令MenuCommand的,因为服务员是将一组菜单下单,而不是一个一个命令下单

3、谁是执行者——在上一个遥控器的例子中,命令需要一个成员变量,即实体类,他们知道如何去执行,命令让实体类去真正执行,在这里,实体类应该是厨师HotCook类。4、Command应该怎么写——我们可以提供一个接口,CookApi来描述厨师的烹饪方法,上面遥控器的例子并没有为实体类提供一个统一的接口,因为他们的行为并不一定一样,现在这里厨师的行为是一样的,是cook(),那么,既然统一了,很明显,Command抽象里面就可以提供一个setCookApi的方法,为每一道命令设置一个厨师,当然命令还需要提供设置点菜的桌号来区别那一桌

5、队列CommandQueue在哪——用list来指定队列,注意他是同步并且是单例的,因为厨师在做菜的时候,他在干掉一桌菜的时间中刚好又来了一组菜单,这样子就冲突了

接下来看看代码:先看看命令类:(实体类在更下面)

public interface Command {
	//执行命令对应操作
	public void execute();
	//设置命令的接收者,这里是厨师,即实体类
	public void setCooApi(CookApi cookapi);
	//发起请求的桌号,即点菜的桌号
	public int getTableNum();
}

public class Command1 implements Command {
	//真正执行的人
	private CookApi cookApi;
	//桌号
	private int tableNum;
	public Command1(int tableNum) {
		this.tableNum = tableNum;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		this.cookApi.cook(tableNum, "青龙过江");
	}

	@Override
	public void setCooApi(CookApi cookapi) {
		// TODO Auto-generated method stub
		this.cookApi = cookapi;
	}
	@Override
	public int getTableNum() {
		// TODO Auto-generated method stub
		return this.tableNum;
	}
}

public class Command2 implements Command {
	private CookApi cookApi;
	private int tableNum;
	public Command2(int tableNum) {
		this.tableNum = tableNum;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		this.cookApi.cook(tableNum, "葱爆大肠");
	}

	@Override
	public void setCooApi(CookApi cookapi) {
		// TODO Auto-generated method stub
		this.cookApi = cookapi;
	}
	@Override
	public int getTableNum() {
		// TODO Auto-generated method stub
		return this.tableNum;
	}
}
单个命令是这样子的,他记录桌号、记录厨师,桌号在下命令的时候设置上去就好,并没有什么特别之处,我们使用了宏命令,看上去是这样子的:

public class MenuCommand implements Command {
	//记录组合本菜单的多道菜,相当于宏命令
	private Collection<Command> col = new ArrayList<Command>();
	//点菜,把菜品加入到菜单
	public void addCommand(Command cmd){
		col.add(cmd);
	}
	
	//这里服务员完成了点单动作,让菜单的菜进队列
	@Override
	public void execute() {
		CommandQueue.addMenu(this);
	}
	@Override
	public void setCooApi(CookApi cookapi) {	}
	@Override
	public int getTableNum() {return 0;}
	public Collection<Command> getCommands() {return col;}
}
队列作为连接服务员跟厨师的类,看上去是这样子的:其实宏命令输入到队列,服务员就完成了他的工作,已经没他的事了!然后就是厨师读取单例队列在while循环里面做菜

public class CommandQueue {
	//用来存储命令对象的队列
	public static List<Command> cmds = new ArrayList<Command>();
	
	//服务员传过来一个新的菜单,需要同步, 
    //因为同时会有很多的服务员传入菜单,而同时又有很多厨师在从队列里取值
	public synchronized static void addMenu(MenuCommand menu){
		for(Command cmd:menu.getCommands()){
			cmds.add(cmd);
		}
	}
	
	public synchronized static Command getOneCommand(){
		Command cmd = null;
		if(cmds.size()>0){
			cmd = cmds.get(0);
			cmds.remove(0);
		}
		return cmd;
	}
}
我们还缺个服务员:他把菜单扔到队列里面去

public class Waiter {
	private MenuCommand menuCommand = new MenuCommand();
	public void orderDish(Command cmd){  
        //添加到菜单中  
        menuCommand.addCommand(cmd);  
    }
	 public void orderOver(){  
	    this.menuCommand.execute();  
	 }
}
为了整理一下顺序流程,厨师我们后面看,现在我们知道关于厨师的一切只是在命令里面的cookapi,他提供cook的方法,我们现在开始点单,先看看main方法:

public class Client {
	public static void main(String[] args) {
		CookManager.runCookManager();
		//为了简单,直接用循环模拟多个桌号点菜  
		//多个服务员给多号桌点菜
        for(int i = 0;i<5;i++){  
            //创建服务员  
            Waiter waiter = new Waiter();  
            //创建命令对象,就是要点的菜  
            Command a = new Command1(i);  
            Command b = new Command2(i);  
  
            //点菜,就是把这些菜让服务员记录下来  
            waiter.orderDish(a);  
            waiter.orderDish(b);  
  
            //点菜完毕  
            waiter.orderOver();  
        }         
    }
}
先看for循环,第一行是厨师的管理器,先撇开,看for循环,他定了5张桌子号,同时为每个桌子号提供了5个服务员,每张桌子点了两个命令,当然这里是为了方便才这样子干,你也可以自己定不同的桌子号和服务员,组合不同的命令

当服务员点单完成的时候就是队列进队完毕的时候,这时候服务员已经没事了,我们之前说过,订单完成了,厨师就会在无限循环里面取命令同时煮菜,这个就是CookManager的作用,他启动了厨师线程,好了,流程是这样子,看看执行者怎么执行

public interface CookApi {
	//每个厨师都有一个cook方法
	public void cook(int tableNum,String name);
}

//为什么要实现runable接口:我们这样想,每一个厨师都是一个线程
//run方法就是他们在菜单取出一道菜来煮的方法
public class HotCook implements CookApi, Runnable {
	//厨师的名字,分辨这些菜谁做的
	private String name;
	public HotCook(String name) {
		super();
		this.name = name;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			//厨师一直等待有没有菜,有就煮,有就煮,没有就无限等待
			//获取一道菜的命令
			Command cmd = CommandQueue.getOneCommand();
			if(cmd!=null){
				//开始煮菜,首先为这个命令标识上是谁做的
				cmd.setCooApi(this);
				//执行这道命令,很明显,这道命令执行是调用了set进去的厨师的cook方法
				cmd.execute();
			}
		}
	}

	@Override
	public void cook(int tableNum, String name) {
		int cookTime = (int) (20*Math.random());
		System.out.println(this.name+"厨师正在为"+tableNum+"号桌子做:"+name);
		try {
			Thread.sleep(cookTime);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name+"厨师为"+tableNum+"号桌子做好了"+name+"共耗时="+cookTime+"秒");
	}
}

//当然是用来管理所有厨师的
public class CookManager {
	private static boolean runFlag = false;
	public static void runCookManager(){  
        if(!runFlag){  
            runFlag = true;  
            //创建三位厨师  
            HotCook cook1 = new HotCook("张三");  
            HotCook cook2 = new HotCook("李四");  
            HotCook cook3 = new HotCook("王五");  
  
            //启动他们的线程  
            Thread t1 = new Thread(cook1);  
            t1.start();  
            Thread t2 = new Thread(cook2);  
            t2.start();  
            Thread t3 = new Thread(cook3);  
            t3.start();  
        }  
    }
}
至此,队列请求的例子完毕

head first 设计模式里面是这样子说队列请求的:想象一个工作队列,你在某一端添加命令,然后另一端是线程,线程执行下面的动作:从队列中取出一个命令,调用他的execute方法,等这个调用完成,将命令丢弃,再取出下一个命令

应用二:日志请求

某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后重新调用这些动作恢复到之前的状态,这就是动机,网上找到一篇代码,谢谢大神对我这种初学者的分享,仅学习笔记之用:http://blog.csdn.net/lovelion/article/details/8806643

其中,我们可以用一个list来干这个事,我们的日志文件只保存命令文本,记录一步步的操作,除了增加他的记录,绝不会减少记录,即你在程序里面执行了删除的命令,他记录的是删除命令的本身,而不是让日志文件返回上一条记录,而是增加一条删除记录操作(不知道我能表达清楚了没。。)

这里的存储是把所有操作完后保存到list,再保存list,然而我有一个想法是可以执行一个命令,再追加保存这个命令,尽量做到实时保存,具体的方法就不去研究了,如果有更好想法请不吝赐教

我对这些代码做了小部分修改,使用内部类的方式去实现:

这是工具类

public class FileUtil {
	//将命令集合写入日志文件  
    public static void writeCommands(ArrayList commands) {  
        try {  
            FileOutputStream file = new FileOutputStream("config.log");  
            //创建对象输出流用于将对象写入到文件中  
            ObjectOutputStream objout = new ObjectOutputStream(new BufferedOutputStream(file));  
            //将对象写入文件  
            objout.writeObject(commands);  
            objout.close();  
            }catch(Exception e) {e.printStackTrace();} 
    }
    
  //从日志文件中提取命令集合  
    public static ArrayList readCommands() {
    	ArrayList<Command> commands = new ArrayList<Command>();
        try {  
            FileInputStream file = new FileInputStream("config.log");  
            //创建对象输入流用于从文件中读取对象  
            ObjectInputStream objin = new ObjectInputStream(new BufferedInputStream(file)); 
            //将文件中的对象读出并转换为ArrayList类型  
            commands = (ArrayList)objin.readObject();  
            objin.close();  
            }catch(Exception e){e.printStackTrace();}   
        return commands;
    }
}
接着,我们需要真正干活的执行者——实体类:

//这算是实体类,真正的执行者
public class ConfigOperator implements Serializable{
	public void insert(String args) {System.out.println("增加新节点:" + args);}    
    public void modify(String args) {System.out.println("修改节点:" + args);}  
    public void delete(String args) {System.out.println("删除节点:" + args);}
}

我们的命令:

public abstract class Command implements Serializable{
	protected String name; //命令名称  
    protected String args; //命令参数
    
    public Command(String name) {this.name = name;}  
    public String getName() {return this.name;}  
    public void setName(String name) {this.name = name;}

    //声明两个抽象的执行方法execute()
    //Command并不一定是interface,正如上面使用内部类的例子一样,可以是提供一个模版方法让子类实现它
    public abstract void execute(String args);  
    public abstract void execute();
}


public class Controller implements Serializable{
	private ConfigOperator operator = new ConfigOperator();
	
	//增加命令类:具体命令  
	class InsertCommand extends Command implements Serializable{  
	    public InsertCommand(String name) {super(name);}     
	    public void execute(String args) {  
	        this.args = args;
	        operator.insert(args);  
	    }  
	    public void execute(){
	    	operator.insert(this.args);  
	    }  
	}
	
	//修改命令类:具体命令  
	class ModifyCommand extends Command implements Serializable{  
	    public ModifyCommand(String name) {super(name);}  
	    public void execute(String args) {  
	        this.args = args;  
	        operator.modify(args);  
	    }  
	      
	    public void execute() {  
	    	operator.modify(this.args);  
	    }  
	} 
	
	class DeleteCommand extends Command implements Serializable{  
	    public DeleteCommand(String name) {super(name);}  
	    public void execute(String args) {  
	        this.args = args;  
	        operator.delete(args);  
	    }
	    public void execute() {  
	    	operator.delete(this.args);
	    }  
	} 
}

注意内部类是怎么new出来的

接着遥控器:

public class ConfigSettingWindow {
	//定义一个集合来存储每一次操作时的命令对象  
    private ArrayList<Command> commands = new ArrayList<Command>();
    private Command command;
    
    //注入具体命令对象  
    public void setCommand(Command command) {this.command = command;}
    
    //执行配置文件修改命令,同时将命令对象添加到命令集合中  
    public void call(String args) {  
        command.execute(args);  
        commands.add(command);  
    }
    
    //记录请求日志,生成日志文件,将命令集合写入日志文件  
    public void save() {  
        FileUtil.writeCommands(commands);  
    }
    
    //从日志文件中提取命令集合,并循环调用每一个命令对象的execute()方法来实现配置文件的重新设置  
    public void recover() {  
    	ArrayList list;  
    	list = FileUtil.readCommands();  

    	for (Object obj : list) {  
    		((Command)obj).execute();  
    	}  
    }  
}
客户调用:
public class Client {
	public static void main(String[] args) {
		ConfigSettingWindow csw = new ConfigSettingWindow(); //定义请求发送者  
		Command command; //定义命令对象  
		Controller controller = new Controller();

		//四次对配置文件的更改  
		command = controller.new InsertCommand("add");   
		csw.setCommand(command);  
		csw.call("test01");
 
		command = controller.new ModifyCommand("modify");    
		csw.setCommand(command);  
		csw.call("test02");  

		System.out.println("保存配置");  
		csw.save();  

		System.out.println("恢复配置"); 
		csw.recover();
	}
}

整篇代码下来不难,其实我们读到概念是不懂得,但是在例子面前也不过是这么一回事,多用还可以发掘出其他的扩展


关于命令模式的最后一点思考:最近在学校学习关于安卓的课程,当安卓请求后台的数据的时候,我们能不能也使用命令模式呢?比如手机是一个遥控器,当我按下一个button的时候相当于发出一个请求,而真正地执行者并不在安卓端,我们只需要构造一个遥控器的类、构造命令类去请求数据,在命令类里提供一个execute方法,通过线程并返回json数据,再在前台解析。当然,是不是有这样的一种设计,让我们我们开一条无线循环的线程,在安卓后台等待请求进队,进队后执行交互,得到返回数据?


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:11225次
    • 积分:205
    • 等级:
    • 排名:千里之外
    • 原创:10篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条
    文章存档