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

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

相关文章

postgresql使用UUID函数的方法

《postgresql使用UUID函数的方法》本文给大家介绍postgresql使用UUID函数的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录PostgreSQL有两种生成uuid的方法。可以先通过sql查看是否已安装扩展函数,和可以安装的扩展函数

Java中Arrays类和Collections类常用方法示例详解

《Java中Arrays类和Collections类常用方法示例详解》本文总结了Java中Arrays和Collections类的常用方法,涵盖数组填充、排序、搜索、复制、列表转换等操作,帮助开发者高... 目录Arrays.fill()相关用法Arrays.toString()Arrays.sort()A

Spring Boot Maven 插件如何构建可执行 JAR 的核心配置

《SpringBootMaven插件如何构建可执行JAR的核心配置》SpringBoot核心Maven插件,用于生成可执行JAR/WAR,内置服务器简化部署,支持热部署、多环境配置及依赖管理... 目录前言一、插件的核心功能与目标1.1 插件的定位1.2 插件的 Goals(目标)1.3 插件定位1.4 核

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

Nginx安全防护的多种方法

《Nginx安全防护的多种方法》在生产环境中,需要隐藏Nginx的版本号,以避免泄漏Nginx的版本,使攻击者不能针对特定版本进行攻击,下面就来介绍一下Nginx安全防护的方法,感兴趣的可以了解一下... 目录核心安全配置1.编译安装 Nginx2.隐藏版本号3.限制危险请求方法4.请求限制(CC攻击防御)

python生成随机唯一id的几种实现方法

《python生成随机唯一id的几种实现方法》在Python中生成随机唯一ID有多种方法,根据不同的需求场景可以选择最适合的方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习... 目录方法 1:使用 UUID 模块(推荐)方法 2:使用 Secrets 模块(安全敏感场景)方法

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

MySQL深分页进行性能优化的常见方法

《MySQL深分页进行性能优化的常见方法》在Web应用中,分页查询是数据库操作中的常见需求,然而,在面对大型数据集时,深分页(deeppagination)却成为了性能优化的一个挑战,在本文中,我们将... 目录引言:深分页,真的只是“翻页慢”那么简单吗?一、背景介绍二、深分页的性能问题三、业务场景分析四、

MySQL 迁移至 Doris 最佳实践方案(最新整理)

《MySQL迁移至Doris最佳实践方案(最新整理)》本文将深入剖析三种经过实践验证的MySQL迁移至Doris的最佳方案,涵盖全量迁移、增量同步、混合迁移以及基于CDC(ChangeData... 目录一、China编程JDBC Catalog 联邦查询方案(适合跨库实时查询)1. 方案概述2. 环境要求3.

JAVA中安装多个JDK的方法

《JAVA中安装多个JDK的方法》文章介绍了在Windows系统上安装多个JDK版本的方法,包括下载、安装路径修改、环境变量配置(JAVA_HOME和Path),并说明如何通过调整JAVA_HOME在... 首先去oracle官网下载好两个版本不同的jdk(需要登录Oracle账号,没有可以免费注册)下载完