2)完整解决方案
1.结构图
FBSettingWindow是"功能键设置"界面类,FunctionButton充当请求调用者,Command充当抽象命令类,MinimizeCommand和HelpCommand充当具体命令类,WindowHanlder和HelpHandler充当请求接收者。
2.代码实现
import lombok.Data;
import java.util.*;
//功能键设置窗口类
@Data
public class FBSettingWindow {
//窗口标题
private String title;
//定义一个ArrayList来存储所有功能键
private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();
public FBSettingWindow(String title) {
this.title = 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 (Object obj : functionButtons) {
System.out.println(((FunctionButton)obj).getName());
}
System.out.println("------------------------------");
}
}
import lombok.Data;
//功能键类:请求发送者
@Data
class FunctionButton {
//功能键名称
private String name;
//维持一个抽象命令对象的引用
private Command command;
public FunctionButton(String name) {
this.name = name;
}
//发送请求的方法
public void onClick() {
System.out.print("点击功能键:");
command.execute();
}
}
//抽象命令类
abstract class Command {
public abstract void execute();
}
//帮助命令类:具体命令类
public class HelpCommand extends Command {
//维持对请求接收者的引用
private HelpHandler hhObj;
public HelpCommand() {
hhObj = new HelpHandler();
}
//命令执行方法,将调用请求接收者的业务方法
public void execute() {
hhObj.display();
}
}
//最小化命令类:具体命令类
public class MinimizeCommand extends Command {
//维持对请求接收者的引用
private WindowHanlder whObj;
public MinimizeCommand() {
whObj = new WindowHanlder();
}
//命令执行方法,将调用请求接收者的业务方法
public void execute() {
whObj.minimize();
}
}
//帮助文档处理类:请求接收者
public class HelpHandler {
public void display() {
System.out.println("显示帮助文档!");
}
}
//窗口处理类:请求接收者
public class WindowHanlder {
public void minimize() {
System.out.println("将窗口最小化至托盘!");
}
}
public class Client {
public static void main(String args[]) {
FBSettingWindow fbsw = new FBSettingWindow("功能键设置");
FunctionButton fb1,fb2;
fb1 = new FunctionButton("功能键1");
fb2 = new FunctionButton("功能键2");
Command command1,command2;
command1 = new HelpCommand();
command2 = new MinimizeCommand();
//将命令对象注入功能键
fb1.setCommand(command1);
fb2.setCommand(command2);
fbsw.addFunctionButton(fb1);
fbsw.addFunctionButton(fb2);
fbsw.display();
//调用功能键的业务方法
fb1.onClick();
fb2.onClick();
}
}
注意:
每一个具体命令类对应一个请求的处理者(接收者),通过向请求发送者注入不同的具体命令对象可以使得相同的发送者对应不同的接收者,从而实现"将一个请求封装为一个对象,用不同的请求对客户进行参数化"。
3)命令队列的实现
1.概述
将多个请求排队,当一个请求发送者发送一个请求时,将不止一个请求接收者产生响应,这些请求接收者将逐个执行业务方法,完成对请求的处理。
2.方案
增加一个CommandQueue类,由该类负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。
3.代码实现
命令队列类
import java.util.*;
public class CommandQueue {
//定义一个ArrayList来存储命令队列
private ArrayList<Command> commands = new ArrayList<Command>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
//循环调用每一个命令对象的execute()方法
public void execute() {
for (Object command : commands) {
((Command)command).execute();
}
}
}
请求发送者类Invoker将针对CommandQueue编程
public class Invoker {
//维持一个CommandQueue对象的引用
private CommandQueue commandQueue;
//构造注入
public Invoker(CommandQueue commandQueue) {
this. commandQueue = commandQueue;
}
//设值注入
public void setCommandQueue(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
//调用CommandQueue类的execute()方法
public void call() {
commandQueue.execute();
}
}
客户端类
import com.xu.model.command.demo02.Invoker;
public class Client02 {
public static void main(String args[]) {
CommandQueue commandQueue = new CommandQueue();
Command command1,command2;
command1 = new HelpCommand();
command2 = new MinimizeCommand();
commandQueue.addCommand(command1);
commandQueue.addCommand(command2);
Invoker invoker = new Invoker(commandQueue);
invoker.call();
}
}
命令队列可以对一组对象(命令)进行批量处理,当一个发送者发送请求后,将有一系列接收者对请求作出响应,如果请求接收者的接收次序没有严格的先后次序,还可以使用多线程技术来并发调用命令对象的execute()方法,从而提高程序的执行效率。
4)撤销操作的实现
1.方案
通过在命令类中增加一个逆向操作(Undo)来实现。
通过保存对象的历史状态来实现,可以使用备忘录模式(Memento Pattern)。
2.结构图
CalculatorForm充当请求发送者,实现了数据求和功能的加法类Adder充当请求接收者,界面类可间接调用加法类中的add()方法实现加法运算,并且提供了可撤销加法运算的undo()方法。
3.代码实现
//抽象命令类
abstract class AbstractCommand {
//声明命令执行方法execute()
public abstract int execute(int value);
//声明撤销方法undo()
public abstract int undo();
}
//具体命令类
public class ConcreteCommand extends AbstractCommand {
private Adder adder = new Adder();
private int value;
//实现抽象命令类中声明的execute()方法,调用加法类的加法操作
public int execute(int value) {
this.value=value;
return adder.add(value);
}
//实现抽象命令类中声明的undo()方法,通过加一个相反数来实现加法的逆向操作
public int undo() {
return adder.add(-value);
}
}
//计算器界面类:请求发送者
public class CalculatorForm {
private AbstractCommand command;
public void setCommand(AbstractCommand command) {
this.command = command;
}
//调用命令对象的execute()方法执行运算
public void compute(int value) {
int i = command.execute(value);
System.out.println("执行运算,运算结果为:" + i);
}
//调用命令对象的undo()方法执行撤销
public void undo() {
int i = command.undo();
System.out.println("执行撤销,运算结果为:" + i);
}
}
//加法类:请求接收者
public class Adder {
//定义初始值为0
private int num = 0;
//加法操作,每次将传入的值与num作加法运算,再将结果返回
public int add(int value) {
num += value;
return num;
}
}
public class Client {
public static void main(String[] args) {
CalculatorForm form = new CalculatorForm();
AbstractCommand command;
command = new ConcreteCommand();
form.setCommand(command); //向发送者注入命令对象
form.compute(10);
form.compute(5);
form.compute(10);
form.undo();
}
}
注意:本实例中只能实现一步撤销操作,因为没有保存命令对象的历史状态,可以通过引入一个命令集合来存储每一次操作时命令的状态,从而实现多次撤销操作,除了Undo操作外,还可以采用类似的方式实现恢复(Redo)操作,即恢复所撤销的操作。
5)请求日志
1.概述
请求日志是将请求的历史记录保存下来,通常以日志文件(Log File)的形式存储在磁盘中。
2.适用场景
- 一旦系统发生故障,日志文件可以为系统提供一种恢复机制,在请求日志文件中可以记录用户对系统的每一步操作,从而让系统能够顺利恢复到某一个特定的状态。
- 请求日志也可以用于实现批处理,在一个请求日志文件中可以存储一系列命令对象,例如一个命令队列。
- 可以将命令队列中的所有命令对象都存储在一个日志文件中,每执行一个命令则从日志文件中删除一个对应的命令对象,防止因为断电或者系统重启等原因造成请求丢失,而且可以避免重新发送全部请求时造成某些命令的重复执行,只需读取请求日志文件,再继续执行文件中剩余的命令即可。
3.结构图
4.代码实现
import lombok.Data;
import java.io.*;
//抽象命令类,由于需要将命令对象写入文件,因此它实现了Serializable接口
@Data
abstract class Command implements Serializable {
//命令名称
protected String name;
//命令参数
protected String args;
//维持对接收者对象的引用
protected ConfigOperator configOperator;
public Command(String name) {
this.name = name;
}
public void setConfigOperator(ConfigOperator configOperator) {
this.configOperator = configOperator;
}
//声明两个抽象的执行方法execute()
public abstract void execute(String args);
public abstract void execute();
}
//增加命令类:具体命令
public class InsertCommand extends Command {
public InsertCommand(String name) {
super(name);
}
public void execute(String args) {
this.args = args;
configOperator.insert(args);
}
public void execute() {
configOperator.insert(this.args);
}
}
//修改命令类:具体命令
public class ModifyCommand extends Command {
public ModifyCommand(String name) {
super(name);
}
public void execute(String args) {
this.args = args;
configOperator.modify(args);
}
public void execute() {
configOperator.modify(this.args);
}
}
import lombok.Setter;
import java.util.ArrayList;
//配置文件设置窗口类:请求发送者
@Setter
public class ConfigSettingWindow {
//定义一个集合来存储每一次操作时的命令对象
private ArrayList<Command> commands = new ArrayList<Command>();
private 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();
}
}
}
import java.io.Serializable;
//配置文件操作类:请求接收者。
//由于ConfigOperator类的对象是Command的成员对象,它也将随Command对象一起写入文件,因此ConfigOperator也需要实现Serializable接口
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);
}
}
import java.io.*;
import java.util.ArrayList;
//工具类:文件操作类
public class FileUtil {
//将命令集合写入日志文件
public static void writeCommands(ArrayList commands) {
try {
FileOutputStream file = new FileOutputStream("/Users/hhx/Desktop/config.log");
//创建对象输出流用于将对象写入到文件中
ObjectOutputStream objout = new ObjectOutputStream(new BufferedOutputStream(file));
//将对象写入文件
objout.writeObject(commands);
objout.close();
} catch (Exception e) {
System.out.println("命令保存失败!");
e.printStackTrace();
}
}
//从日志文件中提取命令集合
public static ArrayList readCommands() {
try {
FileInputStream file = new FileInputStream("/Users/hhx/Desktop/config.log");
//创建对象输入流用于从文件中读取对象
ObjectInputStream objin = new ObjectInputStream(new BufferedInputStream(file));
//将文件中的对象读出并转换为ArrayList类型
ArrayList commands = (ArrayList) objin.readObject();
objin.close();
return commands;
} catch (Exception e) {
System.out.println("命令读取失败!");
e.printStackTrace();
return null;
}
}
}
public class Client {
public static void main(String[] args) {
//定义请求发送者
ConfigSettingWindow csw = new ConfigSettingWindow();
//定义命令对象
Command command;
//定义请求接收者
ConfigOperator co = new ConfigOperator();
//四次对配置文件的更改
command = new InsertCommand("增加");
command.setConfigOperator(co);
csw.setCommand(command);
csw.call("网站首页");
command = new InsertCommand("增加");
command.setConfigOperator(co);
csw.setCommand(command);
csw.call("端口号");
command = new ModifyCommand("修改");
command.setConfigOperator(co);
csw.setCommand(command);
csw.call("网站首页");
command = new ModifyCommand("修改");
command.setConfigOperator(co);
csw.setCommand(command);
csw.call("端口号");
System.out.println("----------------------------");
System.out.println("保存配置");
csw.save();
System.out.println("----------------------------");
System.out.println("恢复配置");
System.out.println("----------------------------");
csw.recover();
}
}
6)宏命令
1.概述
宏命令(Macro Command)又称为组合命令,它是组合模式和命令模式联用的产物。
宏命令是一个具体命令类,它拥有一个集合属性,在该集合中包含了对其他命令对象的引用。
2.原理
通常宏命令不直接与请求接收者交互,而是通过它的成员来调用接收者的方法,当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,还可以继续是宏命令。
执行一个宏命令将触发多个具体命令的执行,从而实现对命令的批处理。
3.结构图
7)总结
1.优点
-
由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦。
-
由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码。
-
可以比较容易地设计一个命令队列或宏命令(组合命令)。
-
为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
2.缺点
- 使用命令模式可能会导致系统有过多的具体命令类。
3.适用场景
-
系统需要将请求调用者和请求接收者解耦。
-
系统需要在不同的时间指定请求、将请求排队和执行请求,一个命令对象和请求的初始调用者可以有不同的生命期,可以通过该命令对象去调用请求接收者,可以通过请求日志文件等机制来实现。
-
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
-
系统需要将一组操作组合在一起形成宏命令。