混合模式:命令模式+责任链模式

这里我们使用命令模式责任链模式模板方法模式,制作一个把UNIX上的命令移植到Windows上的工具。

UNIX下的命令,一条命令分为命令名选项操作数,例如命令"ls -l /usr"。其中,ls是命令名,l是选项,/usr是操作数,后两项都是可选项。

UNIX命令一定遵守以下几个规则:

  • 命令名为小写字母
  • 命令名、选项、操作数之间以空格分隔,空格数量不受限制。
  • 选项之间可以组合使用,也可以单独拆分使用。
  • 选项以横杠(-)开头

其中,ls命令中有几个常用的命令:

  • ls:简单列出一个目录下的文件
  • ls -l:详细列出目录下的文件。
  • ls -a:列出目录下包含的隐藏文件,主要是点号(.)开头的文件。
  • ls -s:列出文件的大小

当然,还有类似"ls -la"、"ls -ls"等的组合

这里针对一个ls命令族的算法要求:

  • 每一个ls命令都有操作数,默认操作数为当前目录
  • 选项不可重复,例如对于"ls-l-l-s",解析出的选项应该只有两个:l选项和s选项。
  • 每个选项返回不同的结果,也就是说每个选项应该由不同的业务逻辑来处理
  • 为提高扩展性,ls命令族内的运算应该是对外封闭的,减少外界访问ls命令族内部细节的可能性。

各个类的职责:

  • ClassUtils:工具类,根据一个接口、父类查找到所有的子类。
  • CommandVO:命令的值对象,它把一个命令解析为命令名、选项、操作数。
  • CommandEnum:枚举类型,是主要的命令配置文件。
  • CommandName:实现责任链的遍历,同时也是命令模式中的命令接收者
  • Command:定义命令的执行方法,同时负责命令族(责任链)的建立
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);
}
public abstract class AbstractLS extends CommandName {	
	public final static String DEFAULT_PARAM = "";	//默认参数
	public final static String A_PARAM = "a";	//参数a	
	public final static String L_PARAM = "l";	//参数l
}

该类的职责:标记ls命令族 和 个性化处理。因为现在还没有思考清楚ls有什么个性(可以把命令的选项也认为是其个性化数据),所以先写个空类放在这里,以后想清楚了再填写上去,留下一些可扩展的类也许会给未来带来不可估量的优点。

public class LS extends AbstractLS {
	//参数为空
	@Override
	protected String getOperateParam() {
		return super.DEFAULT_PARAM;
	}
	//最简单的ls命令
	@Override
	protected String echo(CommandVO vo) {
		return FileManager.ls(vo.getCommandName());
	}
}

public class LS_L extends AbstractLS {
	//l选项
	@Override
	protected String getOperateParam() {
		return super.L_PARAM;
	}
	@Override
	protected String echo(CommandVO vo) {
		return FileManager.ls_l(vo.getCommandName());
	}
}

public class LS_A extends AbstractLS {
	//a选项
	@Override
	protected String getOperateParam() {
		return super.A_PARAM;
	}
	@Override
	protected String echo(CommandVO vo) {
		return FileManager.ls_a(vo.getCommandName());
	}
}

这3个实现类都关联到了FileManager,这个类是负责与操作系统交互的。要把UNIX的命令迁移到Windows上运行,就需要调用Windows的低层函数,这里采用示例性代码代替。

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 2020-5-18 23:14 file1\n";
		str = str + "drw-rw-rw root system 1024 2020-5-18 23:14 file2\n";
		str = str + "drw-rw-rw root system 1024 2020-5-18 23:14 file3";
		return str;
	}
	//ls -a命令
	public static String ls_a(String path) {
		String str = ".\n..\nfile1\nfile2\nfile3";
		return str;
	}
}

Command抽象类中定义了命令的执行方法和命令族(责任链)的建立。其中buildChain方法负责建立一个责任链,它通过接收一个抽象的命令族类,然后通过ClassUtils类遍历其子类,就可以建立一条命令解析链。

import java.util.ArrayList;
import java.util.List;
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> commandNamesList = new ArrayList<CommandName>();
		for(Class c:classes) {
			CommandName commandName = null;
			try {
				//产生实例
				commandName = (CommandName)Class.forName(c.getName()).newInstance();
			} catch (Exception e) {
				// TODO: handle exception
			}
			//建立链表
			if (commandNamesList.size() > 0) {
				commandNamesList.get(commandNamesList.size() - 1).setNext(commandName);
			}
			commandNamesList.add(commandName);
		}
		return commandNamesList;
	}
}
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
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;
	}
}

具体的命令实现类LSCommand类:

public class LSCommand extends Command {
	@Override
	public String execute(CommandVO vo) {
		//返回链表的首节点
		CommandName firstNode = super.buildChain(AbstractLS.class).get(0);
		return firstNode.handleMessage(vo);
	}
}

先建立一个命令族的责任链,然后将一个命令对象从首节点注入。在该类中我们使用 CommandVO类,它封装了一个命令对象。

import java.util.ArrayList;
import java.util.HashSet;
public class CommandVO {
	//定义参数名与参数的分割符号,一般是空格
	public final static String DIVIDE_FLAG = " ";
	//定义参数前的符合,Unix一般是-,如ls -la
	public final static String PREFIX = "-";
	//命令名,如ls、du
	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;
	}
	//获得参数
	public ArrayList<String> getParam(){
		//为了方便处理空参数
		if (this.paramList.size() == 0) {
			this.paramList.add("");
		}
		return new ArrayList(new HashSet(this.paramList));
		//HashSet具有值唯一的优点,这样处理就是为了避免出现两个相同的参数。
	}
	//获得操作数
	public ArrayList<String> getData(){
		return this.dataList;
	}
}

在Invoker类实现命令的分发,从CommandEnum中获得命令与命令类的配置信息,然后建立一个命令实例,调用其execute方法,完成命令的执行操作。

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) {
				// TODO: handle exception
			}
		} else {
			result = "无法执行命令,请检查命令格式";
		}
		return result;
	}
}
import java.util.ArrayList;
import java.util.List;
public enum CommandEnum {
	ls("com.sfq.action.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[] commandEnums = CommandEnum.values();
		List<String> names = new ArrayList<String>();
		for(CommandEnum c:commandEnums) {
			names.add(c.name());
		}
		return names;
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import com.sfq.action.Invoker;
public class Client {
	public static void main(String[] args) throws IOException {
		Invoker invoker = new Invoker();
		while (true) {
			//UNIX下的默认提示符合
			System.out.print("#");
			//捕获输出
			String input = (new BufferedReader(new InputStreamReader(System.in))).readLine();
			//输入quit或exit则退出
			if (input.equals("quit") || input.equals("exit")) {
				return;
			}
			System.out.println(invoker.exec(input));
		}
	}
}
结果
#ls
file1
file2
file3
file4
#ls -l
drw-rw-rw root system 1024 2020-5-18 23:14 file1
drw-rw-rw root system 1024 2020-5-18 23:14 file2
drw-rw-rw root system 1024 2020-5-18 23:14 file3
#ls -a
.
..
file1
file2
file3

总体算法流程:

  • (1)Client将ls指令输送到Invoker的exec()方法中
  • (2)将ls指令在Invoker类中使用CommandVO类解析成vo命令对象,然后将vo对象输送到LSCommand类的execute()方法中
  • (3)在LSCommand类中调用Command中的buildChain()方法生成责任链,并获得责任链的首节点,然后将vo对象输送到首节点中
  • (4)CommandName中从首节点开始对责任链遍历,一直找到可以执行该命令的执行者
  • (5)执行完成后,结果逐级返回,从Client中输出

我们此时呢,相对上述方法进行扩充,在ls命令的基础上,增加一个df命令族,显示磁盘的大小,获得类图如下:

首先,添加类图右下角的抽象类和相应的实现:

public abstract class AbstractDF extends CommandName {
	public final static String DEFAULT_PARAM = "";	//默认参数
	public final static String K_PARAM = "k";	//参数k	
	public final static String G_PARAM = "g";	//参数g
}
public class DF extends AbstractDF {
	@Override
	protected String getOperateParam() {
		return super.DEFAULT_PARAM;
	}
	@Override
	protected String echo(CommandVO vo) {
		return DiskManager.df();
	}
}

public class DF_K extends AbstractDF {
	@Override
	protected String getOperateParam() {
		return super.K_PARAM;
	}
	@Override
	protected String echo(CommandVO vo) {
		return DiskManager.df_k();
	}
}

public class DF_G extends AbstractDF {
	@Override
	protected String getOperateParam() {
		return super.G_PARAM;
	}
	@Override
	protected String echo(CommandVO vo) {
		return DiskManager.df_g();
	}
}
public class DiskManager {
	//默认的计算大小
	public static String df() {
		return "/\t10484848\n/user\t10484848\n/home\t10484848\n";
	}
	//按照kb来计算
	public static String df_k() {
		return "/\t10484\n/user\t10484\n/home\t10484\n";
	}
	//按照gb来计算
	public static String df_g() {
		return "/\t10\n/user\t10\n/home\t10\n";
	}
}

然后,定义实际的命令类,并在枚举类中加入相应的类型:

public class DFCommand extends Command {
	@Override
	public String execute(CommandVO vo) {
		return super.buildChain(AbstractDF.class).get(0).handleMessage(vo);
	}
}
import java.util.ArrayList;
import java.util.List;
public enum CommandEnum {
	ls("com.sfq.action.LSCommand"),
	df("com.sfq.action.DFCommand");
	private String value = "";
	//定义构造函数,目的是Data(value)类型的相匹配
	private CommandEnum(String value) {
		this.value = value;
	}
	public String getValue() {
		return value;
	}
	//返回所有的enum对象
	public static List<String> getNames(){
		CommandEnum[] commandEnums = CommandEnum.values();
		List<String> names = new ArrayList<String>();
		for(CommandEnum c:commandEnums) {
			names.add(c.name());
		}
		return names;
	}
}

场景类不变,依然如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import com.sfq.action.Invoker;
public class Client {
	public static void main(String[] args) throws IOException {
		Invoker invoker = new Invoker();
		while (true) {
			//UNIX下的默认提示符合
			System.out.print("#");
			//捕获输出
			String input = (new BufferedReader(new InputStreamReader(System.in))).readLine();
			//输入quit或exit则退出
			if (input.equals("quit") || input.equals("exit")) {
				return;
			}
			System.out.println(invoker.exec(input));
		}
	}
}
结果
#df
/	10484848
/user	10484848
/home	10484848

#df -g
/	10
/user	10
/home	10

#df -k
/	10484
/user	10484
/home	10484

我们在原本业务逻辑不变的情况下,请示的实现了业务的扩展。在这个例子中用到了以下模式。

  • 责任链模式:负责对命令的参数进行解析,而且所有的扩展都是增加链数量和节点,不涉及原有的代码变更。
  • 命令模式:负责命令的分发,把适当的命令分发到指定的链上。
  • 模板方法模式:在Command类以及子类中,buildChain方法是模板方法,只是没有基本方法而已;在责任链模式的CommandName类中,用了一个典型的模板方法handlerMessage,它调用了基本方法,基本方法由各个实现类实现,非常有利于扩展。
  • 迭代器模式:在for循环中我们多次用到类似for(Class c:classes)的结构,JDK已经把迭代器模式融入到了API中,更方便使用了。

如果想要继续处理,如:"ls-l-a"这样的组合选项。这里提供以下两个思路:

  • 独立处理:"ls-l-a"等同于"ls-la",也等同于"ls-al"命令,可以把"ls-la"中的选项"la"作为一个参数来进行处理,扩展一个类就可以了。该方法的缺点是类膨胀得太大,但是简单。
  • 混合处理:修正命令族处理链,每个命令处理节点运行完毕后,继续由后续节点处理,最终由Command类组装结果,根据每个节点的处理结果,组合后生成完整的返回信息,如"ls-l-a"就应该是LS_L类与LS_A类两者返回值组装的结果,当然链上的节点返回值就要放在Collection类型中了。

该框架叫做命令链(Chain of Command)模式,具体来说就是命令模式作为责任链模式的排头兵,由命令模式分发具体的消息到责任链模式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章