设计模式-策略模式(Stategy Pattern)
设计模式-策略模式(Stategy Pattern)
概要
记忆关键词:算法替换
定义:定义了一个算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
类型:行为型
策略模式结构图如下:
一、解决了什么问题?
1. 算法的选择和切换
策略模式通过将具体的算法实现分离出来,使得可以在运行时选择不同的算法。这对于那些需要在多个算法之间进行选择的应用场景非常有用。例如,支付系统中可以有不同的支付方式,用户可以选择使用信用卡、PayPal、或者银行转账。
2. 减少条件判断
在没有策略模式的情况下,可能会在代码中看到很多的条件判断语句来选择具体的算法或行为。这样代码会变得复杂且难以维护。通过使用策略模式,可以将这些条件判断移到不同的策略实现中,从而简化客户端代码。
二、涉及的角色
1. Strategy(策略)
Strategy角色负责决定实现策略所必需的接口
代码示例:
1 public interface DealStrategy { 2 /** 3 * 定义策略接口 4 * 5 * @param option 请求参数 6 */ 7 void dealMethod(String option); 8 }
2. ConcreteStrategy(具体的策略)
ConcreteStrategy角色负责实现Strategy角色的接口,即负责实现具体的策略
代码示例:
1 public class DealSina implements DealStrategy{ 2 3 /** 4 * 定义策略接口 5 * 6 * @param option 请求参数 7 */ 8 public void dealMethod(String option) { 9 System.out.println("分享到新浪"); 10 } 11 }
3. Context(上下文)
Context负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求。用一个ConcreteStrategy 来配置,维护一个对Strategy 对象的引用。
代码示例:
1 public class DealContext { 2 private final String type; 3 private final DealStrategy deal; 4 5 public DealContext(String type, DealStrategy deal) { 6 this.type = type; 7 this.deal = deal; 8 } 9 10 public DealStrategy getDeal(){ 11 return deal; 12 } 13 14 public boolean options(String type) { 15 return this.type.equals(type); 16 } 17 }
客户端调用:
1 public class Share { 2 3 private static final List<DealContext> ALGORITHMS = new ArrayList<>(); 4 5 //静态代码块,先加载所有的策略 6 static { 7 ALGORITHMS.add(new DealContext("Sina", new DealSina())); 8 ALGORITHMS.add(new DealContext("Wechat", new DealWechat())); 9 } 10 11 public static void shareOptions(String type) { 12 DealStrategy dealStrategy = null; 13 for (DealContext deal : ALGORITHMS) { 14 if (deal.options(type)) { 15 dealStrategy = deal.getDeal(); 16 break; 17 } 18 } 19 20 Optional.ofNullable(dealStrategy).ifPresent(s -> s.dealMethod(type)); 21 } 22 23 public static void main(String[] args) { 24 shareOptions("Wechat"); 25 } 26 }
4. 使用场景
在Java中,比较器接口Comparator就是一个常见的策略模式实践
1 public static <T> void sort(T[] a, Comparator<? super T> c) { 2 if (c == null) { 3 sort(a); 4 } else { 5 if (LegacyMergeSort.userRequested) 6 legacyMergeSort(a, c); 7 else 8 TimSort.sort(a, 0, a.length, c, null, 0, 0); 9 } 10 }
比如 Arrays.sort(numbers, new ReverseComparator());它允许我们传入一个Comparator实例来指定我们想要的策略
1 public class ReverseComparator implements Comparator{ 2 public int compare(String s1, String s2) { 3 return Integer.compare(s1.length(), s2.length()); 4 } 5 }
三、实际项目中的应用:简单工厂+策略模式结合
UML类图如下:
定义了一个 ShowDateFactory 工厂类,用于管理和获取不同风格的 ShowDateStrategy 策略对象
1 public class ShowDateFactory { 2 3 private ShowDateFactory() {} 4 5 private static final Map<ShowDateStyleEnum, ShowDateStrategy> MAP = new HashMap<>(); 6 7 public static void setMap(ShowDateStyleEnum strategy, ShowDateStrategy showDateStrategy) { 8 MAP.putIfAbsent(strategy, showDateStrategy); 9 } 10 11 public static ShowDateStrategy getMap(ShowDateStyleEnum strategy) { 12 return MAP.get(strategy); 13 } 14 }
具体策略类 FuzzyDateStyle:
1 @Service 2 public class FuzzyDateStyle implements ShowDateStrategy { 3 /** 4 * 将策略类注册到工厂中,以便后续可以通过工厂类获取相应的策略对象 5 * 6 */ 7 @PostConstruct 8 public void init() { 9 ShowDateFactory.setMap(strategySign(), this); 10 } 11 12 /** 13 * 模糊时间样式 14 * 15 * @return String 16 */ 17 @Override 18 public ShowDateStyleEnum strategySign() { 19 return ShowDateStyleEnum.SHOW_DATE_FUZZY; 20 } 21 22 }
说明:上面这段代码这段代码使用了 @PostConstruct
注解,表明该方法会在该类被 Spring 容器初始化时执行。在系统初始化的时候,策略类就会被注册到工厂,而不需要手动在其他地方显式地调用注册方法。这种自动注册的机制使得系统更加灵活,可以动态地添加新的策略类而无需修改工厂类。在使用该策略工厂时,只需要调用工厂类的相应方法就能获取到对应的策略对象。
除了上面这种方式,还可以进行动态加载配置文件,通过反射动态地加载这些策略类、创建策略对象来实现策略模式
1. 创建属性文件(例如 strategies.properties)
creditCard=com.example.CreditCardPayment
payPal=com.example.PayPalPayment
2. 定义工厂类:PaymentStrategyFactory,以动态加载配置文件中的策略类信息
1 public class PaymentStrategyFactory { 2 private static final Map<String, Class<? extends PaymentStrategy>> strategies = new HashMap<>(); 3 4 static { 5 loadStrategies(); 6 } 7 8 private static void loadStrategies() { 9 Properties properties = new Properties(); 10 try { 11 properties.load(PaymentStrategyFactory.class.getClassLoader().getResourceAsStream("strategies.properties")); 12 } catch (IOException e) { 13 e.printStackTrace(); 14 } 15 16 for (String strategyName : properties.stringPropertyNames()) { 17 String className = properties.getProperty(strategyName); 18 try { 19 Class<? extends PaymentStrategy> strategyClass = Class.forName(className).asSubclass(PaymentStrategy.class); 20 strategies.put(strategyName, strategyClass); 21 } catch (ClassNotFoundException | ClassCastException e) { 22 e.printStackTrace(); 23 } 24 } 25 } 26 27 public static PaymentStrategy getPaymentStrategy(String strategyName) { 28 Class<? extends PaymentStrategy> strategyClass = strategies.get(strategyName); 29 if (strategyClass == null) { 30 throw new IllegalArgumentException("Unknown payment strategy: " + strategyName); 31 } 32 33 try { 34 return strategyClass.getDeclaredConstructor().newInstance(); 35 } catch (Exception e) { 36 throw new RuntimeException("Failed to create payment strategy", e); 37 } 38 } 39 }
三、算法的分析
1. 策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2. 在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
3. 策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
4. 在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象,这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就最大化地减轻了客户端的职责。
四、 策略模式的使用场景
- 一个项目中有许多类,它们之间的区别仅在于它们的行为,希望动态地让一个对象在许多行为中选择一种行为时;
- 一个项目需要动态地在几种算法中选择一种时;
- 一个对象有很多的行为,不想使用多重的条件选择语句来选择使用哪个行为时。
通过策略模式将策略的定义、创建、使用解耦,让每一部分都不至于太复杂,也去除了if...else这样的条件判断语句,代码的可维护性和可拓展性都提高了。
在日常使用SpringBoot进行开发的项目中,一般都是将策略模式跟工厂模式结合起来使用的,请参考文章《SpringBoot 使用策略+工厂模式的几种实现方式》
参考链接:
https://mp.weixin.qq.com/s/gB1nM4q9PculNVZJr9NSHA
https://design-patterns.readthedocs.io/zh-cn/latest/behavioral_patterns/strategy.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)