策略模式¶
➊单纯策略模式,没有结合其他设计模式¶
感觉策略模式更适合在实际开发中,有这样一种业务:它是有好多的逻辑,但是有很多不同的进入情况,可以是业务需要也可以是逻辑需要, 这个模式对于代码,可以简化业务逻辑,不至于将代码写成一坨,长长的几十上百行,同时又具有很好的扩展能力,可以很方便的加入新逻辑 最典型的场景,就是if条件的逻辑判断简化。
但是它也有缺点
1️⃣如果 if-else的判断情况很多,那么对应的具体策略实现类也会很多 2️⃣没法俯视整个分派的业务逻辑,因为具体的逻辑被分拆
一次面试中,阿里面试官提到策略模式,他不这样认为,觉得都是一样需要看很多分支逻辑的,无论怎么改代码, 基础的业务逻辑就是无可更改的,天王老子来了该写的逻辑就是要写,该看的逻辑还是要看,策略模式更像是整理代码的一种方法, 就是不用也没法“俯视”所有逻辑。这也是一种全新的理解。
不大喜欢一些理论性质的解释,直接样例理解,自己更能懂,代码就是一种语言,它自己会表达清楚
🏀代码样例¶
首先要有一个接口,里面要包含用于判断每个业务的不同,每个业务具体的实现的方法,比如要计算快递的收费, 就要有一个判断每个快递的类型的方法,每一种快递到底是如何计费的。
/**
* 策略模式接口设计
*/
public interface LogisticsService {
/**
* 1️⃣需要用来判断具体走那个逻辑
*/
boolean isCurrentLogistics(Integer type);
/**
* 2️⃣计算逻辑
*/
BigDecimal calculateFee(TransferFeeRequest transferFeeRequest);
}
同时具体如何计费,就要有快递的运输距离,单价,最重要的快递类型,这些都是具体的业务,参数到底如何写是业务决定的, 也可以有快递的重量,体积,形状等,每家快递公司的收费标准不一样,还有一些政策更改,体量优惠减免,是否有保价, 都会决定具体的逻辑。
@Data
public class TransferFeeRequest {
/**
* 距离
*/
private BigDecimal distance;
/**
* 单价
*/
private BigDecimal unitPrice;
/**
* 快递类型
*/
private Integer type;
}
一般这种业务都是写成具体的服务,比如合作的快递公司有京东、顺丰、圆通、中通,那就是四个服务
/**
* JD快递服务
*/
@Service
public class JDTransfercompany implements LogisticsService {
private final BigDecimal pickFee = BigDecimal.TEN;
private final BigDecimal minDistance = BigDecimal.valueOf(80);
@Override
public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
minDistance : transferFeeRequest.getDistance();
// do business
return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
}
@Override
public boolean isCurrentLogistics(Integer type) {
return Objects.equals(type, 1);
}
}
/**
* 顺丰快递服务
*/
@Service
public class SFTransfercompany implements LogisticsService {
private final BigDecimal pickFee = BigDecimal.TEN;
private final BigDecimal minDistance = BigDecimal.valueOf(60);
@Override
public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
minDistance : transferFeeRequest.getDistance();
// do business
return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
}
@Override
public boolean isCurrentLogistics(Integer type) {
return Objects.equals(type, 2);
}
}
/**
* 中通快递服务
*/
@Service
public class ZTTransfercompany implements LogisticsService {
private final BigDecimal pickFee = BigDecimal.TEN;
private final BigDecimal minDistance = BigDecimal.valueOf(40);
@Override
public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
minDistance : transferFeeRequest.getDistance();
// do business
return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
}
@Override
public boolean isCurrentLogistics(Integer type) {
return Objects.equals(type, 3);
}
}
/**
* 圆通快递服务
*/
@Service
public class YTTransfercompany implements LogisticsService {
private final BigDecimal pickFee = BigDecimal.TEN;
private final BigDecimal minDistance = BigDecimal.valueOf(20);
@Override
public BigDecimal calculateFee(TransferFeeRequest transferFeeRequest) {
BigDecimal distance = minDistance.compareTo(transferFeeRequest.getDistance()) > 0 ?
minDistance : transferFeeRequest.getDistance();
// do business
return distance.multiply(transferFeeRequest.getUnitPrice()).add(pickFee);
}
@Override
public boolean isCurrentLogistics(Integer type) {
return Objects.equals(type, 4);
}
}
都是类似的,本来可能是if-else一撸到底,现在变成分开实现(例子只是例子,比较简)
那我们在使用的时候就简单一些了,用stream流就能调用
🐔使用方式¶
/**
* 策略模式需要把所有的可能都注入进来
*/
private final List<LogisticsService> logisticsServices;
public BigDecimal calculateFee(@RequestBody TransferFeeRequest transferFeeRequest) {
// 遍历,根据每个类中的具体实现类来判断到底走那个逻辑
LogisticsService logisticsService = logisticsServices.stream()
.filter(logistics -> logistics.isCurrentLogistics(transferFeeRequest.getType()))
.findFirst()
.orElse(null);
// 一定要有对应匹配为空对不上的兜底逻辑,也可以单独列出一个实现类来写
if (logisticsService == null) {
throw new RuntimeException("没有对应的快递计算方式");
}
return logisticsService.calculateFee(transferFeeRequest);
}
我现在也只讲设计模式如何用,使用场景等,但是对它的理解实在是见仁见智了。其他理论化的介绍就不写了。
➋策略模式结合工厂方法模式¶
这个模式相较于单纯策略模式,加入了工厂方法,将一些获取逻辑再度封装一下,这样使用的时候只要简单的获取, 没有单纯策略模式那样需要自己写获取对象并判断的逻辑
🏀代码样例¶
首先还是一样需要一个接口,里面写一些业务需要的方法
之后就是实现它,每一个实现类,对应一种不同的业务分支情况
最后也是最重要的一步,有一个工厂类方便封装判断逻辑(这里将理解写到代码注释里)
public class ActionServiceFactory {
/**
* 维护一个MAP,作为用来放策略的容器,可以随时添加新的
*/
private static final Map<String, ActionService> ACTION_SERVICE_MAP = new HashMap<>();
static {
ACTION_SERVICE_MAP.put("action1", new ActionService1());
ACTION_SERVICE_MAP.put("action2", new ActionService2());
// .....继续添加新策略
}
private ActionServiceFactory() {
}
/**
* 使用内部类单例模式,统一对象的获取
*/
private static class SingletonHolder {
private static final ActionServiceFactory INSTANCE = new ActionServiceFactory();
}
public static ActionServiceFactory getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* 这里就是将获取判断的逻辑封装进工厂里面,不需要使用的时候写,不然用一次写一次
*/
private static ActionService getActionService(String actionCode) {
ActionService actionService = ACTION_SERVICE_MAP.get(actionCode);
// 一定要有兜底逻辑
if (ObjectUtil.isNull(actionService)) {
throw new RuntimeException("非法 actionCode");
}
return actionService;
}
/**
* 统一获取,使得该方法作为工厂的生产物
*/
public void doAction(String actionCode) {
getActionService(actionCode).doAction();
}
}
🐔使用方式¶
这样的话,使用的时候就很简洁又简单了,一行代码就行,无论在那里使用都一样
@RestController
@RequestMapping("/test")
public class TestController {
@PostMapping("")
public void strategyFactory(String actionCode) {
// 这里的actionCode也要根据实际业务改动
ActionServiceFactory.getInstance().doAction(actionCode);
}
}
➌ Map+函数式接口的变种策略模式¶
这个就没有前面的策略模式那样,需要定义一个接口加实现类,直接将你的业务放到服务里面, 每一个策略就是一个方法,这样就可以避免业务策略多的时候,实现类很多的情况,同时又保留了 工厂模式的简单获取的优点
🏀代码样例¶
首先写一个服务,里面一个业务策略就是一个方法,每个方法写策略对应的逻辑
@Service
public class GrantTypeSerive {
public String redPaper(String resourceId) {
// 红包的发放方式
return "每周末9点发放";
}
public String shopping(String resourceId) {
// 购物券的发放方式
return "每周三9点发放";
}
public String QQVip(String resourceId) {
// qq会员的发放方式
return "每周一0点开始秒杀";
}
public String WXVip(String resourceId) {
// 微信会员的发放方式
return "微信会员";
}
}
然后再写一个服务用于封装判断逻辑,方便获取
/**
* 就是将对应的if-else分支判断依据作为key,要走的逻辑作为value,匹配对应结果达到目的
*/
@Service
@RequiredArgsConstructor
public class QueryGrantTypeService {
private final GrantTypeSerive grantTypeSerive;
private final Map<String, Function<String, String>> grantTypeMap = Maps.newHashMap();
/**
* 初始化业务分派逻辑,代替了if-else部分
* key: 优惠券类型
* value: lambda表达式,最终会获得该优惠券的发放方式
*/
@PostConstruct
public void dispatcherInit() {
grantTypeMap.put("红包", grantTypeSerive::redPaper);
grantTypeMap.put("购物券", grantTypeSerive::shopping);
grantTypeMap.put("qq会员", grantTypeSerive::QQVip);
grantTypeMap.put("微信会员", grantTypeSerive::WXVip);
}
public String getResult(String resourceType) {
// Controller根据 优惠券类型 `resourceType`、编码`resourceId` 去查询 发放方式`grantType`
Function<String, String> result = grantTypeMap.get(resourceType);
if (result != null) {
// 传入`resourceId` 执行这段表达式获得String型的grantType
// 这里的id还是需要根据具体的业务区别,有时候可能压根没有这种东西
return result.apply("resourceId");
}
// 兜底逻辑
return "查询不到该优惠券的发放方式";
}
}
🐔使用方式¶
这样只需要注入并获取就可以了(其实也可以在写一个单例,这样都不用注入了,但是这样就和spring的容器有一点冲突)
@RestController
@RequiredArgsConstructor
public class GrantTypeController {
private final QueryGrantTypeService queryGrantTypeService;
@PostMapping("/grantType")
public String test(String resourceName) {
return queryGrantTypeService.getResult(resourceName);
}
}
➍ 策略+责任链+组合模式(以合同签章为例)¶
这个是我在看微信公众号 “Java基基” 的时候了解到的 TODO 整理文章内容