概述
策略模式组成 :策略使用者、策略接口、策略实现类。
我的理解是:对于某个场景,会有多种分支情况,不同的分支需要特定的逻辑去处理。该场景就是策略的使用者,该场景下要做的事可以抽象成策略接口,不同分支则是策略接口的不同实现。多个分支处理方式不同,但概念上属于同一场景,比如打8折还是打骨折,都属于打折场景;再比如加密脱敏场景,都是为了脱敏,可能会有身份证、地址、电话等不同分支的特定处理。这些分支可以归为一类称为一个算法族。
这种特定场景下的多分支判断,如果不做代码设计层面的处理,跟我们平时的
if-else
类似。所以策略模式也是一种降低if-else分支代码,提高代码可读性的方式。
场景示例代码
以脱敏为例,假设系统需要考虑数据脱敏。最直接的方式是在服务层进行处理,为了代码可以复用,将脱敏的逻辑封装在工具类中。好处是实现简单,易于理解,但后续扩展其他类型的脱敏策略时,需要直接修改工具类中的代码。
看一下策略模式的实现:
1. 定义策略模式基本框架
java
// 1.策略接口
public interface IStrategy<C> {
// 获取策略条件
C getCondition();
}
// 2.策略容器接口
public interface IStrategyContainer<C, S extends IStrategy<C>> {
// 容器的作用就是获取策略
S getStrategy(C condition, Class<S> strategyClass);
}
3.策略容器的基本实现:
这里以spring框架为例,在该类中获取springContext,目的是为了获取指定接口类型的所有bean实例
java
@Component
public class BaseStrategyContainer<C,S extends IStrategy<C>>
implements IStrategyContainer<C, S>, ApplicationContextAware {
//抽象容器集合
private final Map<C, S> strategyContainer = new HashMap<>(16);
// 防止并发问题:重复初始化策略
private final Object lock = new Object();
private ApplicationContext applicationContext;
@Override
public final S getStrategy(C condition, Class<S> strategyClass) {
synchronized (lock) {
if (!strategyContainer.containsKey(condition)) {
initStrategy(strategyClass);
}
}
return strategyContainer.get(condition);
}
/**
* 获取容器中的策略
* @return
*/
public Map<C, S> getStrategyContainer() {
return strategyContainer;
}
/**
* 加载算法族
* @param strategyClass 策略类
* @return
*/
private void initStrategy(Class<S> strategyClass) {
// 获取接口所有算法族的实例
Map<String, S> beansOfType = applicationContext.getBeansOfType(strategyClass);
if (!beansOfType.isEmpty()) {
beansOfType.forEach((k, v) -> getStrategyContainer().put(v.getCondition(), v));
} else {
throw new RuntimeException("未找到class对应的策略类:" + strategyClass.getName());
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public ApplicationContext getApplicationContext() {
return applicationContext;
}
}
2. 具体场景的策略实现
定义了上述的策略基础框架,我们可以在其基础上对具体场景进行抽象,比如脱敏。
java
// 1.预定义脱敏类型
public enum MaskConditionEnum {
MASK_PHONE, MASK_ID, MASK_EMAIL, MASK_ADDRESS, MASK_BANK_CARD, MASK_ALL;
}
// 2.定义脱敏策略接口
public interface IMaskStrategy extends IStrategy<MaskConditionEnum> {
// 只做一件事:脱敏
String mask(String value);
}
// 3.通过脱敏容器作为脱敏的入口
@Component
public class MaskStrategyContainer extends BaseStrategyContainer<MaskConditionEnum, IMaskStrategy> {
public String doMask(MaskConditionEnum condition, String value) {
IMaskStrategy maskStrategy = getStrategy(condition, IMaskStrategy.class);
return maskStrategy.mask(value);
}
}
// 4.定义具体的脱敏算法
@Service
public class MaskStrategy4Phone implements IMaskStrategy{
@Override
public MaskConditionEnum getCondition() {
return MaskConditionEnum.MASK_PHONE;
}
@Override
public String mask(String value) {
// do biz logic,这里简化脱敏逻辑
return "手机号已脱敏";
}
}
3. 功能测试
java
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
// 脱敏策略
MaskStrategyContainer mask = ctx.getBean(MaskStrategyContainer.class);
System.out.println(mask.doMask(MaskConditionEnum.MASK_PHONE, "1234567890"));
}
shell
2024-04-08 16:14:08.963 INFO 32844 --- [ main] org.wyy.App : Started App in 1.265 seconds (JVM running for 2.721)
手机号已脱敏
4. 适应新需求
新的脱敏分支
只新增
IMaskStrategy
接口的实现类,不修改原代码,符合开闭原则。
java
// 邮箱脱敏
@Service
public class MaskStrategy4Email implements IMaskStrategy{
@Override
public MaskConditionEnum getCondition() {
return MaskConditionEnum.MASK_EMAIL;
}
@Override
public String mask(String value) {
return "邮箱已脱敏";
}
}
// 地址脱敏
@Service
public class MaskStrategy4Address implements IMaskStrategy{
@Override
public MaskConditionEnum getCondition() {
return MaskConditionEnum.MASK_ADDRESS;
}
@Override
public String mask(String value) {
return "地址已脱敏";
}
}
// 其他脱敏,如身份证脱敏,银行卡脱敏等
// 测试
ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
MaskStrategyContainer mask = ctx.getBean(MaskStrategyContainer.class);
System.out.println(mask.doMask(MaskConditionEnum.MASK_ALL, "1234567890"));
System.out.println(mask.doMask(MaskConditionEnum.MASK_ID, "1234567890"));
System.out.println(mask.doMask(MaskConditionEnum.MASK_EMAIL, "1234567890"));
System.out.println(mask.doMask(MaskConditionEnum.MASK_ADDRESS, "1234567890"));
System.out.println(mask.doMask(MaskConditionEnum.MASK_PHONE, "1234567890"));
通用脱敏******
身份证已脱敏
邮箱已脱敏
地址已脱敏
手机号已脱敏
新的策略场景
因为已经编写了策略模式的基础框架,那么新的策略场景添加也十分简单。
打折策略:
java
// 1.打折策略接口
public interface IDiscountStrategy extends IStrategy<DiscountConditionEnum> {
BigDecimal discount(BigDecimal price);
}
// 2.打折策略容器类
@Component
public class DiscountStrategyContainer extends BaseStrategyContainer<DiscountConditionEnum, IDiscountStrategy> {
public BigDecimal doDiscount(DiscountConditionEnum condition, BigDecimal price) {
IDiscountStrategy discountStrategy = getStrategy(condition, IDiscountStrategy.class);
return discountStrategy.discount(price);
}
}
// 3.编写具体的打折实现类,形成打折算法族。如VIP打折,新注册打折,无打折等
@Service
public class DiscountStrategy4VIP implements IDiscountStrategy {
@Override
public BigDecimal discount(BigDecimal price) {
System.out.println("VIP: 8折");
return price.multiply(BigDecimal.valueOf(0.8));
}
@Override
public DiscountConditionEnum getCondition() {
return DiscountConditionEnum.VIP;
}
}
// 4.测试
DiscountStrategyContainer discount = ctx.getBean(DiscountStrategyContainer.class);
System.out.println(discount.doDiscount(DiscountConditionEnum.VIP, new BigDecimal("100")));
System.out.println(discount.doDiscount(DiscountConditionEnum.NEW_REGISTER, new BigDecimal("100")));
System.out.println(discount.doDiscount(DiscountConditionEnum.NONE, new BigDecimal("100")));
// 输出
VIP: 8折
80.0
新注册用户: 0.95折
95.00
普通用户: 无折扣
100
总结
如果还有其他IF-ELSE的多分支判断,可以考虑使用该模式进行优化。我本人并不排斥if-else,除非是那种"连环套"或者那种缩进成"倒三角"的代码,因为这种方式可以快速实现功能,如果不考虑代码"美观、优雅、拓展"的话,我觉得只要能按质按量实现,并无大问题,反而使用设计模式后,如果后续负责维护的人员不遵守规约或者水平不够,读不懂其中的设计思路,反而越改越乱。(我就遇到过...)