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

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

相关文章

使用Java实现通用树形结构构建工具类

《使用Java实现通用树形结构构建工具类》这篇文章主要为大家详细介绍了如何使用Java实现通用树形结构构建工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录完整代码一、设计思想与核心功能二、核心实现原理1. 数据结构准备阶段2. 循环依赖检测算法3. 树形结构构建4. 搜索子

如何将Tomcat容器替换为Jetty容器

《如何将Tomcat容器替换为Jetty容器》:本文主要介绍如何将Tomcat容器替换为Jetty容器问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Tomcat容器替换为Jetty容器修改Maven依赖配置文件调整(可选)重新构建和运行总结Tomcat容器替

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

CentOS 7部署主域名服务器 DNS的方法

《CentOS7部署主域名服务器DNS的方法》文章详细介绍了在CentOS7上部署主域名服务器DNS的步骤,包括安装BIND服务、配置DNS服务、添加域名区域、创建区域文件、配置反向解析、检查配置... 目录1. 安装 BIND 服务和工具2.  配置 BIND 服务3 . 添加你的域名区域配置4.创建区域

mss32.dll文件丢失怎么办? 电脑提示mss32.dll丢失的多种修复方法

《mss32.dll文件丢失怎么办?电脑提示mss32.dll丢失的多种修复方法》最近,很多电脑用户可能遇到了mss32.dll文件丢失的问题,导致一些应用程序无法正常启动,那么,如何修复这个问题呢... 在电脑常年累月的使用过程中,偶尔会遇到一些问题令人头疼。像是某个程序尝试运行时,系统突然弹出一个错误提

IDEA中Git版本回退的两种实现方案

《IDEA中Git版本回退的两种实现方案》作为开发者,代码版本回退是日常高频操作,IntelliJIDEA集成了强大的Git工具链,但面对reset和revert两种核心回退方案,许多开发者仍存在选择... 目录一、版本回退前置知识二、Reset方案:整体改写历史1、IDEA图形化操作(推荐)1.1、查看提

电脑提示找不到openal32.dll文件怎么办? openal32.dll丢失完美修复方法

《电脑提示找不到openal32.dll文件怎么办?openal32.dll丢失完美修复方法》openal32.dll是一种重要的系统文件,当它丢失时,会给我们的电脑带来很大的困扰,很多人都曾经遇到... 在使用电脑过程中,我们常常会遇到一些.dll文件丢失的问题,而openal32.dll的丢失是其中比较

python中字符串拼接的几种方法及优缺点对比详解

《python中字符串拼接的几种方法及优缺点对比详解》在Python中,字符串拼接是常见的操作,Python提供了多种方法来拼接字符串,每种方法有其优缺点和适用场景,以下是几种常见的字符串拼接方法,需... 目录1. 使用 + 运算符示例:优缺点:2. 使用&nbsjsp;join() 方法示例:优缺点:3

Mysql中深分页的五种常用方法整理

《Mysql中深分页的五种常用方法整理》在数据量非常大的情况下,深分页查询则变得很常见,这篇文章为大家整理了5个常用的方法,文中的示例代码讲解详细,大家可以根据自己的需求进行选择... 目录方案一:延迟关联 (Deferred Join)方案二:有序唯一键分页 (Cursor-based Paginatio