行为型模式---命令模式

命令模式:

1. 定义: 将一个请求封装成一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作

2. 结构:

1) Command(抽象命令类):一般是一个抽象类或接口,声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作

2) ConcreteCommand(具体命令类): 具体命令类是抽象命令类的子类,对应具体的接收者对象,将接收者对象的动作绑定其中.在execute()方法时,将调用接收者对象的相关操作

3) Invoker(调用者):即请求发送者,通过命令对象来执行请求,一个调用者并不需要在设计时确定其接收者,它只与抽象命令类之间存在关联关系

4) Recevier(接收者):接收者执行与请求相关的操作,它具体实现业务处理

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开.每一个命令都是一个操作:

 

3. 结构图

4. 案例

5. 解决方案

实现代码

package com.zach.pattern.command;
/**
 * 抽象命令接口
 * @author Zach
 *
 */
public interface Command {
	
	public void execute();
}


package com.zach.pattern.command;

import java.util.ArrayList;
import java.util.List;

/**
 * 功能设置界面类
 * @author Zach
 *
 */
public class FBSettingWindow {
	
	private String title;
	
	//定义一个ArrayList存储所有的功能键
	private List<FunctionButton> functionButtons = new ArrayList<>();
	public FBSettingWindow(String title) {
		this.title = title;
	}
	
	public void setTitle(String title) {
		this.title = title;
	}
	
	public String getTitle(){
		return this.title;
	}
	
	public void addFunctionButton(FunctionButton fb) {
		functionButtons.add(fb);
	}
	
	public void removeFunctionButton(FunctionButton fb) {
		functionButtons.remove(fb);
	}
	
	public void display(){
		System.out.println("显示窗口: "+this.title);
		System.out.println("显示功能键: ");
		for (FunctionButton functionButton : functionButtons) {
			System.out.println(functionButton.getName());
		}
		System.out.println("==========================================");
		
	}
}


package com.zach.pattern.command;
/**
 * 请求调用者
 * @author Zach
 *
 */
public class FunctionButton {
	
	private Command command;
	private String name;
	public FunctionButton(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void setCommand(Command command) {
		this.command = command;
	}
	
	public void onClick(){
		System.out.println("发送点击请求");
		command.execute();
	}
}



package com.zach.pattern.command;

public class HelpCommand implements Command{
	
	private HelpHandler helpHandler; //维持一个请求接收者的引用
	
	 public HelpCommand() {
		 helpHandler = new HelpHandler(); 
	}
	 
	@Override
	public void execute() {
		helpHandler.display();
	}
	

}


package com.zach.pattern.command;
/**
 * 请求接收者: 帮助文档处理类
 * @author Zach
 *
 */
public class HelpHandler {
	
	public void display() {
		System.out.println("显示文档帮助!");
	}
}

package com.zach.pattern.command;
/**
 * 具体命令类
 * @author Zach
 *
 */
public class MinimizeCommand implements Command{
	
	private WindowHandler wh; //维持一个请求接收者的引用
	
	 public MinimizeCommand() {
		 wh = new WindowHandler();
	}
	@Override
	public void execute() {
		wh.minimize();
	}
	
	
}


package com.zach.pattern.command;
/**
 * 请求接收者 : 窗口处理类
 * @author Zach
 *
 */
public class WindowHandler {
	
	public void minimize() {
		System.out.println("最小化窗口");
	}
}


package com.zach.pattern.command;

public class Client {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		FBSettingWindow fbs = new FBSettingWindow("功能键设置");
		
		FunctionButton fb1 = new FunctionButton("功能键1");
		FunctionButton fb2 = new FunctionButton("功能键2");
		
		//可通过配置文件配置具体命令类,通过反射读取;这里不展示;
		Command command1 = (Command) Class.forName("com.zach.pattern.command.HelpCommand").newInstance();
		Command command2 = (Command) Class.forName("com.zach.pattern.command.MinimizeCommand").newInstance();
		
		//将命令注入功能键
		fb1.setCommand(command1);
		fb2.setCommand(command2);
		
		fbs.addFunctionButton(fb1);
		fbs.addFunctionButton(fb2);
		fbs.display();
		
		//调用功能键的业务方法
		fb1.onClick();
		fb2.onClick();
		
	}
}

执行结果
显示窗口: 功能键设置
显示功能键: 
功能键1
功能键2
==========================================
发送点击请求
显示文档帮助!
发送点击请求
最小化窗口

6. 命令队列的实现

有时需要将多个请求排队,当一个请求发送者发送一个请求时,不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成请求的处理,此时可以通过命令队列实现

命令队列实现方式之一就是增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者

CommandQueue实现代码

package com.zach.pattern.command.queue;

import java.util.ArrayList;
import java.util.List;

import com.zach.pattern.command.Command;

/**
 * 命令队列
 * @author Zach
 *
 */
public class CommandQueue {
	
	//定义一个ArrayList存储命令队列
	private List<Command> commands = new ArrayList<>();
	
	public void addCommand(Command command) {
		commands.add(command);
	}
	
	public void removeCommand(Command command) {
		commands.remove(command);
	}
	
	//循环调用每一个命令对象的execute()方法
	public void execute() {
		for (Command command : commands) {
			command.execute();
		}
	}
}


package com.zach.pattern.command.queue;

/**
 * 请求发起者
 * @author Zach
 *
 */
public class Invoker {
	private CommandQueue commandQueue; //维持一个CommandQueue对象的引用
	
	public Invoker(CommandQueue commandQueue) {
		this.commandQueue = commandQueue;
	}
	
	public void setCommandQueue(CommandQueue commandQueue){
		this.commandQueue = commandQueue;
	}
	
	//调用CommandQueue类的execute()方法
	public void call() {
		commandQueue.execute();
	}
}

7. 撤销操作

在命令模式中如果需要撤销请求,可通过在命令类中增加一个逆向操作来实现(还可以通过保存对象的历史状态来实现撤销)

1) 案例

2) 实现方案

计算器界面类CalculatorForm充当请求发送者,实现数据求和功能的加法类,Adder充当请求接收者

3) 代码实现

package com.zach.pattern.command.repeal;
/**
 * 抽象命令类
 * @author Zach
 *
 */
public abstract class AbstractCommand {
	
	public abstract int execute(int value);
	
	public abstract int undo();
}

package com.zach.pattern.command.repeal;
/**
 * 具体命令类
 * @author Zach
 *
 */
public class AddCommand extends AbstractCommand {
	
	private Adder adder = new Adder(); //维持一个对命令接收者的引用
	private int value;
	
	@Override
	public int execute(int value) {
		this.value = value;
		return adder.add(value);
	}

	@Override
	public int undo() {
		//通过逆向操作实现撤销的效果
		return adder.add(-value);
	}

}


package com.zach.pattern.command.repeal;
/**
 * 请求接收者: 加法类
 * @author Zach
 *
 */
public class Adder {
	
	private int num = 0;
	
	public int add(int value){
		return num += value;
	}
}


package com.zach.pattern.command.repeal;
/**
 * 请求发送者: 计算器界面类
 * @author Zach
 *
 */
public class CalculatorForm {
	private AbstractCommand command;

	public void setCommand(AbstractCommand command) {
		this.command = command;
	}
	
	public void compute(int value) {
		int i = command.execute(value);
		System.out.println("执行运算,结果为: "+i);
	}
	
	
	public void undo(){
		int i = command.undo();
		System.out.println("执行撤销,结果为: "+i);
	}
	
	
}

package com.zach.pattern.command.repeal;
/**
 * 客户端类
 * @author Zach
 *
 */
public class Client {
	public static void main(String[] args) {
		
		CalculatorForm cf = new CalculatorForm();
		AbstractCommand ac = new AddCommand();
		
		cf.setCommand(ac);
		cf.compute(2);
		cf.compute(20);
		cf.undo();
		cf.undo();
	}
	
}

运行结果:
执行运算,结果为: 2
执行运算,结果为: 22
执行撤销,结果为: 2
执行撤销,结果为: -18

4) 注意点: 在本实例中只能实现一步撤销操作,因为没有保存命令对象的历史状态,可通过引入一个命令集合或其他方法来存储每一次操作时命令状态,实现多次撤销操作;或者是实现恢复操作(Redo)

8. 请求日志

请求日志就是将请求的历史记录保存下来,通常以日志文件的形式永久存储在计算机中.常用的功能:

       1)  为系统提供一种恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统能够顺利恢复到某一个特定的状态

        2)  用于实现批处理,在一个请求日志文件中可以储存一系列命令对象

        3)  可以将命令队列中所有的命令对象存储在一个日志文件中,每执行一个命令则从日志文件中删除一个对应的命令对象,防止因为断电或者系统重启等原因造成请求丢失;而且可避免重新发送全部请求时造成某些命令的重复执行,只需读取请求日志文件,再继续执行文件中剩余的命令即可

实现请求日志时,可以将命令对象实现java.io.Serializable接口,通过序列化写到日志文件中

案例

1) 解决方案

2) 代码实现

package com.zach.pattern.command.requestLog;

import java.io.Serializable;
/**
 * 抽象命令类
 * @author Zach
 *
 */
public abstract class Command implements Serializable {
	/**
	 * 
	 */
	protected static final long serialVersionUID = 1L;
	protected String name;
	protected String args;  //命令参数
	protected ConfigOperator configOperator;  //维持对接收者对象的引用
	
	
	public Command(String name) {
		this.name = name;
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
	}


	public void setConfigOperator(ConfigOperator configOperator) {
		this.configOperator = configOperator;
	}
	
	//声明两个抽象的执行方法
	public abstract void execute(String args);
	public abstract void execute();
	
}

package com.zach.pattern.command.requestLog;

import java.io.Serializable;
/**
 * 配置文件操作类,请求接收者
 * @author Zach
 *
 */
public class ConfigOperator implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public void insert(String args) {
		System.out.println("增加新的节点: "+args);
	}
	
	public void modify(String args) {
		System.out.println("修改节点: "+args);
	} 
	
	
}

package com.zach.pattern.command.requestLog;

import java.util.ArrayList;
import java.util.List;

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


package com.zach.pattern.command.requestLog;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * 文件操作类
 * @author Zach
 *
 */
public class FileUtil {
	
	//将命令集合写入日志文件
	public static void writeCommands(List<Command> commands) {
		try {
			FileOutputStream fos = new FileOutputStream("config.log");
			ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos));
			
			//将对象写入文件
			oos.writeObject(commands);
			oos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	//从日志文件中提取命令集合
	@SuppressWarnings({ "unchecked" })
	public static List<Command> readCommands() {
		
		try {
			FileInputStream fis = new FileInputStream("config.log");
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
			
			//将文件中的对象读出来并转换成ArrayList类型
			List<Command> commands = (ArrayList<Command>)ois.readObject();
			ois.close();
			return commands;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}

package com.zach.pattern.command.requestLog;

//增加命令类:具体命令
public class InsertCommand extends Command {
	

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public InsertCommand(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void execute(String args) {
		this.args = args;
		configOperator.insert(args);
		
	}

	@Override
	public void execute() {
		configOperator.insert(this.args);
	}


}


package com.zach.pattern.command.requestLog;

//修改命令类:具体命令
public class ModifyCommand extends Command {
	

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	public ModifyCommand(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void execute(String args) {
		this.args = args;
		configOperator.modify(args);
		
	}

	@Override
	public void execute() {
		configOperator.modify(this.args);
	}


}

package com.zach.pattern.command.requestLog;
/**
 * 客户端
 * @author Zach
 *
 */
public class Client {
	public static void main(String[] args) {
		ConfigSettingWindow csw = new ConfigSettingWindow();
		ConfigOperator co = new ConfigOperator();
		Command command = null;
		
		command = new InsertCommand("增加");
		command.setConfigOperator(co);
		csw.setCommad(command);
		csw.call("网站首页");
		
		command = new ModifyCommand("增加");
		command.setConfigOperator(co);
		csw.setCommad(command);
		csw.call("端口号");
		
		command = new InsertCommand("修改");
		command.setConfigOperator(co);
		csw.setCommad(command);
		csw.call("网站首页");
		
		command = new InsertCommand("修改");
		command.setConfigOperator(co);
		csw.setCommad(command);
		csw.call("端口号");
		
		System.out.println("================================");
		System.out.println("保存配置");
		csw.save();
		
		System.out.println("================================");
		System.out.println("恢复配置");
		csw.recover();
	}
}

执行结果:
增加新的节点: 网站首页
修改节点: 端口号
增加新的节点: 网站首页
增加新的节点: 端口号
================================
保存配置
================================
恢复配置
增加新的节点: 网站首页
修改节点: 端口号
增加新的节点: 网站首页
增加新的节点: 端口号

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值