Template method vs Callback
以前曾在《也谈谈Spring中的Template和Callback模式》中谈过这两个模式的实用场合,那时候没有给出代码。
最近遇到一个问题,可以很好的说明这两种模式的使用场合。
需求:我暑假实习公司做的一个产品,我们Server组主要事情是:一、把页面的信息组装成DTO,使用JAXB技术
将定义的消息格式(xml schema)自动生成对应的Object,把DTO装配对应的Object,然后marsh成xml(JAXB是什么
东东可以参见以前我写的《尝试了一下JAXB2.0》),最后把消息发送给Client端。二、从Client端接收到xml,然
后unmarch成Object,然后把Object装配成DTO,最后传到页面,来设置页面。
我们可以看到整个过程非常的固定,无非是Send还是Get信息的问题。所以我们很容易想到使用摸版来解决,事实上我们最常用的Servlet也是这两种过程Post和Get,它同样使用了摸版方法。
举一个例子,一个Site里有许多Rules,我们要操作Rules(例如增删改之类的操作),不同的操作过程可能不同,
例如新增一个Rule,我们只需要做上面说的第一件事情,而如果要修改Rule则两件事情都要做。我们现在就用模版
方法来实现:
public String send( long agentId, String fullPath, int typeId,Object dto);
public Object get( long agentId,String fullPath, int typeId);
}
public class CABaseServiceTemplate implements CAIBaseService{
public String send( long agentId, String fullPath, int typeId,Object dto){
RequestBean reqBean = doPrepareToSend(agentId,fullPath,typeId,dto); // 把dto装配成Object[requestBean]
Response response = doProcessRequest(reqBean); // 真正把xml发送给Client端,这个过都是一样的,可以抽象出NetworkService来处理
return doProcessSendResponse(response); // 处理返回的response,例如根据response判断是否发送成功,以及失败的原因
}
public Object get( long agentId,String fullPath, int typeId){
RequestBean reqBean = doPrepareToGet(agentId,fullPath,typeId); // 装配成Object[requestBean]的根,这样Client就知道消息是什么类型的,从而设置信息返回
Response response = doProcessRequest(reqBean); // 真正把xml发送给Client端,这个过都是一样的,可以抽象出NetworkService来处理
return doProcessGetResponse(response); // 处理返回的response,例如把response装配成Dto,判断是请求否发送成功,以及失败的原因
}
protected RequestBean doPrepareToSend( long agentId, String fullPath, int typeId,Object dto);
protected String doProcessSendResponse(Response response);
protected RequestBean doPrepareToGet(agentId,fullPath,typeId);
protected Object doProcessGetResponse(Response response);
}
// 对于Rule
class AddRuleServiceImpl extends CABaseServiceCallbackTemplate {
protected RequestBean doPrepareToSend( long agentId, String fullPath, int typeId,Object dto){
// ...
}
protected String doProcessSendResponse(Response response){
// ...
}
protected RequestBean doPrepareToGet(agentId,fullPath,typeId){
// 因为我们不需要这个操作所以,空实现
}
protected Object doProcessGetResponse(Response response){
// 因为我们不需要这个操作所以,空实现
}
}
class EditRuleServiceImpl extends CABaseServiceCallbackTemplate {
protected RequestBean doPrepareToSend( long agentId, String fullPath, int typeId,Object dto){
// ...
}
protected String doProcessSendResponse(Response response){
// ...
}
protected RequestBean doPrepareToGet(agentId,fullPath,typeId){
// ...
}
protected Object doProcessGetResponse(Response response){
// ...
}
}
我们发现对于一个Rule就需要三到四个Service,Service的粒度显得有点太细,因为我们我们继承了CABaseServiceCallbackTemplate,我们Service的粒度已经由CABaseServiceCallbackTemplate
控制了,
这就是继承的不灵活性,同时我们看到我们在AddRuleServiceImpl中还需要空实现两个方法,当然我们
可以继续对CABaseServiceCallbackTemplate封装出一个新的Template,将他们都默认空实现了CABaseServiceCallbackTemplate未实现的方法,这样我们在AddRuleServiceImpl就不需要空实现了,
另外,
doPrepareToSend,doProcessSendResponse到底是干什么的(由于Send方法在CABaseServiceCallbackTemplate
定义的),别人头一次看说不定发甍。综上,在这里使用Template method模式,产生了过于琐碎的类,并且
显得不够直观。
下面我们考虑使用Callback模式来实现,来避免上面的问题:
public String send( long agentId, String fullPath, int typeId,Object dto);
public Object get( long agentId,String fullPath, int typeId);
}
public interface CAProcessSendResponseCallback{
RequestBean doPrepareToSend( long agentId,String fullPath, int typeId,Object dto);
}
pubilc interface CAProcessSendResponseCallback{
String doProcessSendResponse(Response response);
}
public interface CAPrepareToGetCallback{
RequestBean doPrepareToGet( long agentId,String fullPath, int typeId);
}
public interface CAProcessGetResponseCallback{
public Object doProcessGetResponse(Response response);
}
public class CABaseServiceCallbackTemplate implements CAIBaseCallbackService{
public String send( long agentId, String fullPath, int typeId,Object dto,
CAPrepareToSendCallback prepareToSendCallback,CAProcessSendResponseCallback processSendResponseCallback){
RequestBean reqBean = prepareToSendCallback.doPrepareToSend(agentId,fullPath,typeId,dto); // 把dto装配成Object[requestBean]
Response response = doProcessRequest(reqBean); // 真正把xml发送给Client端,这个过都是一样的,可以抽象出NetworkService来处理
return processSendResponseCallback.doProcessSendResponse(response); // 处理返回的response,例如根据response判断是否发送成功,以及失败的原因
}
public Object get( long agentId,String fullPath, int typeId,CAPrepareToGetCallback prepareToGetCallback,
CAProcessGetResponseCallback processGetResponseCallback){
RequestBean reqBean = processSendResponseCallback.doPrepareToGet(agentId,fullPath,typeId); // 装配成Object[requestBean]的根,这样Client就知道消息是什么类型的,从而设置信息返回
Response response = doProcessRequest(reqBean); // 真正把xml发送给Client端,这个过都是一样的,可以抽象出NetworkService来处理
return processSendResponseCallback.doProcessGetResponse(response); // 处理返回的response,例如把response装配成Dto,判断是请求否发送成功,以及失败的原因
}
}
public class CASiteRuleManageServiceImpl {
private CABaseServiceCallbackTemplate caBaseServiceCallBackTemplate;
public CASiteNewRuleDto getEditRuleInfo( long agentId, String fullPath, int typeId) {
return (CASiteNewRuleDto)caBaseServiceCallBackTemplate.get(agentId, fullPath, typeId, new CAPrepareToGetCallback (){
public RequestBean doPrepareToGet( long agentId, String fullPath, int typeId){
// ...
}
}
, new CAProcessGetResponseCallback (){
protected Object doProcessGetResponse(Response response){
// ...
}
}
);
}
public String sendEditRuleInfo( long agentId, String fullPath, int typeId, CASiteNewRuleDto dto) {
return caBaseServiceCallBackTemplate.send(agentId, fullPath, typeId, dto, new dto,CAPrepareToSendCallback() {
public RequestBean doPrepareToSend( long agentId, String fullPath, int typeId,Object object){
// ...
}
},
new CAProcessSendResponseCallback(){
public String doProcessSendResponse(Response response){
// ...
}
}
);
}
public String sendAddRuleInfo( long agentId, String fullPath, int typeId, CASiteNewRuleDto dto) {
return caBaseServiceCallBackTemplate.send(agentId, fullPath, typeId, dto, new dto,CAPrepareToSendCallback() {
public RequestBean doPrepareToSend( long agentId, String fullPath, int typeId,Object object){
// ...
}
},
new CAProcessSendResponseCallback(){
public String doProcessSendResponse(Response response){
// ...
}
}
);
// ...other operation
}
}
我们发现使用Callback模式,我们可以把对Rule的操作组织成一个Service,避免产生大量的Service,同时我们可以随意使用Get和Send操作,而不象Template method受缚于上层定义步骤,以及要实现的方法,同时我们可以给Service的方法很好的命名(例如sendAddRuleInfo),从而使得程序更加清晰。唯一的不完美的地方是使用了内部匿名类,给程序的阅读带来点困难,当然可以用内部类来代替匿名的,但大家似乎更习惯使用匿名内部类,如果Java象Ruby那样能够支持block就好了(Java 7据说要支持closure,但由于Java历史的包袱太重,实现是何等的丑陋,非泛型所能比--Java为了向前兼容,泛型的实现实在难以和c++和c#相比)。
从上面分析可以看到如果不是真正的"is-a"关系的话,组合优于继承绝对是至理名言,继承就会对子类有所限制,而组合则没有什么限制,显得更加灵活。一般情况下如果一个操作的子步骤比较少(三个以下),特别是如果你不想生成太细粒度的对象时,用Callback来代替template method能够获得更好的灵活性,子步骤多则使用template method.