介绍

策略模式是一种行为型设计模式。

在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

策略模式平常我们多用来消除if-else switch等多重判断的代码,可以有效地应对代码复杂性。

下述代码对应的业务,根据对应的优惠类型,对价格作出相应的优惠。

package cn.meowrain;

import java.util.Objects;

public class DiscountTest {
    public static void main(String[] args) {
        Double result = DiscountTest.discount("1", 100.00);
        System.out.println(result);
    }

    public static Double discount(String type, Double price) {
        if (Objects.equals(type, "1")) {
            return price * 0.8;
        } else if (Objects.equals(type, "2")) {
            return price * 0.6;
        } else if (Objects.equals(type, "3")) {
            return price * 0.5;
        } else {
            return price;
        }
    }
}

但是我们很快就能发现问题了,这还是个案例,if else代码块就这么多了,真实的业务会多少if else可想而知了

我们可以应用策略模式解决这个问题:
1.将不同的优惠类型定义为不同的策略算法实现类。
2. 保证开闭原则,增加程序的健壮性以及可扩展性。

package cn.meowrain;

import java.util.HashMap;
import java.util.Map;

interface DiscountStrategy {
    Double discount(Double price);
}

/**
 * Implements 80% discount logic.
 */
class Discount80Strategy implements DiscountStrategy {
    static {
        DiscountStrategyFactory.registry("1", new Discount80Strategy());
    }

    @Override
    public Double discount(Double price) {
        return price * 0.8; // Bug 1: Incorrect operator `_0.8`
    }
}

/**
 * Implements 60% discount logic.
 */
class Discount60Strategy implements DiscountStrategy {
    static {
        DiscountStrategyFactory.registry("2", new Discount60Strategy());
    }

    @Override
    public Double discount(Double price) {
        return price * 0.6; // Bug 2: Incorrect operator `_ 0.6`
    }
}

/**
 * Implements 50% discount logic.
 */
class Discount50Strategy implements DiscountStrategy {
    static {
        DiscountStrategyFactory.registry("3", new Discount50Strategy());
    }

    @Override
    public Double discount(Double price) {
        return price * 0.5;
    }
}

class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> strategyMap = new HashMap<>();

    public static void registry(String type, DiscountStrategy strategy) {
        strategyMap.put(type, strategy);
    }

    public static DiscountStrategy getStrategy(String type) {
        return strategyMap.get(type);
    }
}

public class DiscountTest2 {
    public static void main(String[] args) {
        new Discount80Strategy();
        new Discount60Strategy();
        new Discount50Strategy();
        Double result1 = DiscountStrategyFactory.getStrategy("1").discount(100.00);
        System.out.println("80% Discount: " + result1);

        Double result2 = DiscountStrategyFactory.getStrategy("2").discount(100.00);
        System.out.println("60% Discount: " + result2);
    }
}

使用spring

package org.example.discount;

import org.springframework.stereotype.Component;

@Component
public class Discount90Strategy implements DiscountStrategy {
    @Override
    public Double discount(Double price) {
        return price * 0.9;
    }

    @Override
    public String mark() {
        return "1";
    }
}

package org.example.discount;

import org.springframework.stereotype.Component;

@Component
public class Discount80Strategy implements DiscountStrategy {
    @Override
    public Double discount( Double price) {
        return price * 0.8;
    }

    @Override
    public String mark() {
        return "2";
    }
}

package org.example.discount;

import org.springframework.stereotype.Component;

@Component
public class Discount50Strategy implements DiscountStrategy {
    @Override
    public Double discount(Double price) {
        return price * 0.5;
    }

    @Override
    public String mark() {
        return "3";
    }
}

package org.example.discount;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class DiscountFactory implements InitializingBean {
    @Autowired
    private ApplicationContext context;


    private final Map<String, DiscountStrategy> discountStrategies = new HashMap<>();

    public DiscountStrategy chooseStrategy(String type) {
        return discountStrategies.get(type);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, DiscountStrategy> beans = context.getBeansOfType(DiscountStrategy.class);
        beans.forEach((k, v) -> discountStrategies.put(v.mark(), v));
    }
}

package org.example.discount;

public interface DiscountStrategy {
    Double discount(Double price);

    String mark();
}
package org.example;

import org.example.config.AppConfig;
import org.example.discount.DiscountFactory;
import org.example.discount.DiscountStrategy;
import lombok.extern.slf4j.Slf4j;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class) // Add this annotation
@SpringBootTest(classes = AppConfig.class)
@Slf4j
public class DiscountTest {
    @Autowired
    private DiscountFactory discountFactory;

    @Test
    public void test() {
        DiscountStrategy strategy = discountFactory.chooseStrategy("2");
        Double result = strategy.discount(1000.0);
        log.info(String.valueOf(result));
    }
}

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>org.example</groupId>
    <artifactId>learn_juc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

优点

提高灵活性和可维护性:通过将算法的实现与使用分离开来,当需要修改或添加新算法时,只需定义新的策略类并将其传递给环境类即可,无需修改环境类代码。

提高代码复用性:算法被封装在独立的策略类中,使得这些算法可以被多个不同的客户(环境类)复用。

动态切换算法:允许在程序运行时根据需要动态地改变和选择算法,从而实现不同的功能和行为,使程序更灵活。

算法实现与使用分离使代码更清晰:客户端代码仅需关注如何选择和使用不同的算法,而不必关心算法的具体实现细节,使代码更简洁、易于理解和扩展。

避免大量条件语句:当需要根据不同条件选择不同算法时,策略模式可以避免使用复杂的 if-else 或 switch 语句,使代码结构更清晰,更易于维护。