设计模式-策略模式

概述

策略模式组成 :策略使用者、策略接口、策略实现类。

我的理解是:对于某个场景,会有多种分支情况,不同的分支需要特定的逻辑去处理。该场景就是策略的使用者,该场景下要做的事可以抽象成策略接口,不同分支则是策略接口的不同实现。多个分支处理方式不同,但概念上属于同一场景,比如打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,除非是那种"连环套"或者那种缩进成"倒三角"的代码,因为这种方式可以快速实现功能,如果不考虑代码"美观、优雅、拓展"的话,我觉得只要能按质按量实现,并无大问题,反而使用设计模式后,如果后续负责维护的人员不遵守规约或者水平不够,读不懂其中的设计思路,反而越改越乱。(我就遇到过...)

相关推荐
lxyzcm12 小时前
深入理解C++23的Deducing this特性(上):基础概念与语法详解
开发语言·c++·spring boot·设计模式·c++23
越甲八千12 小时前
重温设计模式--单例模式
单例模式·设计模式
Vincent(朱志强)12 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式
诸葛悠闲14 小时前
设计模式——桥接模式
设计模式·桥接模式
捕鲸叉18 小时前
C++软件设计模式之外观(Facade)模式
c++·设计模式·外观模式
小小小妮子~18 小时前
框架专题:设计模式
设计模式·框架
先睡18 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
waicsdn_haha19 小时前
Postman最新详细安装及使用教程【附安装包】
测试工具·api·压力测试·postman·策略模式·get·delete
Damon_X1 天前
桥接模式(Bridge Pattern)
设计模式·桥接模式
越甲八千1 天前
重温设计模式--享元模式
设计模式·享元模式