程序设计模式-“OPGA设计模式”简介
即(One Parameter Go Anywhere)“一参走天下”
这是个新想法,看起来很不错。能够解决我们在编程时,要考虑太多的参数传递和回传的问题。如果能实现一种模式,让我们无须传递这么多参数,那是多么的好啊!
先看看,我们编程中经常会遇到的问题:
1、要求Service类回传多个信息。
可行的做法:
public ResultBean doService(xxx){}
即定义一个返回的ResultBean,用于存放返回的多个信息。
2、要求把一堆参数交给下一个方法去处理。
比如,如下代码:
public ResultBean doService(String taskType, String timerAttr,
List<FormVO> formAttrs, List<OthersVO> otherAttrs,
String taskName, String serviceId, String orgID, String userID)
或者把参数全都封装成一个bean,如下:
public ResultBean doService(StatReqBean bean)
第一种呢,参数倒是传递了,但是用起来不给力,特别是链式传递(比如说上面的doService方法用完之后,又把这些参数传给一个叫xxxDAO的方法),又要传这么多参数,麻烦。参数你就不能少一点吗?没办法啊,程序有这么复杂啊,Service层或者Dao层人家就需要这些参数,你怎么办?
第二种呢,其实更“科学”一点,唯独有一点不好的是,每次都要定义各种bean,请求有请求的各种bean,返回有返回的各种bean。(比如在航空公司B2B平台项目中,习惯的是请求的bean命名为XxxReqBean,导致一大堆XxxReqBean,返回的bean命名为XxxRespBean,XxxRespBean也是一大堆)。
很早以前,我还没用Struts 1、2,那时用纯Servlet和JSP做开发,表单参数一大堆,要是想把这些参数传递到Helper类去处理,比如校验,就要传递很多参数,麻烦!
后来用上了Struts的表单自动封装到VO的功能,把数据都定义成一个VO,然后这个VO对象,就可以方便的传到程序的各处了,不管是传到Helper类去处理,还是传到Service或Dao中,都是很方便的。
现在Spring盛行,我们又学到了很巧妙的一招——BeanFactory、ApplicationContext、……即,我们可以把需要的东西放到一个bean容器中,要什么就拿什么。通过bean的id(name)去获取bean。
当然,这个容器需要一些通用性,要能“海纳百川”。
好,思路就是这样。我们需要定义一个通用的bean容器。
我将它命名为:BeanSet
设计目的:可以轻松add一个对象,然后轻松get出来。
那么,现在参数传递的方法,参数都可以统一了,写法如下:
public void doService(BeansSet beans){}
看到没,我们参数统一为BeansSet,这里面可以装任何我们想要的参数,获取也很简单,只需要调用beans.get( id )即可。聪明的你,也许会想到,这个BeansSet怎么这么神奇呢,不会全部都是存的Object对象吧,到时候还要强制转换对象类型,是不是比较麻烦,而且效率低。
呵呵,这些问题,设计者早就已经想到了,咱们既要易用,又要高效!
自从JDK 1.5之后,泛型是被推荐使用,JDK能非常高效地支持泛型!
于是,我通过泛型,实现了类型自动aware的功能。
BeansSet里面装了BeanObject<?>,这是个泛型的对象,在new的时候指定其对象类型,比如:
BeanObject<String> nameObj = new BeanObject<String>(id, value);
其实,BeanObject也是一个容器,它里面可以存储一个特定类型的Java对象实例。通过getObject()方法获取对象的实例(称之为value)。还有一个id,也就是这个对象的名称。通过getId获取。还有一个getType()方法,可以获取该Object的Class类型。
比如String name = "zollty"; 这个name参数,我们要把它装载到BeanObject中:
BeanObject<String> nameObj = new BeanObject<String>("name", name);
然后加入BeansSet里面,
beans.add( nameObject );
现在看,本文的标题,叫做“OPGA设计模式”,“一参走天下”。
这个BeanObject组装好了之后,就可以传给其他程序了,其他程序中去拿自己需要的bean。
我们经常头痛的问题又来了,一个方法可能有多个方法名相同,但是参数个数、类型不同的情况。假设有4个参数,如果要提供全面的构造函数,那要写4的排列组合数(即4*3*2*1=24)个构造函数,而且java相同类型和参数个数的方法或构造函数是不被允许的,所以即使你想写24个构造函数,除非是4个类型各不相同的参数,否则,还不支持这么多构造函数或方法。
比如:
// 两个参数
public Result fileUpload(Request request, String dir) {
fileUpload(request, absDir, null, null); // 调用4参的方法,用两个null参数
}
// 三个参数
public Result fileUpload(Request request, String dir, String suffix) {
fileUpload(request, absDir, null, inludeSuffix);
}
// 四个参数
public Result fileUpload(Request request, String dir, int len, String suffix, String mimeType) {
fileUpload(request, absDir, null, inludeSuffix);
}
大家是不是经常见到这种代码,你用起来是很方便,有时只用两个参数的,某些情况又可能用三、四个参数的。但是对于写API的人来讲,太麻烦了,还要写这么多个不同参数的方法,真是麻烦!
这个还不算什么,最坑的是,有一次我们被坑惨了,本来是一个很小的改动(修改了一个接口,为一个方法增加了一个参数而已),但是要通知所有的接口调用者统统修改他们的代码。造成了多少人力和时间的消耗?比如
public interface IService{
Result doProcess(String appid, List data, User user);
}
现在,改了接口,给doProcess方法多加了一个参数orgid,如下
Result doProcess(String appid, List data, User user, String orgid);
这一改是“牵一发而动全身”啊!,别人以前的代码继承了这个接口,现在全是一堆红叉,报错:未实现接口的所有方法!
有了“OPGA设计模式”,就可以基于一种惯性的编程习惯:我们只传一个参数——BeansSet,比如:
void doProcess(BeansSet beans);
何时何地,我们都传一个参数!因为它是万能的。这是“一参走天下”的第一层含义。
这样,我们也不会像上面所遇到的那种情况,去改接口,然后其他实现类都报错。
我们基于一个原则:“服务者”只需要把值传给“被服务者”,至于哪些值要用,哪些值不用,“服务者”根本不用关心。同理,“被服务者”按需去取它需要的东西,用到哪些取哪些,至于有没有,取了再说。
注意,我们可以把Result对象也省了,因为它也被BeansSet对象取代了,返回的结果也存在BeansSet中。这样,方法从调用,到全部执行完,算是一个周期。在这个周期中,BeansSet可以全线通吃的。这是“一参走天下”的第二层含义,它可以贯彻到一个方法的整个执行线路。
这种设计模式,我刚开始还不是很习惯,但现在我越用越顺手了,它的好处就是基于一种良好的习惯。现在,又发现了很多的好处,比如:
有些方法要throw Exception,但throw Exception这个动作,是和“终止程序运行”绑定了的,也就是说throw了Exception,方法也就返回了。但有时候,我们希望在throw了一些非核心的Exception的情况下,程序不返回,依然继续往下运行。
如果throw Exception的话,意味着我们只有两种状态:要么完全成功,要么完全失败!我现在要多加几种状态,在方法中可以保存多个错误信息,全部都放到BeansSet中,比如throw 了WarnExp1、WarnExp2、ErrorExp1、FatalExp1,把这些信息都回传回去,同时也可以传一个isSuccess的标识,非常简单,只需要添加到BeansSet中去就可以了,比如:
beans.add( new BeanObject<Boolean>("isSuccess", false) );
上层如果需要,它可以查看“有没有成功啊,有没有轻微错误和警告啊,有没有造成严重影响的Fatal错误啊”
小伙伴们,读完动手试试吧。用多了,你会真正喜欢上这种设计模式的。它不是强制性的编程要求,你可用可不用。但是多了一个解决方案,而且是实用的解决方案,总算是一件好事!而且这个设计模式的强大,连作者自己都还未完全发掘,上面那些例子,只是凤毛麟角、普通应用,说不定还能够衍生出一些很Nice的功能!