开发中经常遇到的诸如如下根据不同的标志从而采取不同处理逻辑的if-else/switch 等代码块
if (ConstantValues.TAOBAO_CHANNEL_LIST.contains(config.getChannelCode())) {
taoBaoDownloadHandle.downLoadOrder();
} else if (ConstantValues.TBFX_CHANNEL_CODE.contains(config.getChannelCode())) {
taoBaoFxDownloadHandle.downLoadOrder();
} else if (ConstantValues.SNFX_CHANNEL_LIST.contains(config.getChannelCode())) {
snfxDownloadHandle.downLoadOrder();
}else if (ConstantValues.SN_CHANNEL_LIST.contains(config.getChannelCode())) {
suningDownloadHandle.downloadRefund();
} else if (ConstantValues.PDD_CHANNEL_LIST.contains(config.getChannelCode())) {
pddDownloadHandle.downLoadOrder();
}...//(越来越多又臭又硬)
这样的代码有好处:逻辑简单一看就懂,但是随着需求的变更增加会导致代码块越来越臃肿,可读性和扩展学性都相当之差,那么这时候就需要采用策略模式来重构代码,使之优雅美观(高大上)
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
1.抽象策略:
策略类,通常由一个接口或者抽象类实现,里面定义抽象的方法行为。
2.具体策略:
包装了相关的算法和行为,具体来完成某个策略的类。
3.上下文 :
动态根据策略返回给客户端匹配的那个策略类。
需求:根据渠道编码选择对应的订单下载器
**抽象策略类:AChannelDownloadHandle **
public abstract class AChannelDownloadHandle {
@Autowired
private HandleContextService handleContextService;
public void getOrder(String channelCod){
AChannelDownloadHandle downLoadHandle= handleContextService.getDownLoadHandle(channelCod);//步骤 根据策略模式来获取对应的订单下载器
setSpConfiger(downLoadHandle);//步骤2 设置配置参数
downLoadHandle.downLoadOrder();//步骤3 执行具体的下载逻辑
}
//设置配置类
public void setSpConfiger(AChannelDownloadHandle downLoadHandle) {//...
}
//下载订单
public abstract void downLoadOrder();
//...其他方法
}
AChannelDownloadHandle 是一个抽象类,而我贴出来的setSpConfiger()方法是有方法体的,这里是顺便强调一个抽象类和接口的区别:抽象类可以有具体方法同时为后面的模板方法模式做铺垫
具体策略类
1.TaoBaoDownLoadHandle代码
public class TaoBaoDownLoadHandle {
@Service("taoBaoDownloadHandle")
//自定义注解,标识渠道编码
@ChannelTag({ConstantValues.TB_CHANNEL_CODE,ConstantValues.TMALL_CHANNEL_CODE})
public class TaoBaoDownloadHandle extends AChannelDownloadHandle {
//具体策略
@Override
public void downLoadOrder() {
//具体代码行为
}
}
}
2.SuNingDownLoadHandle代码
@Service("suNingDownloadHandle")
//自定义注解,标识渠道编码
@ChannelTag({ConstantValues.SN_CHANNEL_CODE})
public class SuNingDownLoadHandle extends AChannelDownloadHandle{
@Override
public void downLoadOrder() {
//具体代码行为
}
}
上下文HandleContextService
根据注解的value动态获取需要的aChannelDownloadHandle(订单下载策略实现类)
public interface HandleContextService {
/**
* 根据渠道获得对应的downLoadHandle
* @param channelCode
* @return
*/
public AChannelDownloadHandle getDownLoadHandle(String channelCode);
}
//上下文实现类HandleContextServiceImpl
@Service
public class HandleContextServiceImpl implements HandleContextService{
//自动注入所有实继承了AChannelDownloadHandle的子类
@Autowired
private List<AChannelDownloadHandle> refundHandleList = new ArrayList<>();
public AChannelDownloadHandle getDownLoadHandle(String channelCode){
if (StringUtils.isBlank(channelCode)) {
return null;
}
//核心逻辑:根据注解的value获取需要的aChannelDownloadHandle
return refundHandleList.stream()
.filter(f -> Arrays.asList(f.getClass().getAnnotation(ChannelTag.class).value()).contains(channelCode))
.findFirst()
.orElse(null);
}
}
客户端代码
使用上下文类handleContextService根据channelCode渠道编码动态获取订单下载策略实现类
@Autowired
private HandleContextService handleContextService;
AChannelDownloadHandle downLoadHandle= handleContextService.getDownLoadHandle(channelCode);//步骤 根据策略模式来获取对应的订单下载器
自定义注解ChannelTag
用来标识所有的实现类,方便上下文类进行动态策略选择
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ChannelTag {
String[] value();
}
这样我们便可以据不同的channelCode获取不同的订单下载器,做到代码简洁而优雅,符合设计模式的开闭原则,与模板方法模式结合更加强大灵活,下面具体分析二者区别
1.模板模式
模板模式一般有两部分组成,即抽象模板和具体模板
抽象类来定义一个算法模板即抽象模板(一般拥有多个步骤),在算法实现的不同步骤上抽象方法由子类继承并提供具体实现(具体模板),常见的就是不同步骤提供doXXX抽象方法留给子类实现。。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟加载到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
2.策略模式
上文已经提到策略模式的三个角色:抽象策略,具体策略,上下文
模板模式一般只针对一套算法,注重对同一个算法的不同细节进行抽象提供不同的实现。
策略模式注重多套算法多套实现,在算法中间不应该有交集(有交集的代码应该提取到模板方法中),因此算法和算法只间一般不会有冗余代码!因为不同算法只间的实现一般不同很相近。
3.举例:仍然以上面的项目代码为例
现在每个渠道的拉取方式必然是不同的,提供的API方法也是多种多样的,因此每一个渠道对应一个下载器,可以看做是策略的选择。
而拉取订单的每个模板方法中,获得下载器只是第一个布步骤,下载器还需要进行统一的参数配置,明显是重复的方法调用,所以可以把参数配置抽取出到父类中以减少代码的冗余如下代码块中步骤2,而步骤3由具体模板(子类)来实现抽象模板(父类)中的实现细节
public void getOrder(String channelCod){
AChannelDownloadHandle downLoadHandle= handleContextService.getDownLoadHandle(channelCod);//步骤 根据策略模式来获取对应的订单下载器
setSpConfiger(downLoadHandle);//步骤2 设置配置参数,公共代码提取
downLoadHandle.downLoadOrder();//步骤3 执行具体的下载逻辑,模板方法模式由子类实现具体的方法逻辑
}
所以模板方法模式可以减少代码的冗余,通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合开闭原则。但是却也违反了里氏替换原则,因为它重写了父类方法,有可能改变父类的结果。
4. 总结
.策略模式的关注点更广,模板模式的关注点更深。而且两种模式可以一起使用,即具体某个策略下可以通过模板减少不同步骤的冗余代码。
在思想和意图上看
模板方法更加强调:
1)定义一条线(算法流程),线上的多个点是可以变化的(具体实现在子类中完成),线上的多个点一定是会被执行的,并且一定是按照特定流程被执行的。
2)算法流程只有唯一的入口,对于点的访问是受限的【通常用受保护的虚函数来定义可变点】。
策略模式更注重于:
一个“策略”是一个 整体的(完整的) 算法,算法是可以被整体替换的。而模板方法只能被替换其中的特定点,算法流程是固定不可变的。策略模式符合优先使用对象组合,而不是继承的合成复用原则