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

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

相关文章

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

Spring Gateway动态路由实现方案

《SpringGateway动态路由实现方案》本文主要介绍了SpringGateway动态路由实现方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录前沿何为路由RouteDefinitionRouteLocator工作流程动态路由实现尾巴前沿S

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

SpringBoot中ResponseEntity的使用方法举例详解

《SpringBoot中ResponseEntity的使用方法举例详解》ResponseEntity是Spring的一个用于表示HTTP响应的全功能对象,它可以包含响应的状态码、头信息及响应体内容,下... 目录一、ResponseEntity概述基本特点:二、ResponseEntity的基本用法1. 创