完整案例
Sunny软件公司开发了一个网站配置文件管理工具,可以通过一个可视化界面对网站配置文件进行增删改等操作,该工具使用命令模式进行设计,结构如图所示:
现在Sunny软件公司开发人员希望将对配置文件的操作请求记录在日志文件中,如果网站重新部署,只需要执行保存在日志文件中的命令对象即可修改配置文件。
本实例完整代码如下所示:
//抽象命令类,由于需要将命令对象写入文件,因此它实现了Serializable接口 abstract class Command implements Serializable { protected String name; //命令名称 protected String args; //命令参数 protected ConfigOperator configOperator; //维持对接收者对象的引用 public Command(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public void setConfigOperator(ConfigOperator configOperator) { this.configOperator = configOperator; } //声明两个抽象的执行方法execute() public abstract void execute(String args); public abstract void execute(); }
//增加命令类:具体命令 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); } }
//修改命令类:具体命令 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); } }
//省略了删除命令类DeleteCommand // 配置文件操作类:请求接收者。由于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); } }
//配置文件设置窗口类:请求发送者 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(); } } }
//工具类:文件操作类 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) { System.out.println("命令保存失败!"); e.printStackTrace(); } } //从日志文件中提取命令集合 public static ArrayList readCommands() { try { FileInputStream file = new FileInputStream("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; } } }
编写如下客户端测试代码:
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(); } }
编译并运行程序,输出结果如下:
增加新节点:网站首页 增加新节点:端口号 修改节点:网站首页 修改节点:端口号 ---------------------------- 保存配置 ---------------------------- 恢复配置 ---------------------------- 增加新节点:网站首页 增加新节点:端口号 修改节点:网站首页 修改节点:端口号
命令模式的主要优点如下:
(1) 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
(2) 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
(3) 可以比较容易地设计一个命令队列或宏命令(组合命令)。
(4) 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
命令模式的主要缺点如下:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
在以下情况下可以考虑使用命令模式:
(1) 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
(2) 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
(3) 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
(4) 系统需要将一组操作组合在一起形成宏命令。