请求发送者与接收者解耦——命令模式(二)

3 完整解决方案

为了降低功能键与功能处理类之间的耦合度,让用户可以自定义每一个功能键的功能,Sunny软件公司开发人员使用命令模式来设计“自定义功能键”模块,其核心结构如图4所示:

请求发送者与接收者解耦——命令模式(二) - 图1

图4 自定义功能键核心结构图

在图4中,FBSettingWindow是“功能键设置”界面类,FunctionButton充当请求调用者,Command充当抽象命令类,MinimizeCommand和HelpCommand充当具体命令类,WindowHanlder和HelpHandler充当请求接收者。完整代码如下所示:

  1. import java.util.*;
  2. //功能键设置窗口类
  3. class FBSettingWindow {
  4. private String title; //窗口标题
  5. //定义一个ArrayList来存储所有功能键
  6. private ArrayList<FunctionButton> functionButtons = new ArrayList<FunctionButton>();
  7. public FBSettingWindow(String title) {
  8. this.title = title;
  9. }
  10. public void setTitle(String title) {
  11. this.title = title;
  12. }
  13. public String getTitle() {
  14. return this.title;
  15. }
  16. public void addFunctionButton(FunctionButton fb) {
  17. functionButtons.add(fb);
  18. }
  19. public void removeFunctionButton(FunctionButton fb) {
  20. functionButtons.remove(fb);
  21. }
  22. //显示窗口及功能键
  23. public void display() {
  24. System.out.println("显示窗口:" + this.title);
  25. System.out.println("显示功能键:");
  26. for (Object obj : functionButtons) {
  27. System.out.println(((FunctionButton)obj).getName());
  28. }
  29. System.out.println("------------------------------");
  30. }
  31. }
  32. //功能键类:请求发送者
  33. class FunctionButton {
  34. private String name; //功能键名称
  35. private Command command; //维持一个抽象命令对象的引用
  36. public FunctionButton(String name) {
  37. this.name = name;
  38. }
  39. public String getName() {
  40. return this.name;
  41. }
  42. //为功能键注入命令
  43. public void setCommand(Command command) {
  44. this.command = command;
  45. }
  46. //发送请求的方法
  47. public void onClick() {
  48. System.out.print("点击功能键:");
  49. command.execute();
  50. }
  51. }
  52. //抽象命令类
  53. abstract class Command {
  54. public abstract void execute();
  55. }
  56. //帮助命令类:具体命令类
  57. class HelpCommand extends Command {
  58. private HelpHandler hhObj; //维持对请求接收者的引用
  59. public HelpCommand() {
  60. hhObj = new HelpHandler();
  61. }
  62. //命令执行方法,将调用请求接收者的业务方法
  63. public void execute() {
  64. hhObj.display();
  65. }
  66. }
  67. //最小化命令类:具体命令类
  68. class MinimizeCommand extends Command {
  69. private WindowHanlder whObj; //维持对请求接收者的引用
  70. public MinimizeCommand() {
  71. whObj = new WindowHanlder();
  72. }
  73. //命令执行方法,将调用请求接收者的业务方法
  74. public void execute() {
  75. whObj.minimize();
  76. }
  77. }
  78. //窗口处理类:请求接收者
  79. class WindowHanlder {
  80. public void minimize() {
  81. System.out.println("将窗口最小化至托盘!");
  82. }
  83. }
  84. //帮助文档处理类:请求接收者
  85. class HelpHandler {
  86. public void display() {
  87. System.out.println("显示帮助文档!");
  88. }
  89. }

为了提高系统的灵活性和可扩展性,我们将具体命令类的类名存储在配置文件中,并通过工具类XMLUtil来读取配置文件并反射生成对象,XMLUtil类的代码如下所示:

  1. import javax.xml.parsers.*;
  2. import org.w3c.dom.*;
  3. import org.xml.sax.SAXException;
  4. import java.io.*;
  5. public class XMLUtil {
  6. //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象,可以通过参数的不同返回不同类名节点所对应的实例
  7. public static Object getBean(int i) {
  8. try {
  9. //创建文档对象
  10. DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
  11. DocumentBuilder builder = dFactory.newDocumentBuilder();
  12. Document doc;
  13. doc = builder.parse(new File("config.xml"));
  14. //获取包含类名的文本节点
  15. NodeList nl = doc.getElementsByTagName("className");
  16. Node classNode = null;
  17. if (0 == i) {
  18. classNode = nl.item(0).getFirstChild();
  19. }
  20. else {
  21. classNode = nl.item(1).getFirstChild();
  22. }
  23. String cName = classNode.getNodeValue();
  24. //通过类名生成实例对象并将其返回
  25. Class c = Class.forName(cName);
  26. Object obj = c.newInstance();
  27. return obj;
  28. }
  29. catch(Exception e){
  30. e.printStackTrace();
  31. return null;
  32. }
  33. }
  34. }

配置文件config.xml中存储了具体建造者类的类名,代码如下所示:

  1. <?xml version="1.0"?>
  2. <config>
  3. <className>HelpCommand</className>
  4. <className>MinimizeCommand</className>
  5. </config>
  6. 编写如下客户端测试代码:
  7. [java] view plain copy
  8. class Client {
  9. public static void main(String args[]) {
  10. FBSettingWindow fbsw = new FBSettingWindow("功能键设置");
  11. FunctionButton fb1,fb2;
  12. fb1 = new FunctionButton("功能键1");
  13. fb2 = new FunctionButton("功能键1");
  14. Command command1,command2;
  15. //通过读取配置文件和反射生成具体命令对象
  16. command1 = (Command)XMLUtil.getBean(0);
  17. command2 = (Command)XMLUtil.getBean(1);
  18. //将命令对象注入功能键
  19. fb1.setCommand(command1);
  20. fb2.setCommand(command2);
  21. fbsw.addFunctionButton(fb1);
  22. fbsw.addFunctionButton(fb2);
  23. fbsw.display();
  24. //调用功能键的业务方法
  25. fb1.onClick();
  26. fb2.onClick();
  27. }
  28. }

编译并运行程序,输出结果如下:

  1. 显示窗口:功能键设置
  2. 显示功能键:
  3. 功能键1
  4. 功能键1
  5. ------------------------------
  6. 点击功能键:显示帮助文档!
  7. 点击功能键:将窗口最小化至托盘!

如果需要修改功能键的功能,例如某个功能键可以实现“自动截屏”,只需要对应增加一个新的具体命令类,在该命令类与屏幕处理者(ScreenHandler)之间创建一个关联关系,然后将该具体命令类的对象通过配置文件注入到某个功能键即可,原有代码无须修改,符合“开闭原则”。在此过程中,每一个具体命令类对应一个请求的处理者(接收者),通过向请求发送者注入不同的具体命令对象可以使得相同的发送者对应不同的接收者,从而实现“将一个请求封装为一个对象,用不同的请求对客户进行参数化”,客户端只需要将具体命令对象作为参数注入请求发送者,无须直接操作请求的接收者。