java在实际该项目中,用户会进行各种各样的请求操作,现对该问题处理方法进行探讨。
if-else语句或者switch-case语句
具体内容
在实际开发项目中对要功能模块进行管理,通过对输入的某个值的判断确定用户想要做什么,比如输入1表示做事件1,输入2表示做事件2.传统的实现方式可用if-else或者switch-case语句来完成。
相关代码
//if-else语句下的处理方式
//表示用户的请求
int cmd=1;
if(cmd==1){
//做事件1
}else if(cmd==2){
//做事件2
}
//其他
//switch-case语句下的处理方式
switch (cmd) {
case 1:{
//做事件1
break;
}
case 2:{
//做事件2
break;
}
default://........
break;
}
评价
该方法是一般最初能想到的也是最直接的方法,但此方法存在一下缺陷。
- 没有体现出面向对象的特征。
- 会增大编程复杂度,降低程序的可读性,如果功能过多,则处理语句会很长。
- 降低程序的性能:运行相对靠后的代码块时必须把前面的判断也访问到,增加了运行时间。
- 增大开发维护的成本:如果要增加一个功能,必须到源代码中去寻找,修改也要在源代码中去修改。而在实际项目中修改源代码是不好的。
命令模式的搭建
以上论述了用if-else,swithc-case语句会的缺点,改造可采用命令模式。
命令模式主要有一下三个要素
1. 命令接口
2. 命令实现类
3. 命令处理器
命令接口
命令接口
为了体现面向对象的抽象性,以及接口和实现分离的原则,对处理事件的过程分装为一个抽象的接口,定义如下。
@FunctionalInterface
public interface Command {
/**
* 命令处理接口
* @param obj 处理数据
* @return 处理结果
*/
Object handle(Object obj);
}
传入一个Object类型参数,得到返回结果,具体的过程由实现类自己实现。
命令实现类
有了接口就要有实现该接口的具体类,下面举两个例子。
/**
* 实现功能:打印传入的参数
*
*/
public class Print implements Command {
@Override
public Object handle(Object obj) {
System.out.println("你传入的是:"+obj);
return null;
}
}
/**
* 实现功能 :输出一句话"什么也不做"
*
*/
public class None implements Command {
@Override
public Object handle(Object obj) {
System.out.println("什么也不做");
return null;
}
}
命令处理器
有了接口和实现类,下面要做的就是建立一个类来处理该接口,要想实现对应的功能,必须要知道Command接口对应的是哪个类的对象,为此传入一个String类型表示类名的参数,并通过反射来获取该类的对象。
public class CommandHandler {
/**
* 处理数据的方法
* @param className 类名
* @param obj 处理数据
* @return 结果
*/
public static Object handle(String className,Object obj)
{
try {
//加载对应的Command实现类的对象
Command c=(Command) Class.forName(className).newInstance();
return c.handle(obj);
} catch (Exception e){
}
return null;
}
}
这里将处理方法设为公开静态方便调用,传入类名和具体数据,通过反射机制加载Class对象,并生成具体对象并转为Command类型,这里任何异常都不处理,最后通过Command对象调用方法返回结果。
现在可以进行简单的测试
public class Main {
public static void main(String[] args) {
CommandHandler.handle("Print", "123");
CommandHandler.handle("None", "123");
}
}
测试结果
至此,命令模式的基本框架基本搞定,但该方法存在许多问题,将在下面的叙述中进行优化。
命令模式的优化
使用xml文档
使用xml文档的必要性
1.以上通过反射来加载类,我们发现每次都要进行反射获取Class对象并且调用newInstance()方法生成一个对象,实际上我们只需要对应的方法即可,无须每次都要加载。
2.每次传入的参数中必须包含一个类的全类名,这样对用户来说不是很方便。
基于此,我们可以将所有要加载的类名事先写入xml文档,在程序初始化时读取文件,把所有的类名全部获取一个Command对象并保存,用对应的字符串作为关键字进行hash存取,这里的字符串即可当作用户输入的代表命令的字符串。
xml文档需要命令字符串和类名,可按一下格式定义,也可以自己定义。关于xml文档的知识这里不再详述。
<command>
<name></name>
<classname></classname>
</command>
继续拿本前面的程序来说明,我们可以建立如下的文档
<?xml version="1.0" encoding="utf-8"?>
<body>
<command>
<name>1</name>
<classname>Print</classname>
</command>
<command>
<name>2</name>
<classname>None</classname>
</command>
</body>
用map进行保存
/**命令集到命令对象的映射*/
private static HashMap<String, Command> commands;
初始化时加载该xml文档,把数据读入到commands中
/**
* 加载该类时完成对commands的初始化
*/
static {
// 标志 超时则退出
int flag = 0;
while (flag < 5) {
try {
// 定义文档解析器
DocumentBuilder buidler = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
// 获取解析后的文档对象
Document doc = buidler.parse("main.xml");
// 获取根部元素
Element root = doc.getDocumentElement();
// 加载命令
commands = new HashMap<String, Command>(root.getChildNodes()
.getLength()*2+1);
// 读取每个结点
for (int i = 0; i < root.getChildNodes().getLength(); i++) {
Node e = root.getChildNodes().item(i);
// 找到command结点
if (e instanceof Element) {
Element elem = (Element) e;
if ("command".equals(elem.getNodeName())) {
NodeList nl = elem.getChildNodes();
// 处理每个command节点
String name = null, classname = null;
for (int j = 0; j < nl.getLength(); j++) {
Node n = nl.item(j);
if (n instanceof Element) {
Element elem1 = (Element) n;
// 获取name值
if ("name".equals(elem1.getTagName())) {
name = ((Text) elem1.getFirstChild())
.getData().trim();
}
// 获取classname值
else if ("classname".equals(elem1
.getTagName())) {
classname = ((Text) elem1
.getFirstChild()).getData();
}
}
}
// 把结果加入集合中
if (name != null && classname != null) {
//加载Command命令
Command c = (Command) Class.forName(classname).newInstance();
//放入集合中
commands.put(name, c);
}
}
}
}
break;
} catch (Exception e) {
flag++;
// 设置超过五次则系统停止
if (flag == 5) {
System.out.println("命令系统异常");
//退出系统
System.exit(-1);
}
}
}
if(commands==null)
{
System.out.println("命令系统异常");
//退出系统
System.exit(-1);
}
}
处理方法的代码优化
/**
* 处理数据的方法
* @param cmd 命令字符串
* @param obj 处理数据
* @return 结果
*/
public static Object handle(String cmd,Object obj)
{
try {
//查找map 获取对应的Command实现类的对象
return commands.get(cmd).handle(obj);
} catch (Exception e){
}
return null;
}
测试代码
public static void main(String[] args) {
//1对应的是打印
CommandHandler2.handle("1", "123");
//2对应什么也不做
CommandHandler2.handle("2", "123");
//3无效
CommandHandler2.handle("3", "123");
}
测试结果
这样,只要传入一个命令字符串即可,无须传入类名,每个类也只会加载一次生成一个对象。当需要添加一个命令时,只需添加一个类实现Command接口,并且完成未实现的方法,再把命令字符串和类名在xml文档中进行注册即可。
自定义Message对象
在实际开发中,这样传入参数还是有些麻烦,而且用户很多时候用户有多个请求时间,所以这样的结构还是有些问题,我们在这里定义一个Message对象分装用户的所有请求,本质上是一个个的命令字符串到数据的键对,这里用map存取。
public final class Message {
/**表示信息的集合*/
private Map<String,Object> msg=new HashMap<String, Object>();
/**
* 添加一条信息
* @param str 信息对应指令
* @param obj 信息内容
*/
public void put(String str,Object obj)
{
msg.put(str, obj);
}
/**
* 判断消息为空
* @return true表示消息为空 false 表示有内容
*/
public boolean isEmpty()
{
return msg.isEmpty();
}
/**
* 获取信息长度
* @return 信息长度
*/
public int Size() {
return msg.size();
}
/**
* 根据指令获得信息内容
* @param str 指令
* @return 信息内容
*/
public Object getParameter(String str)
{
return msg.get(str);
}
/**
*
* @return 命令字符串集
*/
public Set<String> KetSet()
{
return msg.keySet();
}
}
修改处理的方法
/**
* 处理数据的方法
* @param message 处理信息
* @return 结果
*/
public static Message handle(Message message)
{
Message r=new Message();
try {
for(String e:message.KetSet())
{
//获取每个命令和对应数据 并获得结果
r.put(e, commands.get(e).handle(message.getParameter(e)));
}
} catch (Exception e){
}
return r;
}
测试
public static void main(String[] args) {
//生成一个Message
Message message=new Message();
message.put("1", "123");
message.put("2", "123");
message.put("3", "123");
CommandHandler3.handle(message);
}
测试结果