了解策略模式–通过详细代码与例子
应用场景
完成一项任务,往往可以有多种策略可以实现,要想灵活选择策略和添加新策略,可以使用策略模式。
比如从数组中查找某个值的任务,我们可以从头到尾遍历查找,可以从尾到头遍历查找,如果数组有序,还可以用二分法查找。不同的数组可以采用不同的策略来实现查找任务,我们还想让数组的查找方式能设置与修改,这时候就可以用上策略模式,把不同的查找方法封装成独立的类,数组可以自行选择使用哪种查找策略。
定义
策略模式定义了一系列策略,每一个策略都封装起来,并让他们可以相互转换,使用策略的客户可以任意选择策略,策略的变化独立于使用策略的客户
例子
就用上面的数组查值为例子,任务是从数组中查找某个值,策略有从头到尾查找和从尾到头查找,策略独立于数组类,下面是UML类图
NumberArray为抽象类,内部属性有protected的arr 和searchAlgorithm ,arr是Number数组,存放数据,searchAlgorithm是接口ISearchAlgorithm类型,后者定义了find方法用于从数据中查找某个数。实现了该接口的类有LeftSearchAlgorithm和RightSearchAlgorithm,分别实现了从头到尾和从尾到头查找数值的策略。NumberArray的子类有IntegerArray和DoubleArray,分别是整数数组和double小数数组,下面是代码
ISearchAlgorithm.java
package priv.mxz.design_pattern.strategy_pattern;
interface ISearchAlgorithm {
int find(Number[] arr, Number num);
}
class LeftSearchAlgorithm implements ISearchAlgorithm {
@Override
public int find(Number[] arr,Number num) {
if (arr==null)
return -1;
for (int i=0; i<arr.length; i++){
if (arr[i].equals(num))
return i;
}
return -1;
}
@Override
public String toString() {
return "strategy_pattern.LeftSearchAlgorithm";
}
}
class RightSearchAlgorithm implements ISearchAlgorithm {
@Override
public int find(Number[] arr, Number num) {
if (arr==null)
return -1;
for (int i=arr.length-1; i>=0; i--){
if (arr[i].equals(num))
return i;
}
return -1;
}
@Override
public String toString() {
return "strategy_pattern.RightSearchAlgorithm";
}
}
NumberArray.java
package priv.mxz.design_pattern.strategy_pattern;
import java.util.Arrays;
abstract class NumberArray {
protected Number[] arr;
protected ISearchAlgorithm searchAlgorithm;
public NumberArray(Number[] arr){
this.arr=arr;
}
int find(Number num){
return searchAlgorithm.find(arr,num);
}
public void setSearchAlgorithm(ISearchAlgorithm searchAlgorithm){
this.searchAlgorithm=searchAlgorithm;
}
@Override
public String toString() {
return "strategy_pattern.NumberArray{" +
"arr=" + Arrays.toString(arr) +
", searchAlgorithm=" + searchAlgorithm +
'}';
}
}
class IntegerArray extends NumberArray{
public IntegerArray(Integer[] arr,ISearchAlgorithm searchAlgorithm){
super(arr);
setSearchAlgorithm(searchAlgorithm);
}
}
class DoubleArray extends NumberArray{
public DoubleArray(Double[] arr,ISearchAlgorithm searchAlgorithm){
super(arr);
setSearchAlgorithm(searchAlgorithm);
}
}
StrategyPattern.java
package priv.mxz.design_pattern.strategy_pattern;
public class StrategyPattern {
public static void main(String[] args) {
IntegerArray integerArray=new IntegerArray(
new Integer[]{1,2,3,3,2,1},
new LeftSearchAlgorithm());
DoubleArray doubleArray=new DoubleArray(
new Double[]{1.0,2.0,3.0,3.0,2.0,1.0},
new LeftSearchAlgorithm());
System.out.println("strategy_pattern.IntegerArray: "+integerArray);
System.out.println("strategy_pattern.IntegerArray found 2 at index "+integerArray.find(2));
System.out.println("Change integerArray search algorithm to right");
integerArray.setSearchAlgorithm(new RightSearchAlgorithm());
System.out.println("strategy_pattern.IntegerArray: "+integerArray);
System.out.println("strategy_pattern.IntegerArray found 2 at index "+integerArray.find(2));
System.out.println("strategy_pattern.DoubleArray: "+doubleArray);
System.out.println("strategy_pattern.DoubleArray found 2 at index "+doubleArray.find(2.0));
}
}
StrategyPattern类中有执行入口,main函数中可以看到,初始化时integerArray和doubleArray的查找策略都设置为从头到尾查找,然后打印integerArray的信息和调用find方法查找2的位置,结果为1,即数组中第一个2的index。随后我们通过integerArray.setSearchAlgorithm(new RightSearchAlgorithm());修改integerArray的查找策略为从尾到头,打印信息和调用find方法查找2的位置,结果为4,即数组中第二个2的index。最后打印doubleArray的信息和调用find方法,发现doubleArray的查找策略不收integerArray的影响,不会因为IntegerArray策略而改变。
运行输出结果如下
strategy_pattern.IntegerArray: strategy_pattern.NumberArray{arr=[1, 2, 3, 3, 2, 1], searchAlgorithm=strategy_pattern.LeftSearchAlgorithm}
strategy_pattern.IntegerArray found 2 at index 1
Change integerArray search algorithm to right
strategy_pattern.IntegerArray: strategy_pattern.NumberArray{arr=[1, 2, 3, 3, 2, 1], searchAlgorithm=strategy_pattern.RightSearchAlgorithm}
strategy_pattern.IntegerArray found 2 at index 4
strategy_pattern.DoubleArray: strategy_pattern.NumberArray{arr=[1.0, 2.0, 3.0, 3.0, 2.0, 1.0], searchAlgorithm=strategy_pattern.LeftSearchAlgorithm}
strategy_pattern.DoubleArray found 2 at index 1
优缺点
在上述的例子中,如果不使用策略模式,采用硬编码把find函数的实现写入NumberArray或者它的子类中,则会有下面的问题
- 如果在NumberArray中实现find方法,那么所有的子类的find方法都默认是同一个find策略,我们假设使用从头到尾策略,除非子类重写覆盖find方法,那么如果有多个子类要使用从尾到头策略,这些子类都需要重写一份从尾到头策略,造成代码的重复。比如DoubleArray自己实现了从尾到头策略,此时若LongArray也要实现从尾到头策略,就需要把DoubleArray中的复制过来做修改,这样就造成了多份类似甚至相同的代码,如果NumberArray不实现find方法,则每个子类都要写自己的find方法,代码的重复度更高。
- 如果子类要修改查找策略,只能修改源代码,如果要实现在运行时改变查找策略,也要修改源代码,添加if else判断来确定查找策略,或者分别定义find和rfind。而是用策略模式只需要调用setSearchAlgorithm方法
所以,使用策略模式的优点如下:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择策略,也可以灵活地增加新策略。
- 策略模式提供了管理相关策略的办法。(例子中的setSearchAlgorithm)
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
策略模式的缺点如下:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。