设计模式之禅之混编
命令模式+责任链模式
- 搬移UNIX命令
- 在Windows系统上使用UNIX命令
- UNIX命令
- 命令名
- 选项
- 操作数
- UNIX规定
- 命令名为小写字母
- 命令名、选项、操作树之间以空格分隔,空格数量不受限制
- 选项之间看可以组合使用,也可以单独拆分使用
- 选项以“-”开头
- 例
- ls简单列出一个目录下的文件
- ls-l详细列出目录下的文件
- ls-a列出目录下包含的隐藏文件,主要是点号(.)开头的文件
- ls-s列出文件的大小
- 针对ls命令族,要求如下:
- 每一个ls命令都有操作数,默认操作数为当前目录
- 选项不可重复
- 每个选项返回不同的结果,也就是说每个选项应该由不同的业务逻辑来处理
- 为提高扩展性,ls应该对外封闭,减少外界访问ls命令族内部细节的可能性
命令选模式
- 责任链模式
- 只需一个参数传递到链首,就可以获得有个结果,中间是如何传递的以及由哪个逻辑解析都不需要外界模块关系
- 命令族解析类图
-
- 类图解释
- ComandName抽象类,所有的地命令都继承该类,它就是责任链中的handle类,负责链表的控制
- 每个命令族都有一个独立的抽象类,因为每个命令族都有其独特的个性
- Context负责建立一条命令链,并返回首节点供高层模块调用
派发命令选模式
- 命令模式
- 命令的解析由责任链解析,但是如此多的命令还需要一个派发的角色,输入一个命令,不管后台由谁来解析,返回一个结果就成,这就要用到命令模式,命令模式负责协调各个正确地传递到各个责任链的首节点,这就是它的任务
- 类图
-
- 类图解析
- ClassUtils工具类:主要职责是根据一个接口、父类查找到所有的子类,在不考虑的应用中,使用该类可以带来非常好的扩展性
- CommandVO:是命令的值对象,它把一个命令解析为命令名、选项、操作数
- CommandEnum:是枚举类型,是命令配置文件
整合这个两个模式
- 类图
-
具体代码
- 项目结构
-
-
详细代码
-
AbstractLS.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public abstract class AbstractLS extends CommandName { // 默认参数 public final static String DEFAULT_PARAM = ""; //参数a public final static String A_PARAM="a"; //参数| public final static String L_PARAM="|"; }
-
ClassUtils.java
package com.peng.zm; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class ClassUtils { // 根据父类查找到所有的子类,默认情况下是子类和父类在同一个包名下 public static List<Class> getSonClass(Class fatherClass) { // 定义一个返回值 List<Class> returnClassList = new ArrayList<Class>(); // 获得包名称 String packageName = fatherClass.getPackage().getName(); // 获得包中的所有类 List<Class> packClasses = getClasses(packageName); // 判断是否是子类 for (Class c : packClasses) { if (fatherClass.isAssignableFrom(c) && !fatherClass.equals(c)) { returnClassList.add(c); } } return returnClassList; } // 从一个包中查出所有的类,在jar包中不能查找 private static List<Class> getClasses(String packageName) { ClassLoader classLoader = Thread.currentThread() .getContextClassLoader(); String path = packageName.replace('.', '/'); Enumeration<URL> resources = null; try { resources = classLoader.getResources(path); } catch (IOException e) { e.printStackTrace(); } List<File> dirs = new ArrayList<File>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } ArrayList<Class> classes = new ArrayList<Class>(); for (File directory : dirs) { classes.addAll(findClasses(directory, packageName)); } return classes; } private static List<Class> findClasses(File directory, String packageName) { List<Class> classes = new ArrayList<Class>(); if (!directory.exists()) { return classes; } File[] files = directory.listFiles(); for (File file : files) { if (file.isDirectory()) { assert !file.getName().contains(".") : classes .addAll(findClasses(file, packageName + "." + file.getName())); } else if (file.getName().endsWith(".class")) { try{ classes.add(Class.forName(packageName+"."+file.getName().substring(0,file.getName().length()-6))); }catch(ClassNotFoundException e){ e.printStackTrace(); } } } return classes; } }
-
Client.java
package com.peng.zm; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class Client { public static void main(String[] args) throws IOException { Invoker invoker = new Invoker(); while (true) { System.out.println("#"); String input = (new BufferedReader(new InputStreamReader(System.in))) .readLine(); // 输入exit或quit则退出 if (input.equals("quit") || input.equals("exit")) { return; } System.out.println("执行结果:" + invoker.exec(input)); } } }
-
Command.java
package com.peng.zm; import java.util.ArrayList; import java.util.List; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public abstract class Command { public abstract String execute(CommandVO vo); // 建立链表 protected final List<? extends CommandName> buildChain( Class<? extends CommandName> abstractClass) { // 取出所有命令下的子类 List<Class> classes = ClassUtils.getSonClass(abstractClass); // 存放命令的实例,并建立链表关系 List<CommandName> commandNameList = new ArrayList<CommandName>(); for (Class c : classes) { CommandName commandName = null; try { // 产生实例 commandName = (CommandName) Class.forName(c.getName()) .newInstance(); } catch (Exception e) { // 异常处理 } // 建立链表 if (commandNameList.size() > 0) { commandNameList.add(commandName); } } return commandNameList; } }
-
CommandEnum.java
package com.peng.zm; import java.util.ArrayList; import java.util.List; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public enum CommandEnum { ls("com.peng.zm.LSCommand"); private String value = ""; // 定义构造函数,目的是Data(value)类型相匹配 private CommandEnum(String value) { this.value = value; } public String getValue() { return value; } // 方式所有的enum对象 public static List<String> getNames() { CommandEnum[] commandEnum = CommandEnum.values(); List<String> names = new ArrayList<String>(); for (CommandEnum commandEnum2 : commandEnum) { names.add(commandEnum2.name()); } return names; } }
-
CommandName.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description 抽象命名类 */ public abstract class CommandName { private CommandName nextOperator; public final String handleMessage(CommandVO vo) { // 处理结果 String result = ""; // 判断是否是自己处理的参数 if (vo.getParam().size() == 0 || vo.getParam().contains(this.getOperateParam())) { result = this.echo(vo); } else { if (this.nextOperator != null) { result = this.nextOperator.handleMessage(vo); } else { result = "命令无法执行"; } } return result; } // 设置剩余参数有谁来处理 public void setNext(CommandName _operator) { this.nextOperator = _operator; } // 每个处理者都要处理一个后缀参数 protected abstract String getOperateParam(); // 每个处理者都要实现必须处理任务 protected abstract String echo(CommandVO vo); }
-
CommandVO.java
package com.peng.zm; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class CommandVO { // 定义参数名与参数的分隔符,一般是空格 public final static String DIVIDE_FLAG = " "; // 定义参数前的符号,Unix一般是- public final static String PREFIX = "-"; // 命令名 private String commandName = ""; // 参数列表 private ArrayList<String> paramList = new ArrayList<String>(); // 操作数列表 private ArrayList<String> dataList = new ArrayList<String>(); // 通过构造函数来传递进来 public CommandVO(String commandStr) { // 常规判断 if (commandStr != null && commandStr.length() != 0) { // 根据分隔符号拆分出执行符号 String[] complexStr = commandStr.split(CommandVO.DIVIDE_FLAG); // 第一个参数是执行符号 this.commandName = complexStr[0]; // 把参数放到List for (int i = 1; i < complexStr.length; i++) { String str = complexStr[i]; // 包含前缀符号,认为是参数 if (str.indexOf(CommandVO.PREFIX) == 0) { this.paramList .add(str.replace(CommandVO.PREFIX, "").trim()); } else { // 操作数列表 this.dataList.add(str.trim()); } } } else { // 传递的命令错误 System.out.println("命令解析失败,必须传递一个命令才能执行!"); } } // 得到命令名 public String getCommandName() { return this.commandName; } /** * 功能: 返回值类型: 参数列表: * * @return */ public List<String> getParam() { // 为了方便处理空参数 if (this.paramList.size() == 0) { this.paramList.add(""); } return new ArrayList(new HashSet(this.paramList)); } // 获得操作数 public ArrayList<String> getaData() { return this.dataList; } /** * 功能: 返回值类型: 参数列表: * * @return */ public String formatDate() { return this.getaData().toString(); } }
-
FileManager.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class FileManager { // ls命令 public static String ls(String path) { return "file1\nfile2\nfile3\nfile4"; } // ls-l命令 public static String ls_l(String path) { String str = "drw-rw-rw root system 1024 2018-1-10 18:00 file1/n"; str += "drw-rw-rw root system 1024 2018-1-10 18:01 file2/n"; str += "drw-rw-rw root system 1024 2018-1-10 18:02 file3/n"; return str; } // ls-a命令 public static String ls_a(String path) { return ".\n..\nfile1\nfile2\nfile3"; } }
-
Invoker.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class Invoker { // 执行命令 public String exec(String _commandStr) { // 返回值 String result = ""; // 首先解析命令 CommandVO vo = new CommandVO(_commandStr); // 检查命令是否支持该命令 if (CommandEnum.getNames().contains(vo.getCommandName())) { // 产生命令对象 String className = CommandEnum.valueOf(vo.getCommandName()) .getValue(); Command command; try { command = (Command) Class.forName(className).newInstance(); result = command.execute(vo); } catch (Exception e) { e.printStackTrace(); } } else { result = "无法执行命令,请检查命令格式!"; } return result; } }
-
LS_A.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class LS_A extends AbstractLS { @Override protected String getOperateParam() { return super.A_PARAM; } @Override protected String echo(CommandVO vo) { return FileManager.ls_a(vo.formatDate()); } }
-
LS_L.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class LS_L extends AbstractLS { @Override protected String getOperateParam() { return super.L_PARAM; } @Override protected String echo(CommandVO vo) { return FileManager.ls_l(vo.formatDate()); } }
-
LS.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class LS extends AbstractLS { // 参数为空 @Override protected String getOperateParam() { return super.DEFAULT_PARAM; } // 最简单的ls命令 @Override protected String echo(CommandVO vo) { return FileManager.ls(vo.formatDate()); } }
-
LSCommand.java
package com.peng.zm; /** * @author kungfu~peng * @data 2018年1月10日 * @description */ public class LSCommand extends Command { @Override public String execute(CommandVO vo) { String result = ""; if (vo.getCommandName().equalsIgnoreCase("ls") && vo.getParam().size() == 1 && vo.getaData().size() == 0) { if (vo.getParam().contains("") || vo.getParam().contains("A")) { AbstractLS al = new LS(); result = al.echo(vo); } else if (vo.getParam().contains("a") || vo.getParam().contains("A")) { AbstractLS al = new LS_A(); result = al.echo(vo); } else if (vo.getParam().contains("l") || vo.getParam().contains("L")) { AbstractLS al = new LS_L(); result = al.echo(vo); } } else { result = "../../file/all../"; } return result; } }
-
执行结果
# ls 执行结果:file1 file2 file3 file4 # ls -a 执行结果:. .. file1 file2 file3 # ls -l 执行结果:drw-rw-rw root system 1024 2018-1-10 18:00 file1/ndrw-rw-rw root system 1024 2018-1-10 18:01 file2/ndrw-rw-rw root system 1024 2018-1-10 18:02 file3/n #
-
最佳实践
- 责任链模式
- 负责对命令参数进行解析,而且所有的扩展都是增加链数量和节点,不涉及原有的代码变更
- 命令模式
- 负责命令的分发,把适当的命令发送到指定的链上
- 模板方法模式
- 建立链的方法作为模板办法,调用基本方法,基本方法由各个实现类实现,非常有利于扩展
- 迭代器模式
- 在for循环中我们多次用到类似for(Class c:classes)的结构,是谁来支撑该方法运行?当然是迭代器模式
未处理的项
- ls-l-a
- 处理方式
- 独立处理:“is-l-a”等同于“ls-a”命令,可以把“ls-la”中的选项“la”作为一个参数来进行处理,扩展一个类就可以了,该方法的缺点就是类膨胀的太大,但是简单
- 混合处理:修正命令链处理链,每个命令处理节点运行完毕后,继续由后续节点处理,最终由Command类组装结果,根据每个节点处理结果,组合后生产完整的返回信息
- 处理方式
关于该框架
- 该框架还有一个名字,叫做命令链模式,具体来说就是命令模式作为责任链模式的排头兵,由命令模式分发具体的消息到责任链模式。