干掉 if else 之策略模式

前置知识

什么是策略模式?

策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在策略模式中,一个类的行为或算法可以在使用时更改。这种模式属于行为模式的一种,它定义了一系列算法,将每个算法封装到具有共同接口的独立类中,并使它们可以相互替换。

在策略模式中,主要包含三个角色:

  1. 抽象策略(Strategy):定义所有支持的算法的公共接口,通常是一个接口或抽象类。
  2. 具体策略(Concrete Strategy):实现了具体算法的类,实现了抽象策略接口定义的方法。
  3. 环境(Context):持有一个策略对象的引用,可以调用策略对象的方法来实现特定的算法。 (提供方法来切换策略)

策略模式的场景

  1. 支付方式选择:一个电商平台可以根据用户选择的支付方式(如信用卡、支付宝、微信等)来选择不同的支付策略,每种支付方式对应一个具体的支付策略。
  2. 打折策略:电商平台,不同用户在下单时可能会有不同的优惠方式。在这种情况下,可以使用策略模式来实现不同的优惠策略,比如,Vip用户打九折,薅羊毛用户打骨折。。

策略模式的应用

说到策略模式,不禁想起之前的一件事情:

我们学校旁边有一个面包店,每天晚上九点半之后,面包就会打五折,有一天小浪晚上11点多才回来,发现面包店居然还没有关门,心想这不得吃一个打折的面包。嘿嘿!!出门结账的时候,发现不对劲,好像没有打五折,马上跑回去和老板理论,老板表示11点过后就不打折了。。。

好了,故事说完了,那我来举一个面包(商品)打折的场景,如果我们没有使用策略模式,我们可能会这么写

typescript 复制代码
public class Main {
    public static void main(String[] args) {
        discount("bone",1);
    }

    /**
     * 根据不同的策略,实现打折
     * @param type
     * @param amount
     * @return
     */
    public static void discount (String type, double amount) {
        if ("bone".equals(type)) {
            System.out.println("打骨折策略" + amount * 0.1);

        } else if ("default".equals(type)) {
            System.out.println("默认打策略" + amount * 0.99);
        }
    }
}

这样,我们完全将代码耦合在里面了,如果有新的打折,我们还需要在来一个 else if,如果使用策略模式就是干掉多余的 if else 了。

使用策略模式

1. 定义抽象策略接口

csharp 复制代码
/**
 * 抽象的策略接口
 */
public interface DiscountStrategy {
    double discount(double amount);
}

2. 具体策略

默认打折策略:

java 复制代码
/**
 * 默认打折策略
 */
@Component
public class DefaultDiscountStrategy implements DiscountStrategy {

    @Override
    public double discount(double amount) {
        System.out.println("默认打策略" + amount * 0.99);
        return amount * 0.99;
    }
}

打骨折策略:

java 复制代码
/**
 * 打骨折策略
 */
@Component
public class BoneDiscountStrategy implements DiscountStrategy{

    @Override
    public double discount(double amount) {
        System.out.println("打骨折策略" + amount * 0.1);
        return amount * 0.1;
    }
}

3. 切换策略

这里使用工厂模式来提供切换打折策略。

typescript 复制代码
@Component
public class DiscountStrategyFactory {
    public static DiscountStrategy chooseStrategy(String type) {
        DiscountStrategy discountStrategy = new DefaultDiscountStrategy();
        if ("bone".equals(type)) {
            discountStrategy = new BoneDiscountStrategy();
        } else if ("default".equals(type)) {
            discountStrategy = new DefaultDiscountStrategy();
        }
        // ....... 其他的策略

        return discountStrategy;
    }
}

4. 测试

typescript 复制代码
public class Main {
    public static void main(String[] args) {
        // 通过改变 type 的值来实现不同策略的切换
        DiscountStrategy discountStrategy = DiscountStrategyFactory.chooseStrategy("bone");
        discountStrategy.discount(1);
    }
}

输出结果:

优化代码

继续干掉 if else

虽然说我们可以更灵活的切换策略了,但是你有没有发现,我们在切换策略的时候,仍然逃不掉 if else if

我们可以使用 Map 来存储我们的策略:

arduino 复制代码
@Component
public class DiscountStrategyFactory {
    public static final Map<String,DiscountStrategy> map = new HashMap<>();
    static {
        map.put("bone",new BoneDiscountStrategy());
        map.put("default",new DefaultDiscountStrategy());
    }
    public static DiscountStrategy chooseStrategy(String type) {
        return map.get(type);
    }
}

这样是不是看起来更舒服了。。

但是 ......

这样的代码不满足开闭原则。

什么是开闭原则?

即在不修改原有代码的情况下可以通过扩展的方式来增加新的功能

假设我现在要加多一个折扣策略,那么我们就需要在 DiscountStrategyFactory 新添加代码:

arduino 复制代码
@Component
public class DiscountStrategyFactory {
    public static final Map<String,DiscountStrategy> map = new HashMap<>();
    static {
        map.put("bone",new BoneDiscountStrategy());
        map.put("default",new DefaultDiscountStrategy());
        // 新的策略
        map.put()
        // 更多....
        .........
    }
    public static DiscountStrategy chooseStrategy(String type) {
        return map.get(type);
    }
}

实现开闭原则

根本原因就是,如果新增新的策略,我们还是需要自己 new 对象 的方式,怎么办?

我们让 Spring 帮我们 new,也就是我们在 SpringBoot 启动的起来,就把这些 bean 都加载好了,此时,我们新增新的策略之后,直接启动程序即可。

1.1. 优化抽象策略接口

csharp 复制代码
public interface DiscountStrategy {
    /**
     * 打折方法
     */
    double discount(double amount);

    /**
     * 标记是什么类型
     * @return
     */
    String type();
    
}

1.2. 具体策略

typescript 复制代码
/**
 * 默认打折策略
 */
@Component
public class DefaultDiscountStrategy implements DiscountStrategy {

    @Override
    public double discount(double amount) {
        System.out.println("默认打策略" + amount * 0.99);
        return amount * 0.99;
    }

    @Override
    public String type() {
        return "default";
    }
}
typescript 复制代码
@Component
public class BoneDiscountStrategy implements DiscountStrategy{

    @Override
    public double discount(double amount) {
        System.out.println("打骨折策略" + amount * 0.1);
        return amount * 0.1;
    }

    @Override
    public String type() {
        return "bone";
    }
}

1.3. 切换策略

InitializingBean 是 Spring 框架提供的一个接口,用于在 Bean 实例化后执行初始化操作。实现 InitializingBean 接口的类需要实现 afterPropertiesSet() 方法,在这个方法内定义 Bean 初始化时需要执行的逻辑。

typescript 复制代码
@Component
public class DiscountStrategyFactory implements InitializingBean {
    @Resource
    private ApplicationContext applicationContext;

    public static final Map<String,DiscountStrategy> map = new HashMap<>();

    /**
    选择策略
    **/
    public static DiscountStrategy chooseStrategy(String type) {
        return map.get(type);
    }

    /**
     * 启动的会执行 afterPropertiesSet
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 获取 DiscountStrategy的bean对象
        // key bean的名字,value是bean对象
        Map<String, DiscountStrategy> strategyMap = applicationContext.getBeansOfType(DiscountStrategy.class);
        // 我们将 type作为 map key,对象作为 value
        strategyMap.forEach((key, value) ->map.put(value.type(),value));
    }

}

总结

今天我们讲了什么是策略模式,以及策略模式的应用场景,举个一个买面包打折的故事,来如何在代码中使用策略模式。无论是使用工厂模式来切换策略,还是通过 Spring 管理bean的方式来切换策略都可以,看你喜欢哪一个。

好了,分享到结束了,如果觉得我写的还不错,记得给我三连哦,创作真的不容易,感谢大家的支持,谢谢!

相关推荐
AskHarries2 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion3 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp3 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder3 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚4 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心5 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
思忖小下5 小时前
梳理你的思路(从OOP到架构设计)_简介设计模式
设计模式·架构·eit
.生产的驴6 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲6 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心6 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端