【策略方法】设计模式:构建灵活的算法替换方案

2024-08-30 19:04

本文主要是介绍【策略方法】设计模式:构建灵活的算法替换方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

摘要

在软件开发中,经常需要根据不同的条件应用不同的算法或行为。策略模式提供了一种优雅的解决方案,允许在运行时根据不同的需求动态替换算法。

原理

策略模式是一种行为设计模式,主要解决“类或对象之间的交互问题”,通过定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。

结构

  • 策略接口(Strategy):定义了一个公共的接口,所有的策略类都必须实现这个接口;
  • 具体策略类(Concrete Strategy):实现了策略接口的具体算法类;
  • 上下文(Context):使用策略接口作为其属性,可以配置并使用一个具体的策略类。

策略定义

策略类的定义:包含一个策略接口和一组实现这个接口的策略类。

public interface Strategy {void algorithmInterface();
}public class ConcreteStrategyA implements Strategy {@Overridepublic void  algorithmInterface() {//具体的算法...}
}public class ConcreteStrategyB implements Strategy {@Overridepublic void  algorithmInterface() {//具体的算法...}
}

策略的创建

根据类型创建策略的逻辑一般都放到工厂类中。

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);}
}

如果策略类是无状态的,不包含成员变量,只是单纯的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用getStrategy时都去创建一个新的策略对象。
这样的情况,我们可以使用上面这种工厂类的实现方式实现创建好每个策略对象,缓存到工厂类中,用的时候直接取出返回。

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

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;}
}

比如有一个计费策略接口BillingStrategy,有两个实现类NormalBillingStrategyDiscountBillingStrategy都需要记录当前的消费计数totalCount

public interface BillingStrategy {int getFinalPrice(int price, int totalCount);
}//普通计费策略
public class NormalBillingStrategy implements BillingStrategy {private int totalCount;public NormalBillingStrategy(int totalCount) {this.totalCount = totalCount;}@Overridepublic int getFinalPrice(int price, int totalCount) {return price * totalCount;}
}
//折扣计费策略
public class DiscountBillingStrategy implements BillingStrategy {private int totalCount;public DiscountBillingStrategy(int totalCount) {this.totalCount = totalCount;}@Overridepublic int getFinalPrice(int price, int totalCount) {if (totalCount > 5) {return (int) (price * totalCount * 0.8); // 8折优惠} else {return price * totalCount;}}
}

我们希望每次调用getStrategy方法都会返回一个新的BillingStrategy实例,以确保每个策略对象的totalCount相互独立。因此可以使用下面的工厂方法来创建策略对象。

public class BillingStrategyFactory {public static BillingStrategy getStrategy(String type, int totalCount) {switch (type) {case "Normal":return new NormalBillingStrategy(totalCount);case "Discount":return new DiscountBillingStrategy(totalCount);default:throw new IllegalArgumentException("Invalid strategy type");}}
}

这样每次调用getStrategy方法都会创建一个新的BillingStrategy实例,以确保状态独立性。

BillingStrategy strategy1 = BillingStrategyFactory.getStrategy("Normal", 10);
BillingStrategy strategy2 = BillingStrategyFactory.getStrategy("Discount", 3);
strategy1.getFinalPrice(100,strategy1.totalCount)

策略的使用

最经典的场景:就是运行时动态确定使用哪种策略
所谓的“运行时动态”,指的是事先并不知道会使用哪种策略,而是在程序运行期间,根据配置、用户输入、计算结果这些不确定因素动态确定使用哪种策略。
比如,现在有一个策略接口EvictionStrategy,三个策略类LruEvictionStrategyFifoEvictionStrategyLfuEvictionStrategy,一个策略工厂EvictionStrategyFactory

运行时动态确定

比如有一个配置文件config.properties

eviction_type = lru
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);}
}

在真实项目中,我们更多的应该是基于用户输入或者其他接口的返回结果来动态确定使用哪种策略。

非运行时动态确定
public class Application {public static void main(String[] args) {EvictionStrategy evictionStrategy = new LruEvictionStrategy(); //直接写死了//...}
}

从上面代码看出,“非运行时动态确定”并不能发挥策略模式的优势。在这种场景下,策略模式实际上退化成了“面向对象的多态特性”或“基于接口而非实现编程原则”。

移除分支判断

策略模式适用于根据不同类型的动态,决定使用哪种策略的应用场景。
比如,下面这段代码,没有使用策略模式。

public class OrderService {public double discount(Order order) {double discount = 0.0;OrderType type = order.getType();if (type.equals(OrderType.NORMAL)) { // 普通订单//...省略折扣计算算法代码} else if (type.equals(OrderType.GROUPON)) { // 团购订单//...省略折扣计算算法代码} else if (type.equals(OrderType.PROMOTION)) { // 促销订单//...省略折扣计算算法代码}return discount;}
}

那么怎么用策略模式来移除分支判断逻辑呢?
将不同类型订单的打折策略设计成策略类,且由工厂类来负责创建策略对象。

// 策略的定义
public interface DiscountStrategy {double calDiscount(Order order);
}
// 省略NormalDiscountStrategy、GrouponDiscountStrategy、PromotionDiscountStrategy类代码...// 策略的创建
public class DiscountStrategyFactory {private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();static {strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());}public static DiscountStrategy getDiscountStrategy(OrderType type) {return strategies.get(type);}
}// 策略的使用
public class OrderService {public double discount(Order order) {OrderType type = order.getType();DiscountStrategy discountStrategy = DiscountStrategyFactory.getDiscountStrategy(type);return discountStrategy.calDiscount(order);}
}

但是,如果业务场景需要每次都创建不同的策略对象,那么就需要另一种工厂类的实现方式了。

public class DiscountStrategyFactory {public static DiscountStrategy getDiscountStrategy(OrderType type) {if (type == null) {throw new IllegalArgumentException("Type should not be null.");}if (type.equals(OrderType.NORMAL)) {return new NormalDiscountStrategy();} else if (type.equals(OrderType.GROUPON)) {return new GrouponDiscountStrategy();} else if (type.equals(OrderType.PROMOTION)) {return new PromotionDiscountStrategy();}return null;}
}

但是这种方式相当于把原来的if-else分支逻辑转移到了工厂类中,并没有真正的移除。那怎么解决呢?
我们可以通过反射来避免对策略工厂类的修改。具体步骤:

  • 通过一个配置文件或者自定义的annotation来标注都有哪些策略类;
  • 策略工厂读取类读取配置文件或搜索被annotation标注的策略类,然后通过反射动态的加载这些策略类,创建策略对象。
  • 当我们添加一个新策略时,只需要将这个新策略类添加到配置文件或使用annotation标注即可。
配置文件

定义配置文件strategies.properties

normal=com.xxx.NormalDiscountStrategy
groupon=com.xxx.GrouponDiscountStrategy
promotion=com.xxx.PromotionDiscountStrategy

策略工厂类 (DiscountStrategyFactory) 使用配置文件。

public class DiscountStrategyFactory {private static Properties properties = new Properties();static {try (FileInputStream fis = new FileInputStream("strategies.properties")) {properties.load(fis);} catch (IOException e) {throw new RuntimeException("Failed to load strategy configuration", e);}}public static DiscountStrategy getDiscountStrategy(OrderType type) {String className = properties.getProperty(type.toString());if (className == null) {throw new IllegalArgumentException("No strategy found for type: " + type);}try {Class<?> clazz = Class.forName(className);return (DiscountStrategy) clazz.getDeclaredConstructor().newInstance();} catch (Exception e) {throw new RuntimeException("Failed to create strategy instance for: " + className, e);}}
}
自定义注解

定义注解StrategyAnnotation

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyAnnotation {OrderType value();
}

定义类型枚举 OrderType

public enum OrderType {GROUP,NORMAL;
}

策略类使用注解

@StrategyAnnotation(OrderType.GROUPON)
public class GrouponDiscountStrategy implements DiscountStrategy {@Overridepublic double calculateDiscount(double originalPrice) {return originalPrice * 0.8; // 20% discount}
}// ... 其他策略类使用注解 ...

策略工厂类DiscountStrategyFactory

public class DiscountStrategyFactory {private static final Map<OrderType, DiscountStrategy> strategyMap = new HashMap<>();private static final Reflections reflections = new Reflections("strategy",new SubTypesScanner(false), new TypeAnnotationsScanner());static {Set<Class<? extends DiscountStrategy>> strategyClasses = reflections.getSubTypesOf(DiscountStrategy.class);for (Class<? extends DiscountStrategy> strategyClass : strategyClasses) {if (strategyClass.isAnnotationPresent(StrategyAnnotation.class)) {try {StrategyAnnotation annotation = strategyClass.getAnnotation(StrategyAnnotation.class);DiscountStrategy strategy = strategyClass.getDeclaredConstructor().newInstance();strategyMap.put(annotation.value(), strategy);} catch (Exception e) {throw new RuntimeException("Failed to instantiate strategy: " + strategyClass.getName(), e);}}}}// 根据类型获取策略算法类public static DiscountStrategy getStrategy(OrderType type) {return strategyMap.get(type);}// 使用public static void main(String[] args) {DiscountStrategy strategy = DiscountStrategyFactory.getStrategy(OrderType.NORMAL);System.out.println(strategy.calculateDiscount(2));}}

总结

策略模式为算法的替换提供了一种灵活且可扩展的方式。通过将策略的实现与使用分离,我们可以根据不同的业务场景轻松地替换或扩展策略,而不需要修改现有的代码。

这篇关于【策略方法】设计模式:构建灵活的算法替换方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121671

相关文章

检查 Nginx 是否启动的几种方法

《检查Nginx是否启动的几种方法》本文主要介绍了检查Nginx是否启动的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1. 使用 systemctl 命令(推荐)2. 使用 service 命令3. 检查进程是否存在4

Java方法重载与重写之同名方法的双面魔法(最新整理)

《Java方法重载与重写之同名方法的双面魔法(最新整理)》文章介绍了Java中的方法重载Overloading和方法重写Overriding的区别联系,方法重载是指在同一个类中,允许存在多个方法名相同... 目录Java方法重载与重写:同名方法的双面魔法方法重载(Overloading):同门师兄弟的不同绝

MySQL字符串转数值的方法全解析

《MySQL字符串转数值的方法全解析》在MySQL开发中,字符串与数值的转换是高频操作,本文从隐式转换原理、显式转换方法、典型场景案例、风险防控四个维度系统梳理,助您精准掌握这一核心技能,需要的朋友可... 目录一、隐式转换:自动但需警惕的&ld编程quo;双刃剑”二、显式转换:三大核心方法详解三、典型场景

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

springboot中配置logback-spring.xml的方法

《springboot中配置logback-spring.xml的方法》文章介绍了如何在SpringBoot项目中配置logback-spring.xml文件来进行日志管理,包括如何定义日志输出方式、... 目录一、在src/main/resources目录下,也就是在classpath路径下创建logba

SQL Server中行转列方法详细讲解

《SQLServer中行转列方法详细讲解》SQL行转列、列转行可以帮助我们更方便地处理数据,生成需要的报表和结果集,:本文主要介绍SQLServer中行转列方法的相关资料,需要的朋友可以参考下... 目录前言一、为什么需要行转列二、行转列的基本概念三、使用PIVOT运算符进行行转列1.创建示例数据表并插入数

C++打印 vector的几种方法小结

《C++打印vector的几种方法小结》本文介绍了C++中遍历vector的几种方法,包括使用迭代器、auto关键字、typedef、计数器以及C++11引入的范围基础循环,具有一定的参考价值,感兴... 目录1. 使用迭代器2. 使用 auto (C++11) / typedef / type alias

python项目打包成docker容器镜像的两种方法实现

《python项目打包成docker容器镜像的两种方法实现》本文介绍两种将Python项目打包为Docker镜像的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 目录简单版:(一次成功,后续下载对应的软件依赖)第一步:肯定是构建dockerfile,如下:第二步

Python + Streamlit项目部署方案超详细教程(非Docker版)

《Python+Streamlit项目部署方案超详细教程(非Docker版)》Streamlit是一款强大的Python框架,专为机器学习及数据可视化打造,:本文主要介绍Python+St... 目录一、针对 Alibaba Cloud linux/Centos 系统的完整部署方案1. 服务器基础配置(阿里