-
需求分析:
假如现在你写了一个函数调整一个整数数组的顺序, 使得所有奇数位于数组的前半部分, 偶数位于数组的后半部分. 代码如下:public class ReorderArray { public static void reorderArrayByEvenAndOdd(int [] array) { if (array == null || array.length == 0) { return; } int start = 0; int end = array.length - 1; while (start < end) { // 这里进行判断 while (start < end && (array[start] & 1) == 1) { start++; } // 这里进行判断 while (start < end && (array[end] & 1) == 0) { end--; } if (start < end) { int temp = array[start]; array[start] = array[end]; array[end] = temp; } } } }现在如果要求改为使得所有负数位于数组前半部分, 所有正数位于数组后半部分, 代码就要改为:
public class ReorderArray { public static void reorderArrayByPositiveAndNegative(int [] array) { if (array == null || array.length == 0) { return; } int start = 0; int end = array.length - 1; while (start < end) { // 这里的判断条件变了 while (start < end && array[start] < 0) { start++; } // 这里的判断条件变了 while (start < end && array[end] > 0) { end--; } if (start < end) { int temp = array[start]; array[start] = array[end]; array[end] = temp; } } } }如果需要同时保留两个调整顺序的功能, 则代码中就要保留两个函数, 如果这个时候还需要一个函数使得 能被 3 整除的数位于数组前半部分, 不能被 3 整除的数位于数组后半部分, 又需要在类里面增加一个函数. 这样代码里面会充斥着大量的重复性代码, 并且客户端调用方法也不好调用, 每个方法的名字也不一样, 客户端需要知道哪个方法是按照哪种规则进行数据顺序调整的.
但是通过观察发现, 我们增加的函数除了代码中有注释的地方的判断条件变了, 其它代码都是一样的, 所以我们可以这样调整一下代码:
public class ReorderArray { public static final String REORDER_TYPE_EVEN_AND_ODD = "even_and_odd"; public static final String RECORD_TYPE_POSITIVE_AND_NEGATIVE = "positive_and_negative"; public static void reorderArray(int [] array, String reorderType) { if (array == null || array.length == 0) { return; } int start = 0; int end = array.length - 1; while (start < end) { if (REORDER_TYPE_EVEN_AND_ODD.equals(reorderType) { // 这里进行判断 while (start < end && (array[start] & 1) == 1) { start++; } // 这里进行判断 while (start < end && (array[end] & 1) == 0) { end--; } } else if (RECORD_TYPE_POSITIVE_AND_NEGATIVE.equals(reorderType)) { // 这里的判断条件变了 while (start < end && array[start] < 0) { start++; } // 这里的判断条件变了 while (start < end && array[end] > 0) { end--; } } if (start < end) { int temp = array[start]; array[start] = array[end]; array[end] = temp; } } } }现在我们在给方法增加了一个 reorderType, 然后在函数内部根据排序类型去执行对应判断, 这样减少了重复性代码的数量, 但是又在排序函数内部增加了 if-else 判断, 这样会增加维护的难度. 比如我们要调整排序策略, 让奇数位于数组后半部分, 让偶数位于数组前半部分, 我们会修改第一个 if 里面的判断条件, 可是如果我们不小心改到了其它分支里面的判断条件, 就会造成其它排序规则出现错误.
我们应该避免在排序函数中进行类型的判断, 排序函数的职责就应该是排序就好了, 不需要知道其它的东西. 我们可以考虑让客户端将排序的规则传入进来, 客户端是知道自己需要怎样规则的排序的.
- 首先定义一个排序策略接口, 包含一个判断函数声明:
public class ReorderStrategy { boolean judgeNumber(int number); }- 然后定义具体的排序策略判断标准:
/** * 奇数和偶数判断标准 */ public class EvenAndOddReorderStrategy { public void boolean judgeNumber(int number) { if ((number & 1) == 1) { return true; } else { return false; } } } /** * 整数和负数判断标准 */ public class PositiveAndNegativeReorderStrategy { public void boolean judgeNumber(int number) { if (number < 0) { return true; } else { return false; } } }- 更改排序函数:
public void reorderArray(int [] array, ReorderStrategy reorderStrategy) { if (array == null || array.length == 0) { return; } int start = 0; int end = array.length - 1; while (start < end) { // 这里进行判断 while (start < end && reorderStrategy.judge(array[start])) { start++; } // 这里进行判断 while (start < end && !recordStrategy.judge(array[start])) { end--; } if (start < end) { int temp = array[start]; array[start] = array[end]; array[end] = temp; } } }这样排序函数中没有了 if-else 判断, 客户端需要什么样的排序就可以传入什么样的排序策略. 若需要更改某个排序策略, 直接找到对应类修改即可, 修改某个排序策略 不会对其它排序策略产生影响. 甚至我们还可以利用 Java 8 的 Lambda 表达式来调用排序函数, 都不用预先实现一个具体的排序策略.
reorderArray(testArr, number -> number % 3 == 0); -
策略模式定义:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it. (定义一组算法, 将每个算法都封装起来, 并且使它们之间可以互换. 策略模式使得算法可以独立于客户端变化.)
Context 叫做上下文, 起承上启下的封装作用, 屏蔽客户端对策略算法的直接访问, 客户端只需要调用
Context#operation()方法即可调用底层的策略方法. Strategy 为策略接口, 声明策略算法方法, 具体策略需要实现 Strategy 接口, 如 ConcretedStrategyA, ConcretedStrategyB. -
策略模式的应用:
javax.servlet.http.HttpServlet#service()方法
java.util.Comparator#compare()方法
javax.servlet.Filter#doFilter()方法 -
策略模式拓展:
策略模式常常和简单工厂模式进行配合使用, 简单工厂根据客户端传入的类型生成策略, 这样客户端不需要知道具体策略的实现类, 只需要传入一个类型码就可以了.
具体的策略可以使用匿名内部类或者 Lambda 表达式来实现
策略还可以使用枚举来实现:public Enum CalculationStratety { /** * 加法策略 */ ADD("+") { public int calculate(int a, int b) { return a + b; } } /** * 乘法策略 */ MULTIPLY("*") { public int calculate(int a, int b) { return a * b; } } private String operationSymbol; CalculationStratety(String operationSymbol) { this.operationSymbol = operationSymbol; } protected abstract int calculate(int a, int b); }枚举策略适合于策略实现逻辑比较简单的情况. 例如上面的运算策略, 基本上一行代码就可以实现. 如果策略实现逻辑比较复杂还是应该拆分成单独的策略实现类.
-
参考:
[1] : Java 库中的设计模式
[2] : 设计模式之禅
[3] : Head First 设计模式