什么是策略模式?

接下来,我们通过之前复习过的排序算法 来谈谈策略模式

选择排序为例,之前我们写的排序算法, 数据类型写的 Integer 类型的数组.

代码如下 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 交换元素
public void swap(int[] nums, int i1, int i2){
    int tmp = nums[i1];
    nums[i1] = nums[i2];
    nums[i2] = nums[i1];
}

// 选择排序
public void selectionSort(int[] nums){
    for (int i = nums.length - 1; i > 0; i--){
        int maxIdx = 0;
        for (int j = 1; j <= i; j++) {
            if (nums[j] > nums[maxIdx]){
                maxIdx = j;
            }
        }
        swap(nums, maxIdx, i);
    }
}

需求变化

这时,如果我们要排序 double 类型的数组呢?

这有啥难的? 直接把上述代码拷贝一份,类型换成double呗。反正Java支持函数重载,确实可以做得到。

但是有个问题,如果有大量的需要比较的类型,甚至包括自定义对象, 岂不是每一种对象都得copy一份方法, 明显不符合开闭原则 (对扩展开放,对修改闭合),因为我们没增加一个比较类,就需要对 selectionSort类修改。

改进

如何解决上边提到的问题?

我们声明一个Comparable接口,接口中有一个 compareTo(T o) 方法,为了能兼容各种类型的对象,我们使用范型.

并要求使用 selectionSort 排序的对象,必须实现 compareTo(T o) 方法.

这时,我们实现多种对象的排序上, 就会方便很多了.

代码如下 :

接口代码 :

1
2
3
4
// 接口
public interface Comparable<T>{
    int compareTo(T o);
}

选择排序代码 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 交换元素
public void swap(Comparable[] nums, int i1, int i2){
    Comparable tmp = nums[i1];
    nums[i1] = nums[i2];
    nums[i2] = tmp;
}

// 选择排序
public void selectionSort(Comparable[] nums){
    for (int i = nums.length - 1; i > 0; i--){
        int maxIdx = 0;
        for (int j = 1; j <= i; j++) {
            if (nums[j].compareTo(nums[maxIdx]) > 0){
                maxIdx = j;
            }
        }
        swap(nums,maxIdx, i);
    }
}

需要排序的对象类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Dog implements Comparable<Dog>{
    int weight;
    public Dog(int weight) {
        this.weight = weight;
    }

    @Override
    public int compareTo(Dog o) {
        return this.weight - o.weight;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "weight=" + weight +
                '}';
    }
}

这样我们就限制了,传入的需要排序的类,必须实现 Comparable接口,并且使用 Comparable接口中的 int compareTo(T o); 方法来比较排序对象。

仍然不够灵活

但是上边这种做法,依然不够灵活. Why? 因为比如 Cat 对象中,有两个属性 height, age, 我们有时候需要用 height 排序,有时候使用 age 来排序.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Cat implements Comparable<Cat>{

    int height;
    int age;

    public Cat(int height, int age) {
        this.height = height;
        this.age = age;
    }

    @Override
    public int compareTo(Cat o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "height=" + height +
                ", age=" + age +
                '}';
    }
}

如果采用上述 Comparable 方法, 我们没办法实现使用 height 排序。

继续改进

接下来正式引入我们的主角,策略模式Comparator接口

策略模式符合设计模式的开闭原则 : 对修改关闭,对扩展开放

我们依然定义一个接口 Comparator接口,并且要求比较的类实现compare方法

代码如下 :

接口代码 :

1
2
3
public interface Comparator <T>{
    int compare(T o1, T o2);
}

选择排序代码 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 交换元素
public void swap(T[] nums, int i1, int i2){
    T tmp = nums[i1];
    nums[i1] = nums[i2];
    nums[i2] = tmp;
}

// 选择排序
public void selectionSort(T[] nums, Comparator<T> comparator){
    for (int i = nums.length - 1; i > 0; i--){
        int maxIdx = 0;
        for (int j = 1; j <= i; j++) {
            if (comparator.compare(nums[j], nums[maxIdx]) > 0){
                maxIdx = j;
            }
        }
        swap(nums,maxIdx, i);
    }
}

比较代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SelectionSort sort = new SelectionSort();
Cat[] cats = {new Cat(39,1),new Cat(20,4), new Cat(10,2)};
sort.selectionSort(cats, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
        Cat c1 = (Cat)o1;
        Cat c2 = (Cat)o2;
        return c1.age - c2.age;
    }
});

排序策略

总结 :

  • 以上就是策略设计模式,在排序中的应用
  • 从上到下的优化过程,可以明显的看出,从上到下依次更容易拓展
    • 第一种,增加排序类型时,只能在 SelectionSort 类中增加代码. 非常不方便
    • 第二种,我们在每个类内部实现 ComparablecompareTo方法
      • 使得增加一种支持排序的类时,只需要将此类实现 Comparable 接口指定方法即可
      • 而如果没有实现 compareto方法,就无法使用我们的 SelectionSort方法
    • 第三种,第二种方法相比第一种来说,易扩展很多。
      • 但是仍然有局限性,就是每个类只支持一种排序方法
      • 当我们分别需要对 Cat 对象,用 age 排序,再用 weight 排序。
      • 第二种就做不到了
      • 所以第三种我们使用 比较器 Comparator, SelectionSort 排序方法中,增加一个传参数,传入一个比较器。实现对应比较方法即可

主要解决的问题

  • 在有多种算法相似的情况下,使用 if…else 或者 switch…case 所带来的复杂和难以维护

  • 优点:

    • 1、算法可以自由切换。
    • 2、避免使用多重条件判断。
    • 3、扩展性良好。

    缺点:

    • 1、策略类会增多。
    • 2、所有策略类都需要对外暴露。