您的位置:首页 > 其它

项目实践之模板方法模式

2015-02-16 00:00 267 查看
摘要: 靡不有初, 鲜克有终。
这是一篇关于设计模式在项目中的实践运用,是一种设计思路,不是设计模式的理论讲解。

业务场景:公司为了提升用户转化率,往往会开展很多活动来吸引用户;比如什么抽奖活动、充值送红包活动、邀请好友活动等等,而且有时候活动内容都一样就形式不同。

例1:某活动必须先交易1元才能抽奖并且一个人只能抽2次、奖品iPad。。。。;需要展示中奖信息。

例2:某活动邀请1人送1次抽奖机会、但是参与过活动1并中奖的人抽不到奖;嘿嘿。

都需要判断活动是否开始或者结束。

如果让你做,你会怎么做(不是具体实现,主要是设计思路;怎么易于扩展,开展新活动更加迅速简单。)

实现1:每个活动封装一个接口、一个实现类;互不想干、老死不相往来。



不知道大家是这个想法吗?在我未进入现在这家公司时,就是这样做的;可能单纯的看也没什么不好的,但一个设计思路在项目中往往不是独立的个体,而且有时候在没有外部压力推动或者对自身要求不高(也可能没经验)的情况下,后来的编写者大部分都会遵循前一个的思路进行编码。

所以,既然是各写各的,大家互不影响;后来变成了:

服务端:每个活动新建interface,实现类,dao,bean,表,又因为是后台服务需要提供前台客户端调用,而公司服务端框架是RMI;每个method又要封装一个接口对外提供服务。

客户端:每次相应的也要重新调用新接口。

不知道如果是你来不断的重复这个编码过程,你会奔溃吗?

实现2:可能有的人想了,为什么不能对外只提供一个interface,不同的活动不同的实现;oop思想,所以有了下图。



我们再回到上面的例子,接下来是不是:

例1:判断活动时间、判断用户是否抽过2次奖、抽奖;

例2:判断活动时间、判断用户是否参加过活动1并中奖、抽奖。

如果严谨点,两个活动是不是都需要判断入参是否正确。聪明的你是不是已经发现判断活动时间、入参是公共方法,oop原则提取到父类;但我们不能更进一步吗?不管是判断抽奖2次,亦或活动2排斥活动1,还是其他;都是校验用户活动资格,我们又可以抽象一个方法出来了。结束了!其实我们还可以继续提取,两个活动的流程是相似的:第一步:都是活动校验,第二步:具体的活动处理。所以新鲜改造出炉的UML,看下面。



由抽象类实现doProcess方法,并控制行为,子类实现扩展可变的部分,这就是模板方法模式;很简单吧。(再次声明:这不是一篇讲解模板方法模式的blog,需要看模板方法模式的讲解度娘很多);代码片段:

ActivityService:

/**
* 受理活动订单,一般分为如下步骤:<br>
* 1、进行规则性校验,常规性校验有、”基础参数校验“、”活动时间校验“、”活动份额校验“、”活动资格校验“、
* 	 若个别活动还有特殊性规则校验,则自行添加。<br>
* 2、预办理活动,进行实际的活动办理步骤,但不保存数据;先预办理活动保证活动实际受理成功率;若实际活动
* 	 办理失败,预办理活动已经修改的参数不进行实时回滚,另起JOB进行调度处理。<br>
* 3、实际办理活动,实际为用户办理活动。<br>
*
* @param activityVo
* 	<p>活动基础VO,具体活动的VO需继承此对象<br>
*
* @throws Exception
* 	<p>调用活动受理接口,必须处理异常:一般为系统异常和自定义异常(BizException)<br>
*/
public ActivityRstVo doProcess(ActivityVo activityVo)throws Exception;

AbstractActivityService:

public ActivityRstVo doProcess(ActivityVo activityVo)throws Exception{
//校验活动基础规则
generalValidate(activityVo);

//活动自定义规则
doActivityFilter(customValidate(activityVo),activityVo);

//预办理活动
beforeActivity(activityVo);

//实际办理活动
return doActivity(activityVo);
}

/**
* 活动一般性规则校验
*
* @param activityVo   .
* @throws Exception
*/
protected void generalValidate(ActivityVo activityVo)throws Exception{
boolean paramFlag = validateActivityParam(activityVo);
if(!paramFlag){
throw new BizException(RespEnum.PARAMETERS_ERROR);
}

boolean activityTimeFlag = validateActivityTime(activityVo);
if(!activityTimeFlag){
throw new BizException(RespEnum.ACT_NOT_EXIST_ERROR);
}

boolean costFlag = validateActivityCost(activityVo);
if(!costFlag){
throw new BizException(RespEnum.ACT_AMT_ERROR);
}

boolean qualifiedFlag = validateActivityQualified(activityVo);
if(!qualifiedFlag){
throw new BizException(RespEnum.ACT_IS_UNQUALIFICATION);
}
}

/**
* 校验用户活动资格,是否还能参加活动。
*
* @param activityVo  .
* @return boolean:true 还能参加活动 false 不能参加此活动
* @throws Exception
*/
protected abstract boolean validateActivityQualified(ActivityVo activityVo)throws Exception;

/**
* 实际为用户办理活动,具体活动业务需要实现此方法
*
* @param activityVo     .
* @throws Exception
*/
protected abstract ActivityRstVo doActivity(ActivityVo activityVo)throws Exception;


其他:在实际项目中,一个接口不可能定义所有的方法,也不能定义所有方法。即你不可能预知所有的活动形式,总有几个特殊逻辑,就比如猫和狗都是动物,但猫会爬树,狗不会;所以我们需要给猫增加一个爬树的能力;IActivityService1就是那个爬树的能力。



这样我们对外就可以仅提供一个接口IActivityService=new subClass()(取不同的子类实现);通过反射调用不同的方法(或者其他方式),做到仅关注具体业务逻辑的实现,不在循环重复流程化的开发。

总结:快过年了,也工作三年了、正好总结下。希望我的抛砖引玉能换来你更好的方案或者意见,如果对你还有那么一点帮助更开心了。由于本人水平有限,不足或错误之处希望大家能不吝赐教,请勿拍砖。也希望自己能不断坚持写下去。

ps:写这篇blog时,重新看了下“设计模式之禅”的模板方法模式(比起一年前看有种豁然开朗的感觉),最佳实践:父类不能调用子类的方法。按照我的理解有如下两种情况:

1、父类parent的doSomething里面调用son的test方法。这个我也是极度不认同的;按照我的想法,有父亲时不一定有儿子(不要纠结中文意思),父类怎么能调用子类的方法。

2、父类的声明子类的实现,client(不是父类,客户端调用的类)通过放射调用子类特有的方法。比如上面的爬树方法。这个按照我的理解是没问题的。

中文的意思太强大了,是不是我理解错作者的意思了,并没有指第二种情况仅指第一种?希望有人能解惑,谢谢!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息