策略模式
策略模式属于行为型模式
概述:在日常开发中,会存在这样一个场景,根据用户请求的参数类型来调用不同的处理逻辑,而大多数开发者遇到这样的场景会优先使用if else 分支来处理这样的业务需求,如果需求迭代过程中,添加了新的类型,就需要在原来的代码上添加新的分支,这样就违背了开闭原则。本篇文章通过一个例子来介绍策略模式的概念和使用方法。
定义:策略模式是定义了一系列的算法,并且将其封装起来,使得这些算法可以相互替换,且不会相互影响。
- 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
- 具体策略角色:包装了相关的算法和行为。
- 环境角色:持有一个策略类的引用,最终给客户端调用
计算物流费用的例子
获取快递费用,我们在寄快递的时候,选择不同的快递公司,所需要的运费不一样,例如(简单打比方):中通的首重价8元,续重价3元,而圆通的的首重价10元,续重价2元。
代码实现
- 业务方法
@Service
public class FreightService {
private int YTInitFreight = 10;
private int ZTInitFreight = 8;
public String getFreightByType(String typeCode, Integer weight) {
//获取物流公司类型枚举
FreightType type = FreightType.getByCode(typeCode);
Integer result = 0;
if(FreightType.YT.equals(type)){
//圆通的计算逻辑
result = weight - 1 > 0 ? (weight - 1) * 2 + YTInitFreight : YTInitFreight;
}else if(FreightType.ZT.equals(type)){
//中通的计算逻辑
result = weight - 1 > 0 ? (weight - 1) * 2 + ZTInitFreight : ZTInitFreight;
}if else {
// 业务迭代时,添加其他类型快递公司须添加新的分支
}else{
return "运费类型不正确";
}
return result.toString();
}
}
- 前端入口
public String calculationFreight(@RequestParam String typeCode,
@RequestParam Integer weight){
return freightService.getFreightByTypeV2(typeCode,weight);
}
测试
以上的例子,通过if else 可以实现我们的需求,但是每当有添加新的快递公司需求时,都需要在原来的代码上新增分支代码,现在的业务代码只是简单的,在实际的开发中,业务逻辑代码远远比这多,如果快递公司少时,还能接受,但一增多,这个方法的代码也会直线增加,影响美观,降低可读性,而且不符合”开闭原则“。
使用策略模式改造以上的例子
接下来,我们使用策略模式来改造以上的例子。
改造代码
- 接口(抽象策略角色)
定义一个接口,接口提供两个方法,判断是否使用当前计算快递费用的方法和计算快递费用的方法
public interface FreightIface {
// 根据重量计算快递费用
public Integer calculateFright(Integer weight);
// 根据类型编码判断是否使用当前计算逻辑
public Boolean isCurrent(String typeCode);
}
- 实现类(具体策略角色)
实现了计算快递费用的接口,重写计算逻辑,根据不同的类型编写不同的逻辑
/**
* 圆通快递费用实现类
*/
@Service
public class ZTFreightService implements FreightIface{
private int ZTInitFreight = 8;
@Override
public Integer calculateFright(Integer weight) {
return weight - 1 > 0 ? (weight - 1) * 2 + ZTInitFreight : ZTInitFreight;
}
@Override
public Boolean isCurrent(String typeCode) {
return FreightType.ZT.equals(FreightType.getByCode(typeCode));
}
}
/**
* 中通快递费用实现类
*/
@Service
public class ZTFreightService implements FreightIface{
private int ZTInitFreight = 8;
@Override
public Integer calculateFright(Integer weight) {
return weight - 1 > 0 ? (weight - 1) * 2 + ZTInitFreight : ZTInitFreight;
}
@Override
public Boolean isCurrent(String typeCode) {
return FreightType.ZT.equals(FreightType.getByCode(typeCode));
}
}
- 业务类改造(环境角色)
@Service
public class FreightService {
@Autowired // 注入所有快递费用的实现类
private List<FreightIface> freightIfaceList;
public String getFreightByTypeV2(String typeCode, Integer weight) {
//根据类型得到对应的计算快递费用的实现类
FreightIface freightIface = freightIfaceList.stream()
.filter(f -> f.isCurrent(typeCode)).findFirst().orElse(null);
if (freightIface == null) {
return "运费类型不正确";
}
return freightIface.calculateFright(weight).toString();
}
}
测试
测试结果和改造前的一样
通过使用策略模式,可以很好的避免了if else 结构编码,如果新加了快递公司,只需要添加新的实现类,并重写接口方法即可,符合”开闭原则“。
总结
优点
- 具体策略角色直接可以相互替换;
- 添加新的策略只需要添加新的实现类即可,不需要修改原来的代码;
- 避免了过多的if else 分支结构。
缺点
- 容易造成类爆炸。
使用场景
- 需要在不同的场景下使用不同的算法(策略),或者该业务会迭代使用其他的方式实现。