干掉 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的方式来切换策略都可以,看你喜欢哪一个。

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

相关推荐
爱勇宝31 分钟前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries1 小时前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术2 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎3 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode3 小时前
Redis 在生产项目的使用
前端·后端
用户559822481223 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode3 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战3 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha4 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn4 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端