| 春's profileDENNYPhotosBlogLists | Help |
|
January 18 Java代码编写的30条建议 [转]1) 类名首字母应该大写。字段、方法以及对象(句柄)的首字母应小写。对于所有标
关于海量数据的SQL查询优化通过内部的计数器得知:访问次数是1071(其中有好多是自己点的:)),人数不是太理想,本来是想看看上万人同时访问的情况:)
讨论的前提是在海量数据的情况下,至少是在10万以上的。如果是很少的数据呢,那怎么翻都可以了。也差不了多少。
3.尽量减少字段的长度
http://community.csdn.net/Expert/TopicView3.asp?id=4182510 Jsp结合XML+XSLT将输出转换为Html格式我们知道 XML+XSLT就可以直接输出到支持XML的浏览器上,如IE 5.0以上,但是,我们还要考虑到有不少浏览器不直接支持XML,在这种情况下,我们需要在服务器上进行转换成html输出到浏览器,这种临时过渡办法恐怕要在一段时间内一直要使用. 使用Jsp 加上tablib标识库,我们可以完成这种转换。 一篇关于session的好文章,写的很详细 [转]摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。 commons-logging和Log4j 日志管理为什么要用日志(Log)?这个……就不必说了吧。 为什么不用System.out.println()?功能太弱;不易于控制。如果暂时不想输出了怎么办?如果想输出到文件怎么办?如果想部分输出怎么办?…… 为什么同时使用commons-logging和Log4j?为什么不仅使用其中之一?Commons-loggin的目的是为“所有的Java日志实现”提供一个统一的接口,它自身的日志功能平常弱(只有一个简单的SimpleLog?),所以一般不会单独使用它。 Log4j的功能非常全面强大,是目前的首选。我发现几乎所有的Java开源项目都会用到Log4j,但我同时发现,所有用到Log4j的项目一般也同时会用到commons-loggin。我想,大家都不希望自己的项目与Log4j绑定的太紧密吧。另外一个我能想到的“同时使用commons-logging和Log4j”的原因是,简化使用和配置。 强调一点,“同时使用commons-logging和Log4j”,与“单独使用Log4j”相比,并不会带来更大的学习、配置和维护成本,反而更加简化了我们的工作。我想这也是为什么“所有用到Log4j的项目一般也同时会用到commons-loggin”的原因之一吧。
Commons-logging能帮我们做什么?l 提供一个统一的日志接口,简单了操作,同时避免项目与某个日志实现系统紧密a耦合 l 很贴心的帮我们自动选择适当的日志实现系统(这一点非常好!) l 它甚至不需要配置
这里看一下它怎么“‘很贴心的’帮我们‘自动选择’‘适当的’日志实现系统”: 1) 首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类; 2) 如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类; 3) 否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类; 4) 否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类); 5) 否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog; (以上顺序不保证完全准确,请参考官方文档)
可见,commons-logging总是能找到一个日志实现类,并且尽可能找到一个“最合适”的日志实现类。我说它“很贴心”实际上是因为:1、可以不需要配置文件;2、自动判断有没有Log4j包,有则自动使用之;3、最悲观的情况下也总能保证提供一个日志实现(SimpleLog)。 可以看到,commons-logging对编程者和Log4j都非常友好。 为了简化配置commons-logging,一般不使用commons-logging的配置文件,也不设置与commons-logging相关的系统环境变量,而只需将Log4j的Jar包放置到classpash中就可以了。这样就很简单地完成了commons-logging与Log4j的融合。如果不想用Log4j了怎么办?只需将classpath中的Log4j的Jar包删除即可。 就这么简单! 代码应该怎么写?我们在需要输出日志信息的“每一人”类中做如下的三个工作: 1、导入所有需的commongs-logging类: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 如果愿意简化的话,还可以两行合为一行: import org.apache.commons.logging.*;
2、在自己的类中定义一个org.apache.commons.logging.Log类的私有静态类成员: private static Log log = LogFactory.getLog(YouClassName.class); 注意这里定义的是static成员,以避免产生多个实例。 LogFactory.getLog()方法的参数使用的是当前类的class,这是目前被普通认为的最好的方式。为什么不写作LogFactory.getLog(this.getClass())?因为static类成员访问不到this指针!
3、使用org.apache.commons.logging.Log类的成员方法输出日志信息: log.debug("111"); log.info("222"); log.warn("333"); log.error("444"); log.fatal("555"); 这里的log,就是上面第二步中定义的类成员变量,其类型是org.apache.commons.logging.Log,通过该类的成员方法,我们就可以将不同性质的日志信息输出到目的地(目的地是哪里?视配置可定,可能是stdout,也可能是文件,还可能是发送到邮件,甚至发送短信到手机……详见下文对log4j.properties的介绍): l debug() 输出“调试”级别的日志信息; l info() 输出“信息”级别的日志信息; l warn() 输出“警告”级别的日志信息; l error() 输出“错误”级别的日志信息; l fatal() 输出“致命错误”级别的日志信息; 根据不同的性质,日志信息通常被分成不同的级别,从低到高依次是:“调试(DEBUG)”“信息(INFO)”“警告(WARN)”“错误(ERROR)”“致命错误(FATAL)”。为什么要把日志信息分成不同的级别呢?这实际上是方便我们更好的控制它。比如,通过Log4j的配置文件,我们可以设置“输出‘调试’及以上级别的日志信息”(即“调试”“信息”“警告”“错误”“致命错误”),这对项目开发人员可能是有用的;我们还可以设置“输出“警告”及以上级别的日志信息”(即“警告”“错误”“致命错误”),这对项目最终用户可能是有用的。 仅从字面上理解,也可以大致得出结论:最常用的应该是debug()和info();而warn()、error()、fatal()仅在相应事件发生后才使用。
从上面三个步骤可以看出,使用commons-logging的日志接口非常的简单,不需要记忆太多东西:仅仅用到了两个类Log, LogFactory,并且两个类的方法都非常少(后者只用到一个方法,前者经常用到的也只是上面第三步中列出的几个),同时参数又非常简单。 上面所介绍的方法是目前被普通应用的,可以说是被标准化了的方法,几乎所有的人都是这么用。如果不信,或想确认一下,就去下载几个知名的Java开源项目源代码看一下吧。
下面给出一个完整的Java类的代码:
package liigo.testlog;
import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
public class TestLog { private static Log log = LogFactory.getLog(TestLog.class);
public void test() { log.debug("111"); log.info("222"); log.warn("333"); log.error("444"); log.fatal("555"); }
public static void main(String[] args) { TestLog testLog = new TestLog(); testLog.test(); } }
只要保证commons-logging的jar包在classpath中,上述代码肯定可以很顺利的编译通过。那它的执行结果是怎么样的呢?恐怕会有很大的不同,请继续往下看。
Log4j在哪里呢?它发挥作用了吗?应该注意到,我们上面给出的源代码,完全没有涉及到Log4j——这正是我们所希望的,这也正是commons-logging所要达到的目标之一。 可是,怎么才能让Log4j发挥它的作用呢?答案很简单,只需满足“classpath中有Log4j的jar包”。前面已经说过了,commons-logging会自动发现并应用Log4j。所以只要它存在,它就发挥作用。(它不存在呢?自然就不发挥作用,commons-logging会另行选择其它的日志实现类。)
注意:配置文件log4j.properties对Log4j来说是必须的。如果classpath中没有该配置文件,或者配置不对,将会引发运行时异常。
这样,要正确地应用Log4j输出日志信息,log4j.properties的作用就很重要了。好在该文件有通用的模板,复制一份(稍加修改)就可以使用。几乎每一个Java项目目录内都会有一个log4j.properties文件,可下载几个Java开源项目源代码查看。本文最后也附一个模板性质的log4j.properties文件,直接复制过去就可以用,或者根据自己的需要稍加修改。后文将会log4j.properties文件适当作一些介绍。 关于Log4j比较全面的配置LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、 回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了
log4j.rootLogger=DEBUG,CONSOLE,A1,im
# 应用于控制台 September 08 青岛之行虽然从青岛回来已经有几天了,但是自己还时不时的沉浸在那美丽的海边城市的生活中。
在绿树如荫的八大处,一对对幸福的新婚夫妇拍着婚纱照,让羡慕不已。
在海岸旁边的海军博物官,看着停靠在那里的101战舰(撒刘少奇骨灰的战舰),不仅设想起那混沌的时代。
还有美丽且有特色的崂山,让人不解的天后宫,......
都会让人留恋忘返。
不过有点遗憾的是,早就听说青岛美女多,可惜亲身体验发现有点言过其实。
技术文档form设置了enctype="multipart/form-data" 属性后的问题
enctype="multipart/form-data"是上传二进制数据;
form里面的input的值以2进制的方式传过去,所以request就得不到值了。 文件传输和文本传输的编码不一样。
要获取文本框的值可以使用专门的文件上传组件。
不过Resin除外。
用Tomcat实现容器内认证
在数据库里新建两个表 在tomcat的server.xml里加入描述 在自己应用程序的web.xml里加入描述(基于表单) <login-config> 登录表单必须包含输入用户姓名和口令的字段,它们必须被分别命名为j_username和j_password,表单将这二个值发送给j_security_check逻辑名字。 这样当对get.jsp进行访问时,tomcat就会自动转到login.jsp页面实现认证。对于简单的认证,小型系统,采用Tomcat实现容器内认证是方便的。在页面中调用request.getRemoteUser()可得到当前访问的用户名 经常到的javaScript技术代码 一。验证类
数字验证类 1.1 整数 /^(-|\+)?\d+$/.test(str) 1.2 大于0的整数 (用于传来的ID的验证) /^\d+$/.test(str) 1.3 负整数的验证 /^-\d+$/.test(str) 2、时间类 2.1 短时间,形如 (13:04:06) function isTime(str) { var a = str.match(/^(\d{1,2})(?(\d{1,2})\2(\d{1,2})$/); if (a == null) {alert('输入的参数不是时间格式'); return false;} if (a[1]>24 || a[3]>60 || a[4]>60) { alert("时间格式不对"); return false } return true; } 2.2 短日期,形如 (2003-12-05) function strDateTime(str) { var r = str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/); if(r==null)return false; var d= new Date(r[1], r[3]-1, r[4]); return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]); } 2.3 长时间,形如 (2003-12-05 13:04:06) function strDateTime(str) { var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2})\d{1,2})\d{1,2})$/; var r = str.match(reg); if(r==null)return false; var d= new Date(r[1], r[3]-1,r[4],r[5],r[6],r[7]); return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]&&d.getHours()==r[5]&&d.getMinutes()==r[6]&&d.getSeconds()==r[7]); } 2.4 只有年和月。形如(2003-05,或者2003-5) 2.5 只有小时和分钟,形如(12:03) 3、表单类 3.1 所有的表单的值都不能为空 3.2 多行文本框的值不能为空。 4.2 判断字符由字母和数字组成。 4.3 判断字符由字母和数字,下划线,点号组成.且开头的只能是下划线和字母 6、结合类 3.7 复选框的全选,多选,全不选,反选 全选
function checkAll(str)
2.验证IP地址 function isip(s){ var s="202.197.78.129"; 3.加sp1后还能用的无边框窗口!!
/* /- Thanks For andot Again ---*/ var CW_width = 400; //Build Window function insert_content(){ "; setTimeout("insert_content()",1000); var if_max = true; }else{ window.onfocus = show_CW; // Move Window function drag_move(e){ function drag_down(e){ function drag_up(e){
要求: function PhoneCheck(s) { --------------------------------------------------------------------------------------
//检验法人代码 //校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串 //校验密码:只能输入6-15个字母、数字 //检验体重 //校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-” //校验手机号码:必须以数字开头,除数字外,可含有“-” //校验地区代码 //校验邮政编码 //校验搜索关键字 //校验是否为ip地址 //检验页码是否正确 //表单输入值错误提示 //判断单选 / //表单的值不能为空 / ********************************************************************************************************************* .cMenu { <!--[if IE]> 电话号码的验证 要求:
<% '******************************************** '******************************************** '******************************************** '******************************************** '******************************************** '******************************************** '******************************************** //检验法人代码 //校验登录名:只能输入5-20个以字母开头、可带数字、“_”、“.”的字串 //校验密码:只能输入6-15个字母、数字 //检验体重 //校验普通电话、传真号码:可以“+”开头,除数字外,可含有“-” //校验手机号码:必须以数字开头,除数字外,可含有“-” //校验地区代码 //校验邮政编码 //校验搜索关键字 //校验是否为ip地址 //检验页码是否正确 //表单输入值错误提示 //判断单选 为tomcat页面设置访问权限 在web应用中,对页面的访问控制通常通过程序来控制,流程为: 登录 -> 设置session -> 访问受限页面时检查session是否存在,如果不存在,禁止访问 对于较小型的web应用,可以通过tomcat内置的访问控制机制来实现权限控制。采用这种机制的好处是,程序中无需进行权限控制,完全通过对tomcat的配置即可完成访问控制。
为了在tomcat页面设置访问权限控制,在项目的WEB-INFO/web.xml文件中,进行如下设置:
<web-app> <!--servlet等其他配置--> <security-constraint> <web-resource-collection> <display-name>Example Security Constraint</display-name> <web-resource-name>My Test</web-resource-name> <url-pattern>/ddly/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>role1</role-name> <role-name>tomcat</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>My Test</realm-name> </login-config> </web-app>
其中,<url-pattern>中指定受限的url,可以使用通配符*,通常对整个目录进行访问权限控制。
<auth-constraint>中指定哪些角色可以访问<url-pattern>指定的url,在<role-name>中可以设置一个或多个角色名。 使用的角色名来自tomcat的配置文件${CATALINA_HOME}/conf/tomcat-users.xml。
<login-config>中设置登录方式,<auth-method>的取值为BASIC或FORM。如果为BASIC,浏览器在需要登录时弹出一个登录窗口。如果为FORM方式,需要指定登录页面和登录失败时的提示信息显示页面。
使用FORM方式的配置样例如下:
<login-config> <auth-method>FORM</auth-method> <realm-name>Example Form-Based Authentication Area</realm-name> <form-login-config> <form-login-page>/login.jsp</form-login-page> <form-error-page>/error.jsp</form-error-page> </form-login-config> </login-config>
其中的<form-login-page>指定登录页面url,<form-error-page>指定登录失败时的提示页面url。
登录页面中,form的action,以及其中的用户名和密码两个参数的名称,都应取固定的值。登录的后台处理程序为j_security_check;用户名和密码的参数名称分别为:j_username和j_password。 如下是登录页面(如:login.jsp)的一段示例代码: <form method="POST" action='<%= response.encodeURL("j_security_check") %>' > <table border="0" cellspacing="5"> <tr> <th align="right">Username:</th> <td align="left"><input type="text" name="j_username"></td> </tr> <tr> <th align="right">Password:</th> <td align="left"><input type="password" name="j_password"></td> </tr> <tr> <td align="right"><input type="submit" value="Log In"></td> <td align="left"><input type="reset"></td> </tr> </table> </form> 表现层框架Struts/Tapestry/JSF架构比较 Struts/Tapestry/JSF是目前J2EE表现层新老组合的框架技术。从诞生时间上看,Struts应该比较早,使用得非常广泛,Tapestry 3.0逐渐引起广泛的重视,正当Tapestry即将大显身手时期,SUN推出JSF标准技术,虽然JSF一开始推出尚不成熟,留出了一段空白期,但是随着JSF1.1标准推出,JSF开始正面出击,粉面隆重登场了。
其实,JSF和Tapestry也并不是那种头碰头的相同竞争性技术,两者还是各有侧重点的,不过比较细微,但是这种细微点在实现一个大工程时可能带来不同的感受和变化。 首先,我们从一个高度来抽象一下表现层框架应有的技术架构,下图可以说所有表现层框架技术都必须实现的功能架构图:
当然,我们不必废话罗嗦MVC模式,MVC模式是基准模式,现在框架技术已经不必再拼是否是MVC模式了。 在上图MVC模式基础上,一个表现层框架无外乎要实现图中的三个功能: 1.在当前页面能够显示一个组件对象的内容;而不是象纯JSP那样,需要在Jsp页面写入“调用对象方法”的Java代码。 2.当用户按下页面的提交按扭或链接后,事件发生,这时应该触发服务器端并将当前页面的参数提交给服务器。这种机制表现在Form表单提交和有参数的链接<a href=""></a> 3.从一个页面视图直接跳转到另外一个页面视图,单纯的导航作用。 我们通过下表来比较这 三种框架在实现上图各个功能时技术细节,从而得出他们的异同点和偏重点。
Struts组件编程模型 Struts实现组件编程时有一些复杂:经常为一个页面中需要引入多个组件而头疼,因为Struts中无法直接引入多个组件,必须绕一些圈子: 一般分两种情况:如果同一个Action就可以对付这些组件,那么在这种情况下有两个办法: 1.将这多个组件装入一个ActionForm中,如使用MapForm等机制; 2.手工将多个组件装入request/session等scope中,然后根据其名称在jsp中获得。 这两个方法都有缺点: 第一种办法经常一个ActionForm弄得面目全非,变成一个大杂烩,违反了OO分派封装的原则;第2种办法其实又回到jsp编程; 第二种情况,如果这些组件必须有预先由不同的Action来处理,每个组件必须经过Action -->ActionForm流程,在这种情况下有两种办法: 1.使用Tiles, 不同流程输出到同一个页面的不同区域。是一种并行处理方式。 2. 对多个流程首尾相连,第一Action forward结果是第二个Action,最后输出一个Jsp,在这个jsp中就可以使用前面多个流程的多个ActionForm了,这属于串行方式。 Struts组件模型缺点 Struts组件编程必须限定在Action/ActionForm/JSP这三个框框中做文章,难度相对比较大,而Tapestry/JSF则没有太多这些技术框框限制,两者在组件编程方面更让编程者自由一些,方便一些,这也是组件型框架的优势吧。 Struts标签库 在Struts中,经常需要使用标签库来显示组件ActionForm中内容,这就涉及到一个结合的问题,标签库是别人写的,参考Struts的标签库用法,而组件是自己的,难度和麻烦就体现在这个结合点上。 JSF基本思路和Struts差不多,只不过换了不同标签库,也需要标签库+组件的结合思考,不过因为组件这里是通用组件,没有什么限制,所以这样比Struts要轻松一些。 Tapestry使用了组件库概念替代了标签库,没有标签库概念,这样就没有标签库和自己的组件需要结合的问题,都是组件的使用,组件中分Tapestry标准组件和自己定义的组件,这也是接触了Jsp体系的人学习Tapestry面临的一个思路转换。 具体以页面跳转为例子,页面跳转是靠链接<a href="目标"></a> 实现,链接是页面经常使用的元素。 Struts提供的html:link在频繁使用就特别不方便,尤其在传递多个参数时:其中html:link的page值,是跳转对方页面或Action的path,这个path一般需要到struts-config.xml查找Action的相应path,一旦配置文件path值修改,涉及到这个所有相关页面都要修改。 JSF将链接概念划分两个方面:导航性质和事件激活,在导航方面还是需要到配置faces-config查询Navigation的from-outcome的值。 由于Tapestry没有标签库概念,只有组件或页面两个概念,因此,链接跳转目标要么是组件,要么是页面,简洁简单,它没有多余的path概念,就是组件名,也就是对象名称,组件名称和path名称合二为一。 总结 JSF在很大程度上类似Struts,而不是类似Tapestry,可以说是一种Struts 2.0,都是采取标签库+组件的形式,只是JSF的组件概念没有象Struts那样必须继承ActionForm的限制;JSF在事件粒度上要细腻,不象Struts那样,一个表单一个事件,JSF可以细化到表单中的每个字段上。 JSF只有在组件和事件机制这个概念上类似Tapestry,但是不似Tapestry那样是一个完全组件的框架,所以,如果你做一个对页面要求灵活度相当高的系统,选用Tapestry是第一考虑。 Struts/JSF则适合在一般的数据页面录入的系统中,对于Struts和JSF的选用,我目前个人观点是:如果你是一个新的系统,可以直接从JSF开始;如果你已经使用Struts,不必转换,如果需要切换,可以将JSF和Tapestry一起考虑。 另外,JSF/Tapestry不只是支持Html,也支持多种客户端语言如WML或XUI等。 这三者之间关系:如果说Struts是左派;那Tapestry则是右派;而JSF则是中间派,中庸主义是SUN联盟的一贯策略。 July 20 设计模式的阐释当前,解决软件开发的效率和质量的问题,复用是重要途径。人们逐渐由原来的代码拷贝粘贴式的复用,转到了基于软构件的复用,也产生了基于构件软件开发CBSD和基于构件软件工程CBSE等概念和研究。尽管如此,人们的复用层次仍停留在代码实现层次。
在进行下一步讨论之前,需要来点相关定义、概念和理论。 模式的层次 1、讨论如何更好的描述设计模式,讨论模式之间的关系,如何分类和组织模式,以让后来者系统的学习。 我们先讨论一下模式分类吧。现在我知道的有如下一些分类方法:
从重构的角度学习bridge设计模式从重构的角度学习bridge设计模式 Bridge模式是一个在实际系统中经常应用的模式。它最能体现设计模式的原则 针对接口进行编程,和使用聚合不使用继承这两个原则。 由于我们过分的使用继承,使类的结构过于复杂,不易理解,难以维护。特别 是在Java中由于不能同时继承多个类,这样就会造成多层继承,维护更难。 Bridge模式是解决多层继承的根本原因。如果你在实现应用中一个类,需要继承 两个以上的类,并且这两者之间又持有某种关系,它们两个都会有多种变化。 Bridge模式是把这两个类,分解为一个抽象,一个实现,使它们两个分离,这样 两种类可以独立的变化。 抽象就是,把一个实体的共同概念(相同的步骤),抽取出来(分解出几个相互独立的步骤), 作为一个过程。如我们把数据库的 操作抽象为一个过程,有几个步骤,创建SQL语句, 发送到数据库处理,取得结果。 实现就是怎样完成这个抽象步骤,如发送到数据库,需要结合具体的数据库,考虑怎样完成这个步骤等。 并且同一步骤可能存在不同的实现,如对不同的数据库需要不同的实现。 现在我们假设一个情况,也是WEB中经常遇到的,在一个page有输入框, 如客户信息的姓名,地址等,输入信息后,然后按查找按钮,把查找的结果显示出来。 我们现在假设查找客户信息和帐户信息,它们在不同的表中。 但是我们的系统面对两种人群,总部的它们信息保存到oracle数据库,但是各个分公司 的数据保存在Sybase中,数据库的位置等各不相同,这两种的操作不同。 下面是我们一般首先会使用的方式,使用if else进行,判断,这样使用系统 难以维护,难以扩展,不妨你增加一种查询,或者一种数据库试试???? public class SearchAction(){ public Vector searchData(string ActionType,String DbType){ String SQL=""; if(ActionType.equal("查找客户信息")){ //如果是查询客户信息,拼SQL语句从客户表中读取数据 SQL="select * from Customer " if(dbType.equal("oracle")){ //从总部数据库读取,数据库为Oracle String connect_string ="jdbc:oracle:thin:hr/hr@localhost:1521:HRDB"; DriverManager.registerDriver (new oracle.jdbc.OracleDriver()); Connection conn = DriverManager.getConnection (connect_string); // Create a statement Statement stmt = conn.createStatement (); ResultSet rset = stmt.executeQuery (SQL); //以下省略部分动态从数据库中取出数据,组装成Vector,返回 .................................. ................................... }else(dbType.equal("sybase")){ //从分公司数据库读取,数据库为Sybase String connect_string ="jdbc:sybase:Tds:cai/cai@192.168.1.12:1521:FIN"; DriverManager.registerDriver (new com.sybase.jdbc.SybDriver()); Connection conn = DriverManager.getConnection (connect_string); // Create a statement Statement stmt = conn.createStatement (); ResultSet rset = stmt.executeQuery (SQL); //以下省略部分动态从数据库中取出数据,组装成Vector,返回 .................................. ................................... } }else if(ActionType.equal("查找帐户信息")){ //如果是查询帐户信息,拼接SQL语句从帐户表中读取数据 SQL="select * from Account " if(dbType.equal("oracle")){ .......................... .......................... (作者注:此处省略从oracle读取,约300字) }else if(dbType.equal("Sybase")){ .......................... .......................... (作者注:此处省略从Sybase读取,约300字) } } } } 如果你认为这写的比较弱智,应该进行使用函数,但是你也会大量使用if else.??? 于是我们进行重构,首先我们学习过DAO模式,就是把数据读取进行分里,我们定义一个 共同的接口,它负责数据库的操作,然后根据不同的数据库进行实现,在我们的查询操作中, 使用接口,进行操作,这样就可以不用考虑具体的实现,我们只管实现过程。 查询共同接口: public interface searchDB{ public Vector searchFromDB(String SQL) } Oracle数据库的查询实现 public class searchDBOracleImpl{ public Vector searchFromDB(String SQL){ //从总部数据库读取,数据库为Oracle String connect_string ="jdbc:oracle:thin:hr/hr@localhost:1521:HRDB"; DriverManager.registerDriver (new oracle.jdbc.OracleDriver()); ResultSet rset = stmt.executeQuery (SQL); ............................. ............................ } } Sybase数据库的查询实现 public class searchDBSysbaseImpl{ public Vector searchFromDB(String SQL){ //从分公司数据库读取,数据库为Sysbase String connect_string ="jdbc:sybase:Tds:cai/cai@192.168.1.12:1521:FIN"; DriverManager.registerDriver (new com.sybase.jdbc.SybDriver()); ResultSet rset = stmt.executeQuery (SQL); ............................. ............................ } } 这样在我们的查询中就可以使用接口searchDB,但是创建有是一个问题,因为我们不能 静态的确定,查询的数据库类型,必须动态确定,于是我们又想到使用简单工厂方法, 来分别创建这里的具体实现,根据类别,创建 public class searchFactory{ public static searchDB createSearch(int DBType){ if(DBType.equal("oracle")){ return searchDBOracleImpl(); }else if(DBType.equal("sybase")){ return searchDBSysbaseImpl(); } } } 于是我们的查询代码可以改变为这样了; public class SearchAction(){ public Vector searchData(string ActionType,String DbType){ String SQL=""; if(ActionType.equal("查找客户信息")){ //如果是查询客户信息,拼SQL语句从客户表中读取数据 SQL="select * from Customer " searchDB obj=searchFactory.createSearch(DbType); return obj.searchFromDB(SQL); }else if(ActionType.equal("查找帐户信息")){ //如果是查询帐户信息,拼接SQL语句从帐户表中读取数据 SQL="select * from Account " searchDB obj=searchFactory.createSearch(DbType); return obj.searchFromDB(SQL); } } } 是不是简单一些,如果增加一个新的数据库,对我们只需增加一个新的数据库实现便可, 老的代码,不需改变,这样便实现开-闭原则(Open-closed原则),在我们的查询查询 中使用的是接口,这就是设计模式的原则,针对接口进行编程,并且使用聚合,而不是直接的继承 大家,可以考虑使用继承来完成该工作怎样实现????? 上面是把实现进行分离,实现可以动态变化!!!!! 我们把查询的操作的具体数据库实现进行了分离,增强了灵活性,但是我们的查询。 仍然使用了if else这样仍然不易进行扩展,于是我们进行抽象一个查询操作的过程, 把它分成几个具体步骤,创建SQL语句,发送到数据库,执行查询,返回结果。 它们虽然是不同的查询,SQL各不相同,不同数据库执行不同,返回结果的内容不同。但是 这个过程却是不变的,于是我们声明一个抽象类,来完成这个过程。 public abstract class searchAction{ searchDB obj; //两个步骤 public searchDB createSearchImple(int DbType){ return searchFactory.createSearch(DbType); } public abstract String createSQL(); //查询过程,最后返回结果 public vector searchResult(int DbType){ obj=createSearchImple(DbType); return obj.searchFromDB(createSQL()) } } //我们客户查询,操作 public class searchCustomerAction{ public String createSQL(){ return "select * from Customer" } } //我们的帐户查询操作 public class searchAccountAction{ public String createSQL(){ return "select * from account" } } 这样我们的查询编程简单的创建SQL语句,我们应该再创建一个工厂方法, 来完成创建它们 public class actionFactory{ public static searchAction ceateAction(int actionType){ if(actionType.equal("customer")){ return searchCustomerAction(); }else if(actionType.equal("account")){ return searchAccountAction(); } } } 这样我们把查询操作的过程进行了抽象,定义了步骤,和具体过程,经过我们的两次改变 把抽象部分和实现部分进行分离,使他们都可以独立的变化,增强灵活性。 我们再看当初查询实现,现在经过这两次的地修改,变成了什么模样?如下: public class SearchAction(){ public Vector searchData(string ActionType,String DbType){ searchAction action=actionFactory.ceateAction(ActionType); return action.searchResult(DbType); } 现在假如增加一个数据库类型,将会改变那些??,如果增加一种查询操作需要改变那些??? 讨论点: 1:在我们的重构过程中, 怎样使用设计模式原则的??? 现在如果增加功能,遵循开闭原则吗?? 2:我们使用了两个简单工厂,这是为了简化,一般最好使用抽象工厂方法, 如果改为抽象工厂,怎样修改??? 我打算写一系列的文章介绍设计模式, 希望从重构的角度考虑模式的应用,而不是 直接介绍模式,这样对初学者容易入门,step by step。 如果在自己的代码中遇到类似的情景,可以进行重构。 从重构学习proxy,预告 为什么EJB有Home,remote,bean这三种角色? 为什么又客户端与容器进行交互?? 介绍在EJB中的应用??? 从重构学习decorator?? 设计模式在EJB中的应用什么是设计模式 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。 GoF的“设计模式”是第一次将设计模式提升到理论高度,并将之规范化,本书提出了23种基本设计模式,自此,在可复用面向对象软件的发展过程中,新的大量的设计模式不断出现。 设计模式和框架 框架通常定义了应用体系的整体结构 类和对象的关系等等设计参数,以便于具体应用实现者能集中精力于应用本身的特定细节。框架主要记录软件应用中共同的设计决策,框架强调设计复用,因此框架设计中必然要使用设计模式. 另外,设计模式有助于对框架结构的理解,成熟的框架通常使用了多种设计模式,如果你熟悉这些设计模式,毫无疑问,你将迅速掌握框架的结构,我们一般开发者如果突然接触EJB J2EE等框架,会觉得特别难学,难掌握,那么转而先掌握设计模式,无疑是给了你剖析EJB或J2EE系统的一把利器。 EJB中的设计模式 EJB是采取多层结构,原先我们数据库开发基本是应用程序(商业逻辑运算)直接调用数据库驱动,在EJB中,为将商业逻辑计算和数据库截然分开,使用多个结构式模式:Adapter模式和Bridge模式等.这样做的好处显然有三个: 1.分离了商业逻辑层和数据访问层; EJB中将对数据库进行调用(如发出select等语句)称为会话bean(Sessionbean),而将对应数据库一个个记录的bean称为实体bean(Entity bean);由这两种类型的bean完成对数据库的访问. 会话bean一般和客户端应用是一一对应,而和数据库端联系紧密的是实体bean,EJB在实体bean(或直接在会话bean)和数据库之间使用了Adapter模式和Bridge模式,无意在实体bean和数据库之间又多了一层,称之为DAO(Data Access Object ),DAO实际就是设计模式的混合体. 我们以Java的宠物店中的Catalog为例,这是专门处理宠物店中的宠物类别,在对数据库访问中,有两个主要程序:CatalogEJB和CatalogDAO,我们从具体代码中看看设计模式是怎么应用的. Bridge模式和Adapter模式
我们发现在CatalogEJB中并没有通常的会话bean那样有对数据库操作的"select .. from ."等之类SQL操作语句,这些都被封装到DAO的具体实现中(Concrete class). 在Catalog这个示例中使用了设计模式的Bridge模式,判断是否是某种模式,主要依据其参与者的种类和相互关系,我们先看看Bridge模式的定义和参与者: Bridge模式是将抽象和行为划分开来,各自独立,但能动态的结合起来(好象搭建了一座桥)。在本例中,是将商业逻辑和数据库访问这样的行为划分开来,数据库访问专门放置在DAO中了。 Bridge模式需要两个接口(抽象类和接口通称为接口),一个用来封装抽象部分,本例中是封装商业逻辑,是CatalogEJB;还有一个是封装行为(Implementor),本例中是CatalogDAO,看看CatalogDAO代码:
Bridge模式中参与者还需要有行为接口的具体实现(ConcreteImplementor),在本例中是CatalogDAOImpl,虽然在目前宠物店中只有一个ConcreteImplementor,但是可扩展为到Mysql XML等数据源访问,比如你可以自己新增一个叫CatalogDAOImplMysql,也是作为CatalogDAO的子类。 看看CatalogDAO的一个子类CatalogDAOImpl的代码:
Bridge模式参与者总结如下: 商业逻辑抽象类 (CatalogEJB)
DAO(Data Access Object) (CatalogDAO)
DAOImplementor (CatalogDAOImpl 有可能有CatalogDAOImplSybase CatalogDAOImplMysql 等)
数据源 ( Oracle, or Sybase database via JDBC API) 提供访问具体数据库的驱动接口,如包括连接池等. 在使用数据源驱动接口时,需要使用Adapter模式,Adapter模式将两个不相关的类纠合在一起使用,Adapter模式实际是使用组合(composition)和继承(inheritance)两种方式再生类,在著名的"think in Java"的"类再生"专门提到这两个方式. 很显然,如果你对Bridge模式和Adapter模式熟悉,那么对宠物店中的Catalog理解就会非常快,同样,在宠物店其他部分如订单 用户注册 等都能迅速理解。 Factory模式和Singleton模式 本例CatalogEJB中是使用Factory模式获得一个DAO的具体实例对象,见上面CatalogEJB代码中注释。我们看看CatalogDAOFactory的代码:
在CatalogDAOFactory可以依据系统的配置文件,动态获得DAO的方法,之所以采取动态方式,当然便于用户自己增加自己的DAO方式,而不必修改代码,只要直接修改配置文件就可以。 如果在这里只需要CatalogDAOFactory产生一个实例,可以采取Singleton模式,Singleton的目的是控制类实例对象的创建,并且允许整个程序只在一点对它进行访问。Singleton本身类只能创建一个,是单线程。
那么在CatalogEJB的调用从 Facade模式 但是如果用户端直接和这些bean互动,会有以下问题:
那么我们使用Facade模式来解决这个问题,Facade的定义是为子系统中的一组接口提供一个一致的界面,很显然我们需要为这些bean提供一个统一的对外界面。如下图:
在宠物店中,ShoppingClientFacadeLocalEJB是面对所有用户端操作的统一界面,用户端操作就不直接和那些EJB如CustomerEJB或ShoppingCartEJB有联系,而是都通过ShoppingClientFacadeLocalEJB来联系的。代码如下:
Facade模式参与者: SessionFacade (ShoppingClientFacadeLocalEJB)
EJB的bean (CustomerEJB, ShoppingCartEJB等等)
这样不但可扩展性大大增强,效率也提高了,用户端只需要一次Remote对SessionFacade调用就可以了,而SessionFacade会自动定位到与它同一台服务器的那些邻居bean(CustomerEJB, ShoppingCartEJB等等),无疑减少网络拥挤,提高了速度. 总结 例如Proxy模式可以为我们在访问巨大的需要花费一定时间才能展开的对象时,提供一个代理,这样不会因为那个巨大对象而影响当前运行速度,EJB中的那些bean很显然属于巨大对象(因为它们有反复的数据库操作,这些很费时间〕。 Flyweight模式是避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).当你要从EJB中获取一系列字符串,而这些字符串中肯定有许多是重复的,那么我们可以将这些重复的字符串储存在Flyweight池(pool)中以达到共享。 AOP vs Decorator更确切地说,我们在比较AOP的拦截器和Decorator模式,它们非常相似,一些AOP框架本身就是使用Decorator模式来实现拦截器功能的。
过滤器是架构设计模式中比较常用的一种,几乎每个灵活动态系统都需要过滤器,特别是当我们的数据以内存状态出现时,过滤器无疑成为领域层的一个核心业务逻辑,当然如果你还是使用面向数据库的编程模式,过滤器功能就被你用SQL语句的where语法给替代了,那么以下你可能不必再看,请浏览这篇文章:状态对象:数据库的替代者。 当我们在一个AOP框架下编程,经常会问自己,到底过滤器这个功能是应该做成拦截器还是 其实这是一个分析模式中的过滤器实现问题,也是一个现实设计的问题:在Servlet Filter和职责链以及装饰模式Decorator和AOP几个方面如何选择? 选择标准无外乎软件的两个终极目标:简单和高质量;高质量是反映在细粒度方面,简单则是在进行设计实现时比较容易方便。 在粒度粗细方面,又有一个衡量指标:功能覆盖范围,也是一个scope,例如:如果功能需求要为某个类的方法实现实现过滤,但是,你使用一个Servlet Filter这样过滤器实现,虽然 以一个具体案例为例子: 这里的Model是ForumMessage,而对应的Service则是ForumMessageService; ForumMessageService的getMessage可以获得帖子内容,这里我们在getMessage之前加一个过滤器;那么这里应该如何选择呢? 因为我在Jdon Framework这样的Ioc/AOP框架下实现,所以想到的是AOP,我们定义一个拦截器,拦截的对象是ForumMessageService的getMessage方法,进行配置文件配置即可。似乎很简单很酷,可是真的很美丽吗? 这里,我们从原理上这几个过滤器实现进行解析一下: 这两种哪个效率最高?无疑是亲自跑一趟,可能半个小时或一个小时解决问题,但是你一旦委托第三方,我想,无论快递公司如何高效,肯定没有你自己解决快速。所以,当你能够自己处理把握时,亲自动手无疑最快的。 编码时也类似这样,如果你能在编码时,能够确定拦截那个类哪个方法,有什么比你直接写代码还能执行更快的呢? AOP的拦截器实现原理也是这样,当然,正是有这种考虑,所以所有AOP框架内,只有 AspectJ性能是最好的,因为它在你编译代码时就将你的拦截意图实现了,这属于静态织入 weaving,这就类似你自己写代码实现,但是与你自己写代码不同的是,你自己不但要写拦截器,而且还要自己手工将拦截器语句插入被拦截的那段代码,现在,使用AspectJ你就不用做后者了,这也是整合了AspectJ的Spring 2.0给我们带来的效能。所以,为了在编译代码时做手脚,就不能使用SUN的JDK原来的javac了,必须用他们自己特定的javac了。有得必有失吧。 理解了AOP拦截器拦截的原理,你可能感慨:原来所有的动态AOP(不改换编译器的AOP框架)拦截效能没有Decorator直接指定要快啊。所以,如果你在编码设计阶段,可以知道你所要拦截的方法,那么,无疑直接使用Decorator模式组成过滤器是一种好方式。 这也是我先期不怎么看好使用动态AOP实现的Spring 1.X版本,也不太热衷于同样使用 当然,不是说动态AOP没有用武之地,它类似SOA的集成作用,可以在不用修改原来代码结构上强行加入过滤器。而且AOP不只是拦截器,还有introduction,这一神奇功能可以突破Java单继承法则,显得象儿童世界里面的神奇魔法一样。 从某个方面来说,动态AOP和职责链非常类似,职责链类似击鼓传花,更像典型的打太极拳,推来推去,这也是政府效率不高的主要原因,同样,用在软件系统里,性能最差。想象一下,当这朵花传到真正主人面前时,而这个主人是传花环节中最后一个,这种效率和直接抛绣球一样,直接将花抛给真正主人相比,无疑最耗时间的,所以击鼓传花游戏就是玩的这个耗时间,在鼓声中,消耗时间,煎熬折磨你的心思,它玩的不是要把花如何快速准确地送给某人,它的目的性不确切。 我们的软件系统不能这么玩吧? 总结如下:过滤器实现方式在不保证功能前提下,从性能角度考虑有如下先后顺序:Decorator或Proxy模式;AOP拦截器。 考虑使用AOP拦截器时,最好选择那些受众面积比较广的功能,例如一些基础通用功能:权限检查;事务机制;Pool等,这些功能不是针对某个具体类或方法(方法权限除外),而是一系列类,这样使用动态AOP拦截器,就是有些性能损耗也是值得的,而且是必要的,使用其他方法也会引起这样的损耗。 如果过滤器是业务逻辑的一部分,而且在设计时,我们可以确定这些过滤器,这样我们使用Decorator模式或Proxy模式进行特定指定的拦截,当然,因为每个类/接口都需要一个附加的Decorator/Proxy,如果某个过滤功能是很多类都需要的,会形成很多Decorator/Proxy附加类,当点形成面时,这时AOP切面概念就应该浮现在你脑海,这时升级使用AOP拦截器就更好。Decorator/Proxy在点上针对性相当强,特别在这个点上有一系列过滤器需要实现时。 职责链和Decorator/AOP拦截器是有些区别的,在一个动态运行系统中,有两个概念:由客户端触发的请求对象,该请求对象需要穿透一系列过滤器(防火墙),最终可能达到持久层数据库。Decorator/AOP拦截器是对过滤器管理的一种模式,也就是说:怎么设计过滤器类;过滤器类关系是怎样;而职责链不是对类关系管理定义,而是为了处理某个请求对象而实现的。他们区别在于目标对象不一样,所以职责链是一种很具体的行为。 在这个层面上,Command模式和其是相竞争的,Command模式类似直接抛绣球,知道目的,能够最有效率,但是前提在设计编码阶段你必须知道你的目的地;Command模式和职责链的区别与Decorator和AOP拦截器的区别是类似的。 GoF模式打开的新境界GoF模式打开的新境界 没有知晓GoF模式之前,我们总是以为编码就是写一些代码,然后运行,复杂吗?如果我们来分析一下GoF模式三个类型,你会发现平时熟视无睹的代码中隐藏如此多考虑方面。 GOF模式三种类型:结构型模式、创建型模式和行为型模式其实函括了OO编码的三个方面:静态类关系、类创建成为运行时对象实例;运行时的对象运行行为,也就是说,我们在编码阶段不但考虑现阶段各个类之间静态解耦关系,而且还要考虑这些代码激活后,运行时的情况。 而以往过程化编程中,编码状况=运行状况,如何先后编码,这些编码运行时就按照这些先后编码顺序执行,两者是统一的,不可能出现运行时可能和编码时预想不一样,更何况需要我们还要在进行类编码时,考虑这些类运行时是如何实现的,有如何对这些类运行时的关系进行解耦和分离呢?所以,我们“天生”就无法理解设计模式,因为我们从来就认为软件就是实现功能,哪里还会考虑到实现同样功能会涉及各种考量了呢? 如果说设计模式是程序员的圣经,那么不掌握设计模式可能就是异教徒,从此教徒和异教徒两者之间就缺乏沟通对话平台,就象鸡对鸭讲话了。 命令模式命令模式前言
第一章:通常的命令模式 第二章:简化的命令模式 第三章:其他要说的内容 通常的命令模式: 1.1 通常命令模式有一下几个角色 调用者:(命令的执行者) 生成有序的命令队列 按顺序执行命令操作 提供撤销命令操作 记录已经操作的命令 抽象命令: 抽象的命令接口 具体命令: 具体的命令。 由三个要素组成:执行者,执行者要作的操作和被执行的对象组成。当然还可以有其他,比如将对象执行成什么结果。例如:调用Mypait类(执行者)将My rectangle(对象)填充(操作)为红色(结果)。这样就可以完全描述一个命令了。 执行者: 真正执行逻辑操作的对象 1.2原型: //调用者 public class Invoker{ List commands; //命令集合 public void setCommands(List commands){ this.commands = commands; } public void addCommand (Command command,int i){ commands.add(i,command); } public void removeCommand (int i){ commands.add(i,command); } //得代执行命令 public void action(){ for(Iterator it = list.iterator();it.hasNext();){ Command command = Command) it.next(); Command. execute(); } } …………… //还可以有丰富的redo和undo操作;(当然一些都给基于命令类提供的相应方法) } //抽象命令 abstract class Command { abstract public void execute(); abstract public void unexecute(); abstract public void reexecute(); //一般有这样这个方法,根据需要可以增删 } // 具体的命令类1:写作命令,选择一个作者(Author类实例对象),让他写作(调用它的write方法)写作的对象是书(Book的实例对象)形成了一个写作的命令,写作的对象是Book的实例 public class WriteCommand implement Command { Author author; //执行者 Book book; //要执行的对象 public WriteCommand (Author author,Book book) { this. author = author; this. book = book; } // 在这里执行要执行的操作 public override void Execute() { author.write (book); } } // 具体的命令类2: 出版命令,选择一个出版社(publisher类实例对象),让他出版书(调用它的publisherBook方法)出版的对象是书(Book的实例对象)形成了一个出版的命令 public class PublishCommand implement Command { Publisher publisher; Book book; public PublishCommand (Publisher publisher) { this. publisher = publisher; this. book = book; } // Methods public override void Execute() { publisher. publisherBook(book); } } // Publisher和Author类为执行者 略 这样我们的客户端代码就可以这样写: //如果我要出一本书 //一本空白的书 Book book = new Book(); //先找一个作者和出版社 Author author = new Author(); Publisher publisher = new Publisher (); //产生命令集合 Command writeCommand = new WriteCommand (author,book); Command publishCommand = new PublishCommand(publisher,book); List commands = new List (); Commands.add(writeCommand); //找个调用者,把命令给它,让他来根据命令协调工作 Invoker invoker = new invoker(); Invoker.setCommands(commands); public void addCommand (Command command,int i){ commands.add(i,command); } invoker.action(); 特点: 1. 分布登记统一执行: 在作程序时,经常碰到一些需求,先注册一些操作,并不马上执行,等最终确定后统一执行。如一个具体的例子:用户定制自己的报表,可以订阅饼,柱,折线,曲线图,客户选择相应的报表组合,这样对应一个命令集合,在没确定之前用户可以增删这些报表(命令),等最终确定统一交给调用者根据命令执行,生成组合报表。实现了命令分布提出,确定后统一执行的功能。 2.形如流水线操作:还是出书的例子 //先是一本空白的书: Book book = new Book(); //找几个作者 Author author1 = new Author(); Author author2 = new Author(); //把写1,2章的名类分别给这两个作者 Command writeCommand = new Write1Command (author1,book); Command writeCommand = new Write2Command (author2,book); List commands = new List (); Commands.add(writeCommand); //调用者 Invoker invoker = new invoker(); Invoker.setCommands(commands); //流水写书 invoker.action() 实际上在aciton这一方法中,invoker按照命令,让两个作者流水写作这本书。(类似一个书的流水线加工工厂) 这样我们的书就被流水加工成功(当然这本书只有两章) 这样就给了我们一种系统设计的框架, 模型+工具+命令 客户端产生命令,命令调用工具操作模型。 Book 相当于模型 Author 相当于和多工具类中的一个 Command 命令 3.系统需要支持命令的撤消(undo)。提供redo()方法 我们可以和容易的加入undo和redo,这个不难理解 4.在Invoker中我们可以实现跟踪,和日志。 5.当系统需要为某项复制增加形的功能的时候,命令模式使新的功能(表现为一种命令)很容易地被加入到服务种里。 命令联系了工具类即执行类和系统逻辑, 简化/变化的命令模式: 命令模式的角色比较多,在实际应用种我们可以根据所需要的功能和不需要的功能加以简化。 1》去掉 调用者 产生命令集合后,我们可以直接在client中迭代执行执行操作 2》 变化 调用者 成为 跟踪者 //调用者 public class Invoker{ List commands; //已经执行完毕的命令集合 public void addCommand (Command command,int i){ commands.add(i,command); } public void action(Command command){ //执行操作 command. execute(); // commands.add(command); } } …………… //还可以有丰富的redo和undo操作;(当然一些都给基于命令类提供的相应方法) } 这样这个类就记录了所有执行过的操作。 3》去掉 命令 用map替代 我们完全可以用map代替命令,这样无需定义各种命令类 我们改进例子 Author author = new Author(); Publisher publisher = new Publisher (); Map m = new HashMap; m.put(author, write); m.put(author, publisherBook); 在Invoker的action方法: 得代map 运用java反射来调用方法; 4》去掉执行者: 直接在命令中(execute方法种)加业务逻辑。这样只适合于简单的小的系统. 其他要说的内容 1》 将某些参数传给某个方发的方式很多,除了当作方法的参数外还可以当作类的成员便俩变量传入: 这就为命令的抽象带来了极大的方便 abstract class Command { abstract public void execute(); } 当我们已经有了执行者(类Test)方法execute(args1,args2 ….argsn) 我们不必向Command加入execute(args1,args2 ….argsn)抽象方法,在说即使加了,在我们迭代的时候也无法判断或十分不容易判断哪个命令调用哪个execute方法。 那么我们可以这样 class ConcreteCommand : Command { Test test; args1 args2 ….. argsn public override void Execute() { test. execute (args1,args2 ….argsn); } } 2》 在想跟踪操作的时候,一般为每一个操作对象分配一个调用者,操作对象在调用者中设置。(可以抽象出一个总的调用者,来协调调用每一个具体的调用者) 3》 命令的抽象粒度我觉得是要注意的。 4》 理解思想,不要机械的照搬。消化成自己的,加以灵活的运用和创造在是根本出路。 所谓命令模式的根本思想就是在 先形成命令,在根据命令执行。 李开复给中国学生的信
Eclipse启动错误
|
|
|