命令模式:
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();
}
}
执行结果:
增加新的节点: 网站首页
修改节点: 端口号
增加新的节点: 网站首页
增加新的节点: 端口号
================================
保存配置
================================
恢复配置
增加新的节点: 网站首页
修改节点: 端口号
增加新的节点: 网站首页
增加新的节点: 端口号