前置知识
什么是策略模式?
策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在策略模式中,一个类的行为或算法可以在使用时更改。这种模式属于行为模式的一种,它定义了一系列算法,将每个算法封装到具有共同接口的独立类中,并使它们可以相互替换。
在策略模式中,主要包含三个角色:
- 抽象策略(Strategy):定义所有支持的算法的公共接口,通常是一个接口或抽象类。
- 具体策略(Concrete Strategy):实现了具体算法的类,实现了抽象策略接口定义的方法。
- 环境(Context):持有一个策略对象的引用,可以调用策略对象的方法来实现特定的算法。 (提供方法来切换策略)
策略模式的场景
- 支付方式选择:一个电商平台可以根据用户选择的支付方式(如信用卡、支付宝、微信等)来选择不同的支付策略,每种支付方式对应一个具体的支付策略。
- 打折策略:电商平台,不同用户在下单时可能会有不同的优惠方式。在这种情况下,可以使用策略模式来实现不同的优惠策略,比如,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的方式来切换策略都可以,看你喜欢哪一个。
好了,分享到结束了,如果觉得我写的还不错,记得给我三连哦,创作真的不容易,感谢大家的支持,谢谢!