目录
3、实体类实现 Comparable接口,避免Sort相关类越来越多
4、实体类实现带泛型 Comparable接口,避免重写compareTo方法时类型强转
一、策略模式定义
策略模式定义了一系列的方法,并将每个方法封装起来,使每个方法可以相互替代,使用方法本身和使用方法的客户端分割开来,相互独立
大白话:同一个类的同一个方法存在不同策略,且策略根据客户端来决定选择哪种策略适合。
二、深入浅出拨开策略模式
这么介绍可能很抽象,还是较难理解策略模式。下面我们将从实际代码一层层拨开策略模式的知识。
1、普通整型排序
定义一个对 整型 int 排序的类及排序方法(为了好理解,以选择排序为例);应用 T01_Main中 main 方法对其调用排序。
a) 下面是对 整型 int 排序的类及排序方法
public class T01_Sorter {
public static void sort(int[] arr) {
for (int time = 0; time < arr.length - 1; time++) {
int min = time; // 取当前剩下数组第一个元素下标为最小下标
for (int i = time + 1; i < arr.length; i++) {
// 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
if (arr[i] < arr[min]) {
min = i;
}
}
//System.out.println("minPos: " + minPos);
// 找到后,将两个值进行交换
swap(arr, min, time);
//System.out.println("经过第" + i + "次循环之后,数组的内容:");
}
}
private static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
b) 下面是应用 T01_Main中 main 方法对其调用排序
public class T01_Main {
public static void main(String[] args) {
int[] a = {9,2,3,5,7,1,4};
T01_Sorter sorter = new T01_Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
这似乎世界很友好,也能解决当前问题。现在来了个新需求:新增猫类,并需要对其进行排序。于是我们继续分析
2、新增自定义类(猫类)并排序
接到了新需求:新增猫类,并需要对其进行排序。首先,我们先定义一个 Bean 类 猫类 Cat。
public class T02_Cat {
int weight, height;
public T02_Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
// 定义猫的比较方法
public int compareTo(T02_Cat c) {
if (this.weight < c.weight) return -1;
else if (this.weight > c.weight) return 1;
else return 0;
}
@Override
public String toString() {
return "T03_Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
再编写一个对猫进行排序的类,因为猫的排序方式是按照身高或体重来排序的,不能复用之前的整型的排序方式
public class T02_Sorter {
public static void sort(T02_Cat[] arr) {
for (int time = 0; time < arr.length - 1; time++) {
int min = time; // 取当前剩下数组第一个元素下标为最小下标
for (int i = time + 1; i < arr.length; i++) {
// 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
min = arr[i].compareTo(arr[min]) == -1 ? i : min;
}
//System.out.println("minPos: " + minPos);
// 找到后,将两个值进行交换
swap(arr, min, time);
//System.out.println("经过第" + i + "次循环之后,数组的内容:");
}
}
private static void swap(T02_Cat[] arr, int i, int j){
T02_Cat temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
再创建应用类,对猫进行排序
public class T02_Main {
public static void main(String[] args) {
T02_Cat[] a = {new T02_Cat(3,3), new T02_Cat(5,5), new T02_Cat(1,1)};
T02_Sorter sorter = new T02_Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
这似乎也能解决需求新增类实体并排序问题,但仔细观察我们发现,如果此时又来一个新的需求:新增狗类并排序。如果按照上述思路继续,我们又得增加狗的Bean类、增加狗的排序类。类越来越多,后期维护起来麻烦。
既然意识到此问题,我们应该怎样解决问题呢?实体Bean类、及对应的排序类是一一对应关系,将比较大小的方式统一写在实体Bean类就可以解决类越来越多的问题,是不错的解决方案。
3、实体类实现 Comparable接口,避免Sort相关类越来越多
为了统一实体类的比较大小的方式,实体类需要实现 Comparable 接口,重写 compareTo方法,这样的好写,实体类的比较方法都是统一的,且大家都需要重写 compareTo 方法。
public interface T03_Comparable {
public int compareTo(Object a);
}
接下来在定义实体Bean 类时,使用 implements 关键字来实现 Comarable接口
public class T03_Cat implements T03_Comparable {
int weight, height;
public T03_Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
// 定义猫的比较方法
@Override
public int compareTo(Object c) {
T03_Cat cat = (T03_Cat)c;
if (this.weight < cat.weight) return -1;
else if (this.weight > cat.weight) return 1;
else return 0;
}
@Override
public String toString() {
return "T03_Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
排序类 Sorter 不再是对Bean 类进行排序了,而是对接口进行排序。具体比较大小的规则在对应的实体类内部实现,既后续只需要扩展新增对应的实体类(要求实现 implement Compare接口)即可。
public class T03_Sorter {
public static void sort(T03_Comparable[] arr) {
for (int time = 0; time < arr.length - 1; time++) {
int min = time; // 取当前剩下数组第一个元素下标为最小下标
for (int i = time + 1; i < arr.length; i++) {
// 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
min = arr[i].compareTo(arr[min]) == -1 ? i : min;
}
//System.out.println("minPos: " + minPos);
// 找到后,将两个值进行交换
swap(arr, min, time);
//System.out.println("经过第" + i + "次循环之后,数组的内容:");
}
}
private static void swap(T03_Comparable[] arr, int i, int j){
T03_Comparable temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
应用类对实体类对象进行排序调用保持不变
public class T03_Main {
public static void main(String[] args) {
T03_Cat[] a = {
new T03_Cat(3,3),
new T03_Cat(5,5),
new T03_Cat(1,1)
};
T03_Sorter sorter = new T03_Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
至此: 实体类通过实现 Compare接口,重写 compareTo比较大小方法;比较类 Sorter只需要对接口进行排序即可,算是解决Sorter不再随着实现类新增而新增问题。
但是,上述这种方式不太方便,每次在实体类重定 compareTo 比较大小方法,都需要进行类型强转。为此,可以引用泛型来避免类型强转问题。补充说明一下:泛型是JavaSE5 引入的一个新概念。
4、实体类实现带泛型 Comparable接口,避免重写compareTo方法时类型强转
第三个版本使用Object,实现类在重写 compareTo方法需要进行类型强转,非常不便,可以使用泛型解决
补充说明:泛型是指在接口中,不确定实现此接口的类型是什么,先由泛型T 代替,在实现类中需要指定泛型类型
// 第三个版本使用Object,实现类需要进行强转;非常不便,可以使用泛型
public interface T04_Comparable<T> {
public int compareTo(T a);
}
实体类实现 Comparable 接口,同时指定泛型类型
// 实体类实现Comparable 时指定泛型类型
public class T04_Cat implements T04_Comparable<T04_Cat> {
int weight, height;
public T04_Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
// 定义猫的比较方法
@Override
public int compareTo(T04_Cat cat) {
if (this.weight < cat.weight) return -1;
else if (this.weight > cat.weight) return 1;
else return 0;
}
@Override
public String toString() {
return "T03_Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
Sorter 类排序还是一样对 Compareable排序,与第三个版本保持不变
public class T04_Sorter {
public static void sort(T04_Comparable[] arr) {
for (int time = 0; time < arr.length - 1; time++) {
int min = time; // 取当前剩下数组第一个元素下标为最小下标
for (int i = time + 1; i < arr.length; i++) {
// 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
min = arr[i].compareTo(arr[min]) == -1 ? i : min;
}
//System.out.println("minPos: " + minPos);
// 找到后,将两个值进行交换
swap(arr, min, time);
//System.out.println("经过第" + i + "次循环之后,数组的内容:");
}
}
private static void swap(T04_Comparable[] arr, int i, int j){
T04_Comparable temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
应用类与第三个版本应用类保持不变
public class T04_Main {
public static void main(String[] args) {
T04_Cat[] a = {
new T04_Cat(3,3),
new T04_Cat(5,5),
new T04_Cat(1,1)
};
T04_Sorter sorter = new T04_Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
这似乎能满足大部分场景,答案是:可以!但到了大型后台业务系统中,就可能水土不服了。为什么这么说呢?
我们还是接着前面猫的比较大小的场景,在业务系统中可能需要按照体重比较大小,也有可能按照身高比较大小,即同一个实体类在不同的业务情况下比较大小的方式不一样。按照上述方式依然无法解决此问题(只能解决一种比较大小的方式),难道就没有办法解决了么?答案是:有的!
终于,引入了正题,前面都是铺垫,是为了更好的理解策略模式。
5、通过比较器实现:实体类比较大小的不同策略
先定认猫类,这里猫类不再实现 Comparable 接口,因此该方式只能实现一种比较大小的策略,如下:
public class T05_Cat {
public int weight, height;
public T05_Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
@Override
public String toString() {
return "T03_Cat{" +
"weight=" + weight +
", height=" + height +
'}';
}
}
猫类比较大小的不同策略,都是compare 方法,为此先定义一个接口比较器 T05_Comparator<T>
,后续按体重比较策略类、按身高比较策略类都实现 T05_Comparator<T> 接口即可
@FunctionalInterface // 如果确认只有这一个方法,不编写@FunctionalInterface 也是可以的
public interface T05_Comparator<T> {
int compare(T o1, T o2);
}
再定义身高大小比较器、体重大小比较器
// 按身高大小的比较器
public class T05_CatHeightComparator implements T05_Comparator<T05_Cat> {
@Override
public int compare(T05_Cat o1, T05_Cat o2) {
if (o1.height > o2.height) return -1;
else if (o1.height < o2.height) return 1;
else return 0;
}
}
// 按体重大小的比较器
public class T05_CatWeightComparator implements T05_Comparator<T05_Cat> {
@Override
public int compare(T05_Cat o1, T05_Cat o2) {
if (o1.weight < o2.weight) return -1;
else if (o1.weight > o2.weight) return 1;
else return 0;
}
}
再定义排序类,将比较器作为参数传入,按照比较器策略来进行大小比较
// 排序类中,将比较器作为参数传入,按照比较器策略来进行大小比较
public class T05_Sorter<T> {
public void sort(T[] arr, T05_Comparator comparator) {
for (int time = 0; time < arr.length - 1; time++) {
int min = time; // 取当前剩下数组第一个元素下标为最小下标
for (int i = time + 1; i < arr.length; i++) {
// 找到比当前元素值更小的下标,从当前数组序列头开始一直到结尾
min = comparator.compare(arr[i], arr[min]) == -1 ? i : min;
}
//System.out.println("minPos: " + minPos);
// 找到后,将两个值进行交换
swap(arr, min, time);
//System.out.println("经过第" + i + "次循环之后,数组的内容:");
}
}
private void swap(T[] arr, int i, int j){
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
最后,通过应用类来选择不同的比较策略,按需所取
public class T05_Main {
public static void main(String[] args) {
T05_Cat[] a = {
new T05_Cat(3,3),
new T05_Cat(5,5),
new T05_Cat(1,1)
};
T05_Sorter<T05_Cat> sorter = new T05_Sorter<T05_Cat>();
//sorter.sort(a, new T05_CatWeightComparator());
// 采用策略模型:
//sorter.sort(a, new T05_CatHeightComparator());
// 写法上,可以采用lambda 表达式进行
sorter.sort(a, (o1, o2) -> {
T05_Cat cat1 = (T05_Cat)o1;
T05_Cat cat2 = (T05_Cat)o2;
if (cat1.weight < cat2.weight) return -1;
else if (cat1.weight > cat2.weight) return 1;
else return 0;
});
// 如果一个策略每次调用,都需要 new,因此应该把 DefaultStrategy -> Singleton
// 通过配置文件加载策略类名,可以动态调整:对修改关闭、对扩展开放(Extensibility Scalability)
String goodStrategyClassName = "";//PrppertyMgr.get("goodWay");
try {
//T05_CatHeightComparator goodStrategy = (T05_CatHeightComparator)Class.forName(goodStrategyClassName).newInstance();
T05_CatHeightComparator goodStrategy = (T05_CatHeightComparator)Class.forName(goodStrategyClassName).getDeclaredConstructor().newInstance();
sorter.sort(a, goodStrategy);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 还可以通过加载类名
System.out.println(Arrays.toString(a));
}
}
小总结:
核心点,将策略封装成一个接口,然后可以通过实现策略接口方式实现各式各样的不同策略;应用类调用时按需传入不同的策略即可,或者使用匿名内部类的方式实现接口方法
文章最后,给大家推荐一些受欢迎的技术博客链接:
- JAVA相关的深度技术博客链接
- Flink 相关技术博客链接
- Spark 核心技术链接
- 设计模式 —— 深度技术博客链接
- 机器学习 —— 深度技术博客链接
- Hadoop相关技术博客链接
- 超全干货--Flink思维导图,花了3周左右编写、校对
- 深入JAVA 的JVM核心原理解决线上各种故障【附案例】
- 请谈谈你对volatile的理解?--最近小李子与面试官的一场“硬核较量”
- 聊聊RPC通信,经常被问到的一道面试题。源码+笔记,包懂
- 深入聊聊Java 垃圾回收机制【附原理图及调优方法】
欢迎扫描下方的二维码或 搜索 公众号“大数据高级架构师”,我们会有更多、且及时的资料推送给您,欢迎多多交流!