15分钟了解设计模式----策略模式(全网看这篇就够了!)

在软件开发中,经常会遇到需要根据不同的条件来实现不同行为的场景。这种场景下,策略模式(Strategy Pattern)就是一种非常有用的设计模式。

那什么是策略模式呢?咱们先看定义

定义:策略是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。

 其UML结构为:

Context(上下文)
        │
    Strategy(抽象策略)
        │
    ┌───┴──────────┐
ConcreteStrategyA  ConcreteStrategyB(具体策略)

组成部分

  1. Strategy(策略接口):定义所有策略的公共方法。
  2. ConcreteStrategy(具体策略):实现 Strategy,提供具体算法。
  3. Context(上下文):持有 Strategy,并在运行时调用其方法。
  4. Client(客户端):选择合适的 Strategy 并传递给 Context

应用场景 

策略模式适用于以下场景:

✔ 支付方式(如支付宝、微信、信用卡支付)。

✔ 排序算法(如冒泡排序、快速排序、归并排序)。

✔ 日志记录(如本地日志、远程日志、数据库日志)。

✔ 数据压缩(如 ZIP、RAR、GZIP)。

✔ 促销策略(如满减、折扣、积分兑换)。

以上是策略模式的理论知识,相信大家看到这里,也是云里雾里吧。Talk enough,show me the code!

接下来,我将以实践形式,以折扣为场景手动带大家进一步加深印象。

首先,我们定义一个抽象接口类 MemberStrategy

public interface MemberStrategy {
    // 一个计算价格的抽象方法
    //price商品的价格 n商品的个数
    double calcPrice(double price, int n);
}

然后,我们再创建其实现类(假设我们有普通会员中级会员高级会员,对应分别是不打折打九折以及打八折

// 普通会员——不打折
public class PrimaryMemberStrategy implements MemberStrategy {
    @Override
    public double calcPrice(double price, int n) {
        return price * n;
    }
}
// 中级会员 打百分之10的折扣
public class IntermediateMemberStrategy implements MemberStrategy{
    @Override
    public double calcPrice(double price, int n) {
        double money = (price * n) - price * n * 0.1;
        return money;
    }
}
// 高级会员类 20%折扣
public class AdvanceMemberStrategy implements MemberStrategy{
    @Override
    public double calcPrice(double price, int n) {
        double money = price * n - price * n * 0.2;
        return money;
    }
}

 这三种不同的策略类实现了同一个抽象策略类,每种策略对应一种实现,分别应对一个业务处理方式。

最后,我们还需要定义一个上下文类/环境类,它是负责和具体的策略类交互。

/**
 * 负责和具体的策略类交互
 * 这样的话,具体的算法和直接的客户端调用分离了,使得算法可以独立于客户端独立的变化。
 */

// 上下文类/环境类
public class MemberContext {
    // 用户折扣策略接口
    private MemberStrategy memberStrategy; // 关键:持有策略接口

    // 注入构造方法
    public MemberContext(MemberStrategy memberStrategy) {
        this.memberStrategy = memberStrategy;
    }

    // 计算价格
    public double qoutePrice(double goodsPrice, int n){
        // 此处可以添加统一逻辑(例如参数校验、日志等)
        // 通过接口变量调用对应的具体策略
        return memberStrategy.calcPrice(goodsPrice, n);
    }

}

 接下来,我们进行测试

public class Test{
    public static void main(String[] args) {

        // 具体行为策略
        MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy(); // 接口回调(向上转型)
        MemberStrategy intermediateMemberStrategy = new IntermediateMemberStrategy();
        MemberStrategy advanceMemberStrategy = new AdvanceMemberStrategy();

        // 用户选择不同策略
        MemberContext primaryContext = new MemberContext(primaryMemberStrategy);
        MemberContext intermediateContext = new MemberContext(intermediateMemberStrategy);
        MemberContext advanceContext = new MemberContext(advanceMemberStrategy);

        //计算一本300块钱的书
        System.out.println("普通会员的价格:"+ primaryContext.qoutePrice(300,1));// 普通会员:300
        System.out.println("中级会员的价格:"+ intermediateContext.qoutePrice(300,1));// 中级会员 270
        System.out.println("高级会员的价格:"+ advanceContext.qoutePrice(300,1));// 高级会员240

}

以上就是一个整体的演示了! 

但是看到这里,有人就会说,我都  MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy();,那我为什么不直接 primaryMemberStrategy.calcPrice(300, 1)。

为什么我还得再 new MemberContext,然后再把 primaryMemberStrategy 传进去调用,怎么感觉有点多此一举呢

我觉得这块有疑问是正常的!在这个简单的示例中,确实可以直接用 primaryMemberStrategy.calcPrice(300, 1),而不需要通过 MemberContext 类。但策略模式的核心价值在于更复杂的场景,下面我解释为什么需要 MemberContext 以及策略模式的真正意义。

1. 当前简单场景的局限性

咱们的例子只有一种策略(普通会员),且直接暴露了具体策略类。这种情况下,策略模式确实显得冗余。但实际业务中通常会有:

  • 多种策略动态切换(如普通会员/黄金会员/钻石会员)

  • 策略的复杂预处理/后处理(如日志记录、参数校验、结果格式化等)

  • 策略的集中管理(避免策略逻辑散落在代码各处)

2. MemberContext 的核心作用

public class MemberContext {
    private MemberStrategy memberStrategy; // 关键:持有策略接口

    public MemberContext(MemberStrategy memberStrategy) {
        this.memberStrategy = memberStrategy; // 依赖注入
    }

    public double qoutePrice(double goodsPrice, int n) {
        // 此处可以添加统一逻辑(例如参数校验、日志等)
        return memberStrategy.calcPrice(goodsPrice, n);
    }
}
优势体现:
  1. 解耦客户端与策略

    • 客户端不需要知道具体策略类(如 PrimaryMemberStrategy),只需通过接口与 MemberContext 交互。

    • 符合迪米特法则(最少知识原则)。

  2. 动态切换策略

// 根据用户类型动态切换策略
MemberStrategy strategy = user.isVIP() ? new PrimaryMemberStrategy() : new IntermediateMemberStrategy();
MemberContext context = new MemberContext(strategy);
context.qoutePrice(300, 1);

  3. 扩展统一逻辑
如果所有策略都需要添加折扣日志: 

public double qoutePrice(double goodsPrice, int n) {
    double result = memberStrategy.calcPrice(goodsPrice, n);
    System.out.println("折扣策略:" + memberStrategy.getClass().getSimpleName() + ",最终价格:" + result);
    return result;
}

符合开闭原则
新增策略时(如 PrimaryMemberStrategy),只需扩展新类,无需修改 MemberContext。 

3. 直接调用策略的问题

如果直接调用 primaryMemberStrategy.calcPrice(300, 1)

  1. 业务逻辑散落各处:如果需要在计算前后加逻辑(如验证价格不能为负),需修改所有调用点。

  2. 耦合度高:客户端代码依赖具体策略类,违反面向接口编程原则。

  3. 难以维护:当策略增多时,代码会充满大量的 if-else 或 switch-case

策略模式的本质是通过 Context 隔离策略的创建和使用。

============================================================================================================

【注】在实际开发项目中,我们更多的是"将 Strategy 放在配置类中配置,然后在代码中读取",也就是通过 外部化配置 来动态管理策略的实现,而不是在代码中硬编码 new 具体策略类。这样做的好处是 灵活性高、易于维护,尤其适合需要频繁切换策略或策略需要动态更新的场景。

1. 直接 new 策略的问题

原代码中直接在 main 方法里实例化策略:

MemberStrategy primaryMemberStrategy = new PrimaryMemberStrategy(); // 硬编码

缺点:

  • 策略类名写死在代码中,修改策略需要重新编译。

  • 无法在运行时动态切换策略(例如根据配置文件切换折扣策略)。

接下来,我将展示如何通过外部化配置方式实现~~

 1. 定义策略配置属性类

通过 @ConfigurationProperties 绑定配置文件中的策略配置:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "member.strategy") // 绑定配置前缀
public class MemberStrategyProperties {
    private String active; // 对应 member.strategy.active
    private Map<String, String> classes; // 可选:支持多策略映射

    // Getters and Setters (必须)
    public String getActive() {
        return active;
    }

    public void setActive(String active) {
        this.active = active;
    }

    public Map<String, String> getClasses() {
        return classes;
    }

    public void setClasses(Map<String, String> classes) {
        this.classes = classes;
    }
}

2. 在 application.yml 中配置策略 

# 测试专用配置
member:
  strategy:
    active: "primary"
    classes:
      primary: "com.example.test.策略模式.配置化.PrimaryMemberStrategy"
      vip: "com.example.test.策略模式.配置化.IntermediateMemberStrategy"
      platinum: "com.example.test.策略模式.配置化.AdvanceMemberStrategy"

3. 创建策略配置类

结合 @ConfigurationProperties 和策略工厂模式:

@Configuration
@Slf4j
public class MemberStrategyConfig {

    private final MemberStrategyProperties properties;

    // 自动注入配置属性
    @Autowired
    public MemberStrategyConfig(MemberStrategyProperties properties) {
        this.properties = properties;
    }

    @Bean
    public MemberStrategy memberStrategy() throws Exception {
        // 根据配置的Key获取具体策略类名
        String strategyClassName = properties.getClasses().get(properties.getActive());
        log.info("正在加载策略类: {}", strategyClassName); // 添加日志
        Class<?> clazz = Class.forName(strategyClassName);
        log.info("策略类加载成功: {}", clazz.getName()); // 添加日志
        if (!MemberStrategy.class.isAssignableFrom(clazz)) {
            throw new IllegalStateException(strategyClassName + " 未实现 MemberStrategy 接口");
        }
        return (MemberStrategy) clazz.getDeclaredConstructor().newInstance();
    }
}

4. 使用策略(通过自动注入) 

@Service
public class OrderService {
    private final MemberContext memberContext;

    @Autowired
    public OrderService(MemberStrategy memberStrategy) {
        this.memberContext = new MemberContext(memberStrategy);
    }

    public double calculatePrice(double price, int count) {
        return memberContext.qoutePrice(price, count);
    }
}

补充:添加配置元数据(可选)

在 resources/META-INF 下创建 additional-spring-configuration-metadata.json,提供配置提示:

{
  "properties": [
    {
      "name": "member.strategy.active",
      "type": "java.lang.String",
      "description": "当前激活的会员策略Key",
      "sourceType": "com.example.test.策略模式.配置化.config.MemberStrategyProperties"
    },
    {
      "name": "member.strategy.classes",
      "type": "java.util.Map<java.lang.String, java.lang.String>",
      "description": "策略类全限定名映射",
      "sourceType": "com.example.test.策略模式.配置化.config.MemberStrategyProperties"
    }
  ]
}

依赖要求

确保 pom.xml 包含:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

5. 准备测试类 

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test") // 激活test配置profile
class OrderServiceIntegrationTest {

    @Autowired
    private OrderService orderService;

    @Test
    void testPrimaryStrategy() {
        // 根据test配置,应使用PrimaryMemberStrategy(原价)
        double price = orderService.calculatePrice(100.0, 2);
        System.out.println("primary会员价格:" + price);
//        assertEquals(200.0, price);
    }

}

运行 

 

 @ActiveProfiles("test") 是 Spring 测试框架中的一个注解,用于在测试时显式激活指定的 Spring 配置环境(Profile)。它的核心作用是告诉 Spring 在运行测试时,应该加载哪些特定的配置文件或配置段。

核心作用

  • 隔离测试环境:让测试使用独立的配置(如测试数据库、Mock服务等),避免污染生产配置。

  • 灵活切换配置:针对不同测试场景加载不同的配置(如 dev/test/prod)。

工作原理

  1. 启动测试:Spring 会优先加载 application-test.yml 或 application.yml 中标记为 test 的配置段。

spring:
  profiles: test  # 标记为test环境
  1. Bean覆盖:测试Profile中的配置会覆盖主配置中的同名属性。

  2. 依赖注入:所有 @Autowired 的Bean都会基于激活的Profile创建。

核心规则

Spring Boot 通过 文件名约定 自动关联 Profile,无需显式标记。具体规则:

  • 文件命名格式为 application-{profile}.yml(或 .properties

  • 当使用 @ActiveProfiles("test") 时,会自动加载:

    • application-test.yml高优先级

    • application.yml 中的通用配置(低优先级

 相信你读完这里,对策略模式有了更深的印象吧!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值