策略设计模式

3. 策略设计模式

3.1 原理和实现

3.1.1 简介

策略模式(Strategy Design Pattern), 定义的一族算法类,将每个算法分别封装起来,让它们可以互相替 换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式主要包含以下角色:

  • 策略接口(Strategy):定义所有支持的算法的公共接口。客户端使用这个接口与具体策略进行交互。
  • 具体策略(Concrete Strategy):实现策略接口的具体策略类,这些类封装了实际的算法逻辑。
  • 上下文(Context):持有一个策略对象,用于与客户端进行交互。上下文可以定义一些接口,让客户端不直接与策略接口交互,从而实现策略的封装。

简单案例说明:

假设我们要实现一个计算器,支持加法、 减法和乘法运算。我们可以使用策略模式将各种运算独立为不同的策略,并让客户端根据需要选择和使用不同的策略。

首先,我们定义一个策略接口 Operation :

package cn.itcast.designPatterns.strategy.demo1;

/**
 * 类描述:抽象操作接口
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:48
 */
public interface Operation {
    double execute(double num1, double num2);

}

然后,创建具体策略类来实现加法、减法和乘法运算:

package cn.itcast.designPatterns.strategy.demo1;

/**
 * 类描述:加操作
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:49
 */
public class Addition implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 + num2;
    }
}

/**
 * 类描述:减操作
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:50
 */
public class Subtraction implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 - num2;
    }
}

/**
 * 类描述:乘法操作
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:51
 */
public class Multiplication implements Operation {
    @Override
    public double execute(double num1, double num2) {
        return num1 * num2;
    }
}

创建一个上下文类 Calculator ,让客户端可以使用这个类来执行不同的运算:

package cn.itcast.designPatterns.strategy.demo1;

/**
 * 类描述:暴露给客户端使用的上下文类
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:52
 */
public class Calculator {
    private Operation operation;

    public Calculator() {
    }

    public Calculator(Operation operation) {
        this.operation = operation;
    }

    public void setOperation(Operation operation) {
        this.operation = operation;
    }

    public double executeOperatiion(double num1, double num2) {
        return operation.execute(num1, num2);
    }
}

客户端测试案例:

/**
 * 类描述:策略模式测试案例
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/3 19:54
 */
@Slf4j
public class StrategyPatternTest {

    @Test
    public void testCalculator() {
        double num1 = 6, num2 = 4;
        Calculator calculator = new Calculator();
        calculator.setOperation(new Addition());
        double result = calculator.executeOperatiion(num1, num2);
        log.info(">>>Addition:{}", result);

        calculator.setOperation(new Subtraction());
        result = calculator.executeOperatiion(num1, num2);
        log.info(">>>Subtraction:{}", result);

        calculator.setOperation(new Multiplication());
        result = calculator.executeOperatiion(num1, num2);
        log.info(">>>Multiplication:{}", result);

    }
}

在这个例子中,使用策略模式将加法、减法和乘法运算独立为不同的策略。客户端可以根据需要选择和使用不同的策略。 Calculator 上下文类持有一个Operation 策略对象,并通过 setOperation 方法允许客户端设置所需的策略。

策略模式的优点:

  • 提高代码的可维护性和可扩展性。当需要添加新的算法时,无需修改客户端代码,只需实现一个新的具体策略类。
  • 符合开闭原则。策略模式允许我们在不修改现有代码的情况下引入新的策略。
  • 避免使用多重条件判断。使用策略模式可以消除一些复杂的条件判断语句,使代码更加清晰和易于理解。

策略模式的缺点:

  • 客户端需要了解所有的策略。为了选择合适的策略,客户端需要了解不同策略之间的区别。
  • 增加了类的数量。策略模式会导致程序中具体策略类的数量增加,这可能会导致代码的复杂性增加。

3.1.2 策略的定义

策略类的定义比较简单,包含一个策略接口和一组实现这个接口的具体策略类。因为所有的策略类都实现相同的接口,所以客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。示例代码如下所示:

// 策略接口
public interface Strategy {
    void algorithmInterface();
}

// 具体策略A
public class ConcreteStrategyA implements Strategy {
    @Override

    public void  algorithmInterface() {
        //具体的算法...

   }
}
// 具体策略B
public class ConcreteStrategyB implements Strategy {
    @Override
    public void  algorithmInterface() {
        //具体的算法...

   }
}

3.1.3 策略的创建

因为策略模式会包含一组策略,在使用时会通过类型(type) 断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。

如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 getStrategy() 的时候,都创建一 个新的策略对象。可以把根据 type 创建策略的逻辑抽离出来,放到工 厂类中。示例代码如下所示:

// 创建策略的工厂
public class StrategyFactory {
    // 缓存策略
    private static final Map<String, Strategy> strategies = new HashMap<>();
    static {
        strategies.put("A", new ConcreteStrategyA());
        strategies.put("B", new ConcreteStrategyB());
   }
   
    // 从缓存中获取策略
   public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
       }
        return strategies.get(type);
   }
}

如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中, 获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那我们就需要按照 如下方式来实现策略工厂类。

public class StrategyFactory {
    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
       }
        if (type.equals("A")) {
            return new ConcreteStrategyA();
       } else if (type.equals("B")) {
            return new ConcreteStrategyB();
       }
        return null;
   }
}

3.1.4 策略的使用

策略模式通常包含一组可选策略,客户端代码如何确定使用哪个策略呢? 最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。

这里的“运行时动态”指的是,我们事先并不知道会使用哪个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。

// 策略接口:EvictionStrategy
// 策略类:LruEvictionStrategy、FifoEvictionStrategy、LfuEvictionStrategy...
// 策略工厂:EvictionStrategyFactory
// ... 以上省略
public class UserCache {
    private Map<String, User> cacheData = new HashMap<>();
    private EvictionStrategy eviction;
    public UserCache(EvictionStrategy eviction) {
        this.eviction = eviction;
   }
    //...

}

// 运行时动态确定,根据配置文件的配置决定使用哪种策略
public class Application {
    public static void main(String[] args) throws Exception {
        EvictionStrategy evictionStrategy = null;
        Properties props = new Properties();
        props.load(new FileInputStream("./config.properties"));
        String type = props.getProperty("eviction_type");
        evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
        UserCache userCache = new UserCache(evictionStrategy);
        //...
   }
}

// 非运行时动态确定,在代码中指定使用哪种策略
public class Application {
    public static void main(String[] args) {
        //...
        EvictionStrategy evictionStrategy = new LruEvictionStrategy();
        UserCache userCache = new UserCache(evictionStrategy);
        //...
   }
}

非运行时动态确定,也就是第二个 Application 中的使用方式,并不能发挥策略模式的优势。在这种应用场景下,策略模式实际上退化成了“面向对象的多态特性”或“基于接口而非实现编程原则”,这其实就是开篇时列举的例子场景。

3.2 优化if分支

下边这段代码是一个解析不同来源报文的方法,用了1000 多行。针对这样的代码,我们如何优化呢?使用工厂模式和策略设计模式优化代码。
策略设计模式_第1张图片

3.2.1 基础优化

以解析报文案例为基础,展示如何使用策略模式优化具有 if 条件分支的代码。 首先这是一个包含大量 if 分支的报文解析系统:

package cn.itcast.designPatterns.strategy.demo2;

import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:报文解析
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:43
 */
@Slf4j
public class MessageParser {
    public void parseMessage(Message message) {
        String messageType = message.getType();
        if ("XML".equalsIgnoreCase(messageType)) {
            // 解析XML报文
            log.info("解析XML报文:{}", message.getContent());
        } else if ("JSON".equalsIgnoreCase(messageType)) {
            // 解析 JSON 报文
            log.info("解析 JSON 报文: {}", message.getContent());
        } else if ("CSV".equalsIgnoreCase(messageType)) {
            // 解析CSV报文
            log.info("解析CSV报文:{}", message.getContent());
        } else {
            throw new IllegalArgumentException("未知的报文类型:" + messageType);
        }
    }
}

使用策略模式优化上述代码。 首先定义一个报文解析策略接口 MessageParserStrategy :

package cn.itcast.designPatterns.strategy.demo2;

/**
 * 接口描述:报文解析策略接口
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:51
 */
public interface MessageParserStrategy {

    /**
     * 解析报文的方法
     *
     * @param message
     */
    void parse(Message message);
}

实现 XML、JSON 和 CSV 报文解析策略:

package cn.itcast.designPatterns.strategy.demo2;

import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:XML报文解析策略的具体实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:52
 */
@Slf4j
public class XmlMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        log.info("解析XML报文:{}", message.getContent());
    }
}

/**
 * 类描述:JSON报文解析策略的具体实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:52
 */
@Slf4j
public class JsonMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        log.info("解析 JSON 报文: {}", message.getContent());
    }
}

/**
 * 类描述:CSV报文解析策略的具体实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:54
 */
@Slf4j
public class CsvMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        log.info("解析CSV报文:{}", message.getContent());
    }
}

创建一个 MessageParserService类,该类将根据传入的策略解析报文:

package cn.itcast.designPatterns.strategy.demo2;

/**
 * 类描述:暴露的报文解析服务
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 20:54
 */
public class MessageParserService {
    private MessageParserStrategy strategy;

    /**
     * 设置报文解析策略
     *
     * @param strategy
     */
    public void setStrategy(MessageParserStrategy strategy) {
        this.strategy = strategy;
    }

    /**
     * 根据选择的策略解析报文
     *
     * @param message
     */
    public void parseMessage(Message message) {
        this.strategy.parse(message);
    }
}

测试案例:

@Slf4j
public class StrategyPatternTest {
    @Test
    public void testMessageParserStrategy() {
        MessageParserService parserContext = new MessageParserService();
        // 使用 XML 报文解析策略
        parserContext.setStrategy(new XmlMessageParserStrategy());
        parserContext.parseMessage(Message.builder().type("XML").content("这是一个 XML 报文").build());

        // 使用 JSON 报文解析策略
        parserContext.setStrategy(new JsonMessageParserStrategy());
        parserContext.parseMessage(Message.builder().type("JSON").content("{\"message\": \"这是一个 JSON 报文\"}").build());

        // 使用 CSV 报文解析策略
        parserContext.setStrategy(new CsvMessageParserStrategy());
        parserContext.parseMessage(Message.builder().type("CSV").content("这是一个,CSV,报文").build());
    }
}

3.2.2 结合工厂模式优化

我们可以将策略模式与工厂模式结合,以便根据不同的消息类型自动匹配不同的解析策略。下面是如何实现这个优化的:

创建一个 MessageParserStrategyFactory 类,用于根据报文类型创建相应的解析策略:

package cn.itcast.designPatterns.strategy.demo2;

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

/**
 * 类描述:报文解析策略工厂
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 21:02
 */
public class MessageParserStrategyFactory {
    private static final Map<String, MessageParserStrategy> STRATEGIES = new HashMap<>();

    static {
        STRATEGIES.put("XML", new XmlMessageParserStrategy());
        STRATEGIES.put("JSON", new JsonMessageParserStrategy());
        STRATEGIES.put("CSV", new CsvMessageParserStrategy());
    }

    /**
     * 根据报文类型获取报文解析策略
     *
     * @param messageType
     * @return
     */
    public static MessageParserStrategy getStrategy(String messageType) {
        MessageParserStrategy strategy = STRATEGIES.get(messageType.toUpperCase());
        if (strategy == null) {
            throw new IllegalArgumentException("未知的报文类型:" + messageType);
        }
        return strategy;
    }
}

创建新的服务MessageParserServiceV2,提供解析报文的入口:

package cn.itcast.designPatterns.strategy.demo2;

/**
 * 类描述:暴露的报文解析服务2
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/4 21:08
 */
public class MessageParserServiceV2 {
    /**
     * 根据选择的策略解析报文
     *
     * @param message
     */
    public void parseMessage(Message message) {
        MessageParserStrategyFactory.getStrategy(message.getType()).parse(message);
    }
}

测试案例2:

@Slf4j
public class StrategyPatternTest {
    @Test
    public void testMessageParserStrategyV2() {
        MessageParserServiceV2 parserContext = new MessageParserServiceV2();
        // 使用 XML 报文解析策略
        parserContext.parseMessage(Message.builder().type("XML").content("这是一个 XML 报文").build());

        // 使用 JSON 报文解析策略
        parserContext.parseMessage(Message.builder().type("JSON").content("{\"message\": \"这是一个 JSON 报文\"}").build());

        // 使用 CSV 报文解析策略
        parserContext.parseMessage(Message.builder().type("CSV").content("这是一个,CSV,报文").build());
    }
}

3.3 源码使用

3.3.1 ssm框架

  • Spring中的Resource接口用于抽象不同类型的资源, 例如文件系统资源、类路径资源、URL资源等。Resource接口就像策略模式中的策略接口,而不同类型的资源类(如 ClassPathResource 、 FileSystemResource 等)就像具体策略,客户端可以根据需要选择和使用不同的资源类。
  • Spring AOP中,代理类的创建使用了策略模式。ProxyFactory 中的 AopProxy 接 口定义了创建代理对象的策略接口,而 JdkDynamicAopProxy 和 CglibAopProxy 这两个类分别为基于JDK动态代理和CGLIB动态代理的具体策略实现,客户端可以根据需要选择使用哪种代理方式。
  • MyBatis中, Executor 接口定义了执行SQL语句的策略接口。MyBatis提供了不同的 Executor 实现,例如 SimpleExecutor 、 ReuseExecutor 和 BatchExecutor 等,它们分别表示不同的执行策略。客户端可以通过配置选择使用哪种执行策略。
  • Spring MVC框架中, HandlerMapping 接口定义了映射请求到处理器的策略接口。Spring MVC提供了多种 HandlerMapping 实现,例如 BeanNameUrlHandlerMapping 、 RequestMappingHandlerMapping 等,分别表示不同的映射策略。客户端可 以通过配置选择使用哪种映射策略。

策略模式通过将算法和客户端分离, 使得系统更加灵活和可扩展。在实际开发中,我们可以参考这些例子,根据业务需求和系统架构灵活地运用策略模式。

3.3.2 Jdk源码

以 java.util.Comparator 接口为例,展示策略模式在JDK中的应用。 假设有一个 Student 学生类,我们需要对一个 Student 对象的列表进行排序(可能需要按照学生的姓名、年龄或成绩进行排序)。我们可以使用策略模式,通过实现 Comparator 接口,为不同的排序需求提供不同的比较策略。

首先定义Student类

public class Student {
    private String name;
    private int age;
    private double score;
    // 构造方法、getter和setter方法省略

}

实现 Comparator 接口,定义不同的比较策略:

public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.getName().compareTo(s2.getName());
   }
}

// 根据学生的年龄进行排序
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Integer.compare(s1.getAge(), s2.getAge());
   }
}

// 根据学生的成绩进行排序
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return Double.compare(s1.getScore(), s2.getScore());
   }
}

在客户端代码中,根据需要选择和使用不同的比较策略:

public class Client {
    public static void main(String[] args) {
        // 创建一个Student对象的列表
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 20, 90.0));
        students.add(new Student("Bob", 18, 85.0));
        students.add(new Student("Charlie", 22, 88.0));
        // 使用姓名比较策略进行排序
        Collections.sort(students, new NameComparator());
        System.out.println("按姓名排序: " + students);
        // 使用年龄比较策略进行排序
        Collections.sort(students, new AgeComparator());
        System.out.println("按年龄排序: " + students);
        // 使用成绩比较策略进行排序
        Collections.sort(students, new ScoreComparator());
        System.out.println("按成绩排序: " + students);
   }
}

这个例子中使用策略模式将不同的排序策略独立为不同的类。客户端可以根据需要选择和使用不同的排序策略,而无需修改代码。

3.4 使用场景

3.4.1 支付系统

使用策略模式的支付系统的简化示例。

首先定义一个支付策略接口 PaymentStrategy

package cn.itcast.designPatterns.strategy.demo3;

/**
 * 接口描述:支付策略接口
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 17:47
 */
public interface PaymentStrategy {

    /**
     * 支付操作
     *
     * @param amount
     */
    void pay(double amount);
}

具体的策略实现

package cn.itcast.designPatterns.strategy.demo3;

import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:支付宝支付策略实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 17:51
 */
@Slf4j
public class AlipayPaymentStrategy implements PaymentStrategy {
    private String email;
    private String password;

    public AlipayPaymentStrategy(String email, String password) {
        this.email = email;
        this.password = password;
    }

    @Override
    public void pay(double amount) {
        log.info(">>> 支付宝支付:{}元.", amount);
    }
}

/**
 * 类描述:信用卡支付策略实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 17:48
 */
@Slf4j
public class CreditCardPaymentStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String expirationDate;

    public CreditCardPaymentStrategy(String name, String cardNumber, String cvv, String expirationDate) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.expirationDate = expirationDate;
    }

    @Override
    public void pay(double amount) {
        log.info(">>> 信用卡支付:{}元.", amount);
    }
}

测试案例

@Test
public void testPaymentStrategy() {
    // 创建信用卡支付策略实例
    PaymentStrategy creditCardStrategy = new CreditCardPaymentStrategy("张三", "15616221523", "1230", "12/23");
    // 创建支付宝支付策略实例
    PaymentStrategy alipayStrategy = new AlipayPaymentStrategy("[email protected]", "mypassword");

    // 根据用户选择的支付方式进行支付
    double amount = 100.33;
    creditCardStrategy.pay(amount);
    alipayStrategy.pay(amount);
}

在这个示例中定义了一个支付策略接口 PaymentStrategy ,并为不同的支付方式提供了具体实现。客户端代码可以根据用户选择的支付方式来调用相应的支付策略。当需要添加新的支付方式时,只需提供新的支付策略实现,而无需修改客户端代码,从而提高了系统的可扩展性。

3.4.2 优惠策略

使用策略模式的促销系统的简化示例

首先定义一个促销策略接口 PromotionStrategy :

package cn.itcast.designPatterns.strategy.demo4;

/**
 * 类描述:优惠策略接口
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 17:57
 */
public interface PromotionStrategy {

    /**
     * 计算优惠后的价格
     *
     * @param originalPrice
     * @return
     */
    double calculateDiscount(double originalPrice);
}

满减优惠方式的策略实现

package cn.itcast.designPatterns.strategy.demo4;

import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:满减优惠方式的策略实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 17:58
 */
@Slf4j
public class FullReductionStrategy implements PromotionStrategy {
    /**
     * 初始价格
     */
    private double threshold;
    /**
     * 满减金额
     */
    private double reduction;

    public FullReductionStrategy(double threshold, double reduction) {
        this.threshold = threshold;
        this.reduction = reduction;
    }

    @Override
    public double calculateDiscount(double originalPrice) {
        return originalPrice >= this.threshold ? originalPrice - reduction : originalPrice;
    }
}

打折优惠方式的策略实现

package cn.itcast.designPatterns.strategy.demo4;

/**
 * 类描述:打折优惠方式的策略实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 18:01
 */
public class DiscountStrategy implements PromotionStrategy {
    private double discount;

    public DiscountStrategy(double discount) {
        this.discount = discount;
    }

    @Override
    public double calculateDiscount(double originalPrice) {
        return originalPrice * this.discount;
    }
}

赠品策略实现

package cn.itcast.designPatterns.strategy.demo4;

import lombok.extern.slf4j.Slf4j;

/**
 * 类描述:新的促销方式策略(购买指定商品后可以获得免费赠品)
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 18:08
 */
@Slf4j
public class GiftStrategy implements PromotionStrategy {
    private double targetPrice;
    private String giftName;

    public GiftStrategy(double targetPrice, String giftName) {
        this.targetPrice = targetPrice;
        this.giftName = giftName;
    }

    @Override
    public double calculateDiscount(double originalPrice) {
        if (originalPrice >= targetPrice) {
            log.info(">>> 获得赠品:{}", giftName);
        }
        return originalPrice;
    }
}

无优惠的策略实现

package cn.itcast.designPatterns.strategy.demo4;

/**
 * 类描述:无优惠的策略实现
 *
 * @Author crysw
 * @Version 1.0
 * @Date 2024/3/10 18:02
 */
public class NoDiscountStrategy implements PromotionStrategy {
    @Override
    public double calculateDiscount(double originalPrice) {
        return originalPrice;
    }
}

测试案例

@Test
public void testPromotionStrategy() {
    // 创建满减策略实例
    PromotionStrategy fullReductionStrategy = new FullReductionStrategy(300, 100);
    // 创建打折策略实例
    PromotionStrategy discountStrategy = new DiscountStrategy(0.8);
    // 创建无优惠策略实例
    PromotionStrategy noDiscountStrategy = new NoDiscountStrategy();

    // 根据用户选择的促销方式计算优惠后的价格
    double originalPrice = 350.0;
    log.info(">>> 用户选择满减优惠, 优惠后的价格是:{}元", fullReductionStrategy.calculateDiscount(originalPrice));
    log.info(">>> 用户选择打折优惠, 优惠后的价格是:{}元", discountStrategy.calculateDiscount(originalPrice));
    log.info(">>> 用户无优惠, 价格是:{}元", noDiscountStrategy.calculateDiscount(originalPrice));

    log.info("===============================");
    // 创建赠品策略实例
    PromotionStrategy giftStrategy = new GiftStrategy(500, "公仔熊");
    originalPrice = 550.0;
    log.info(">>> 用户选择赠品优惠, 优惠后的价格是:{}元", giftStrategy.calculateDiscount(originalPrice));
}

3.5 总结

提到 if-else 分支判断,有人就觉得是烂代码。如果 if-else 分支判断不复杂、代码不多,并没有任何问题,毕竟 if-else 分支判断几乎是所有编程语言都会提供的语法。遵循KISS原则,简单就是最好的设计。非得用策略模式,搞出 n 多个类反倒是一种过度设计。

提到策略模式,有人就觉得它的作用是避免 if-else 分支判断逻辑。这种认识是很片面的。策略模式的主要作用是解耦策略的定义、创建和使用,控制代码的复杂度,让每个部分都不至于过于复杂、代码量过多。除此之外,对于复杂代码来说,策略模式还能让其满足开闭原则,添加新策略的时候,最小化、集中化代码改动,减少引入 bug 的风险。

策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式用来解耦策略的定义、创建、使用。 一个完整的策略模式就是由下面三个部分组成:

  • 策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
  • 策略的创建由工厂类来完成,封装策略创建的细节。
  • 策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。

除此之外,我们还可以通过策略模式来移除 if-else 分支判断。 这得益于策略工厂类,是借助“查表法”,根据 type 查表替代根据 type 分支判断。

你可能感兴趣的:(设计模式)