设计模式-策略模式

本文详细介绍了策略模式在IT中的应用,包括策略接口、策略实现、策略容器的实现以及如何通过策略模式处理打折扣和数据脱敏场景,强调了其在代码复用和降低if-else复杂性方面的优势。
摘要由CSDN通过智能技术生成

概述

策略模式组成:策略使用者、策略接口、策略实现类。
我的理解是:对于某个场景,会有多种分支情况,不同的分支需要特定的逻辑去处理。该场景就是策略的使用者,该场景下要做的事可以抽象成策略接口,不同分支则是策略接口的不同实现。多个分支处理方式不同,但概念上属于同一场景,比如打8折还是打骨折,都属于打折场景;再比如加密脱敏场景,都是为了脱敏,可能会有身份证、地址、电话等不同分支的特定处理。这些分支可以归为一类称为一个算法族。
这种特定场景下的多分支判断,如果不做代码设计层面的处理,跟我们平时的if-else类似。所以策略模式也是一种降低if-else分支代码,提高代码可读性的方式。

场景示例代码

以脱敏为例,假设系统需要考虑数据脱敏。最直接的方式是在服务层进行处理,为了代码可以复用,将脱敏的逻辑封装在工具类中。好处是实现简单,易于理解,但后续扩展其他类型的脱敏策略时,需要直接修改工具类中的代码。
看一下策略模式的实现:

1. 定义策略模式基本框架

// 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实例

@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. 具体场景的策略实现

定义了上述的策略基础框架,我们可以在其基础上对具体场景进行抽象,比如脱敏。

// 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. 功能测试

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"));
}
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 接口的实现类,不修改原代码,符合开闭原则。

// 邮箱脱敏
@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"));
通用脱敏******
身份证已脱敏
邮箱已脱敏
地址已脱敏
手机号已脱敏

新的策略场景

因为已经编写了策略模式的基础框架,那么新的策略场景添加也十分简单。
打折策略:

// 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: 880.0
新注册用户: 0.9595.00
普通用户: 无折扣
100

总结

如果还有其他IF-ELSE的多分支判断,可以考虑使用该模式进行优化。我本人并不排斥if-else,除非是那种“连环套”或者那种缩进成“倒三角”的代码,因为这种方式可以快速实现功能,如果不考虑代码“美观、优雅、拓展”的话,我觉得只要能按质按量实现,并无大问题,反而使用设计模式后,如果后续负责维护的人员不遵守规约或者水平不够,读不懂其中的设计思路,反而越改越乱。(我就遇到过…)

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值