👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 什么是策略模式?
- 为什么需要策略模式?
- 怎样实现策略模式?
- 二、策略模式在源码中的运用
- 2.1、Java Collections 中的排序策略
- 2.2、Spring 中的资源加载策略
- 三、总结
- 参考
一、入门
什么是策略模式?
策略模式是一种行为设计模式,允许在运行时选择算法或行为。它将算法封装在独立的类中,使得它们可以互换,而不影响客户端代码。
为什么需要策略模式?
策略模式的主要目的是解决算法或行为在代码中硬编码的问题,使得系统更加灵活、可扩展和易于维护。可以优化大量的if-else。
怎样实现策略模式?
策略模式的主要角色如下:
● 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略所需的接口。
● 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为
● 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
【案例】促销活动
百货公司,在不同的节日,会有不同的促销
定义百货公司所有促销活动的共同接口
public interface Strategy {void show();
}
定义具体策略角色(Concrete Strategy):每个节日具体的促销活动
//为春节准备的促销活动A
public class StrategyA implements Strategy {public void show() {System.out.println("买一送一");}
}
//为中秋准备的促销活动B
public class StrategyB implements Strategy {public void show() {System.out.println("满200元减50元");}
}
//为圣诞准备的促销活动C
public class StrategyC implements Strategy {public void show() {System.out.println("满1000元加一元换购任意200元以下商品");}
}
定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
public class SalesMan { //持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } //向客户展示促销活动 public void salesManShow(){ strategy.show(); }
}
二、策略模式在源码中的运用
2.1、Java Collections 中的排序策略
Java的Collections.sort()
方法使用了策略模式来实现排序功能。它允许通过传递不同的Comparator
实现来定义不同的排序策略。
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);// 策略1:升序排序
Collections.sort(numbers, new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return a.compareTo(b);}
});
System.out.println("升序排序: " + numbers);// 策略2:降序排序
Collections.sort(numbers, new Comparator<Integer>() {@Overridepublic int compare(Integer a, Integer b) {return b.compareTo(a);}
});
System.out.println("降序排序: " + numbers);
在上面的代码中Comparator
是策略接口。具体的排序逻辑(升序、降序)是策略实现。Collections.sort()
是上下文,负责调用策略。
源码中,Collections
类的sort
方法,里面调用了Arrays
类的sort
方法。Arrays
的TimSort
类的sort
方法
// Collections类
public static <T> void sort(List<T> list, Comparator<? super T> c) {list.sort(c);
}default void sort(Comparator<? super E> c) {Object[] a = this.toArray();Arrays.sort(a, (Comparator) c); // 调用Arrays类的sort方法ListIterator<E> i = this.listIterator();for (Object e : a) {i.next();i.set((E) e);}
}// Arrays类
public static <T> void sort(T[] a, Comparator<? super T> c) {if (c == null) {sort(a);} else {if (LegacyMergeSort.userRequested)legacyMergeSort(a, c);elseTimSort.sort(a, 0, a.length, c, null, 0, 0); // 调用TimSort类的sort方法}
}
TimSort
类中,这里我们只要关注,我们传的策略,入参c
的使用地方就好了。这里会调用countRunAndMakeAscending
方法,我们关注这个方法,我们传的排序策略,也就是入参c
,会被使用。
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,T[] work, int workBase, int workLen) {assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;int nRemaining = hi - lo;if (nRemaining < 2)return; // Arrays of size 0 and 1 are always sorted// If array is small, do a "mini-TimSort" with no mergesif (nRemaining < MIN_MERGE) {int initRunLen = countRunAndMakeAscending(a, lo, hi, c); // 关注调用这个方法
...// countRunAndMakeAscending 方法
private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {assert lo < hi;int runHi = lo + 1;if (runHi == hi)return 1;if (c.compare(a[runHi++], a[lo]) < 0) { // 排序策略被使用while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)runHi++;reverseRange(a, lo, runHi);} else { while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)runHi++;}return runHi - lo;
}
2.2、Spring 中的资源加载策略
Spring框架中的ResourceLoader
接口和其实现类(如ClassPathResourceLoader
、FileSystemResourceLoader
等)也使用了策略模式。
ResourceLoader resourceLoader = new DefaultResourceLoader();// 策略1:从类路径加载资源
Resource classPathResource = resourceLoader.getResource("classpath:application.properties");
System.out.println("类路径资源: " + classPathResource.exists());// 策略2:从文件系统加载资源
Resource fileSystemResource = resourceLoader.getResource("file:/path/to/file.txt");
System.out.println("文件系统资源: " + fileSystemResource.exists());
ResourceLoader
是策略接口。具体的资源加载逻辑(类路径、文件系统等)是策略实现。DefaultResourceLoader
是上下文,负责调用策略。
下面是结合源码说明
策略接口:ResourceLoader 接口
public interface ResourceLoader {Resource getResource(String location);
}
具体策略:ClassPathResource、FileSystemResource等是具体的策略实现。
// ClassPathResource实现
public class ClassPathResource extends AbstractFileResolvingResource {private final String path;public ClassPathResource(String path) {this.path = path;}@Overridepublic InputStream getInputStream() throws IOException {InputStream is = getClassLoader().getResourceAsStream(path);if (is == null) {throw new FileNotFoundException("Resource not found: " + path);}return is;}
}// FileSystemResource
public class FileSystemResource extends AbstractResource {private final File file;public FileSystemResource(String path) {this.file = new File(path);}@Overridepublic InputStream getInputStream() throws IOException {return new FileInputStream(file);}
}
上下文:DefaultResourceLoader 是上下文,负责根据路径选择合适的策略。
public class DefaultResourceLoader implements ResourceLoader {@Overridepublic Resource getResource(String location) {// 根据路径前缀选择策略if (location.startsWith(CLASSPATH_URL_PREFIX)) {return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()));}else if (location.startsWith(FILE_URL_PREFIX)) {return new FileSystemResource(location.substring(FILE_URL_PREFIX.length()));}else {try {// 尝试作为URL加载URL url = new URL(location);return new UrlResource(url);}catch (MalformedURLException ex) {// 如果都不是,默认为文件系统资源return new FileSystemResource(location);}}}
}
这里应该有UU们会好奇,诶,为什么具体策略没有实现策略接口?Spring 的资源加载策略是策略模式的一种变体。它与经典策略模式的区别在于:
- 经典策略模式:
- 策略接口和具体策略是直接实现的。
- 例如,排序策略中,
Comparator
是策略接口,具体的排序类是策略实现。
- Spring 的资源加载策略:
- 策略接口(
ResourceLoader
)和具体策略(ClassPathResource
等)之间通过上下文(DefaultResourceLoader
)连接。 - 具体策略实现的是
Resource
接口,而不是ResourceLoader
接口。
- 策略接口(
三、总结
策略模式通过将算法或行为封装到独立的类中,提供了一种灵活、可扩展的方式来管理代码中的变化部分。它的核心优势是解耦和动态切换,但也会带来类的数量增加和客户端使用成本的问题。适用于需要动态切换行为、避免重复代码或隔离算法实现细节的场景。
优点
● 灵活性:允许在运行时动态切换算法或行为,无需修改客户端代码。
● 可扩展性:新增策略时只需添加新的策略类,符合开闭原则(对扩展开放,对修改关闭)。
● 解耦:将算法或行为与使用它的上下文分离,降低了代码的耦合度。
● 避免重复代码:将相似的算法提取到独立的策略类中,减少代码重复。
● 易于测试:每个策略类可以独立测试,简化了测试过程。
缺点
● 增加类的数量:每个策略都需要一个独立的类,可能会导致类的数量增多,增加系统复杂性。
● 客户端需要了解策略:客户端必须知道有哪些策略,并选择合适的策略,增加了使用成本。
● 性能开销:在运行时切换策略可能会引入额外的性能开销(如对象创建和销毁)
适用场景
● 需要动态切换算法或行为:例如,支付方式、排序算法、资源加载策略等。
● 有多个相似的类,只有行为不同:例如,不同类型的折扣计算、不同的日志记录方式等。
● 避免使用复杂的条件语句:当代码中有大量if-else或switch-case语句时,可以用策略模式替代。
● 需要隔离算法的实现细节:当不希望暴露算法的实现细节,或者希望算法可以独立变化时。
● 需要对算法进行扩展:当系统需要支持新的算法,且不希望修改现有代码时。
参考
黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili