策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换。当对象有某个行为,但是在不同的场景中该行为有不同的实现算法,这时便可以使用策略模式这一设计模式。
假设我们有一个排序器,使用简单的插入排序实现对int类型的数组进行排序,代码如下所示:
package com.rul.designpattern.strategy;
import java.util.Arrays;
/**
* 简单插入排序排序器
*
* @author LuoRu
*/
public class Sorter01 {
public void sort(int[] array) {
for (int i = 1; i < array.length; i++) {
int temp = array[i];
int j = i;
while (j > 0 && temp < array[j - 1]) {
//元素向后移动
array[j] = array[j - 1];
j--;
}
//插入元素
array[j] = temp;
}
}
public static void main(String[] args) {
int[] array = new int[]{2, 1, 5, 3, 4};
Sorter01 sorter = new Sorter01();
sorter.sort(array);
System.out.println(Arrays.toString(array));
}
}
如果某一天我们想要对double类型数组排序怎么办、想对自定义类型数组排序怎么办?直接往排序器里面加sort方法,实现很多的重载方法?这样每当我们想要对一种新类型进行排序,就需要新写一个重载方法,最终会使代码变得特别臃肿。我们可以这样实现:
package com.rul.designpattern.strategy;
/**
* 规定每个需要使用Sorter02进行排序的类必须实现Comparable接口
*
* @param 实现类的类型
* @author LuoRu
*/
@FunctionalInterface
public interface Comparable<T> {
int compareTo(T o);
}
我们设计一个Comparable接口,包含一个compareTo方法,该方法用来实现两个对象比较大小的策略。同时我们规定想要使用排序器进行排序的类型必须实现该接口。如下,我们定义了Cat类型,并实现了Comparable接口,实现的compareTo方法表示age小的Cat小。
package com.rul.designpattern.strategy;
/**
* 自定义类型,我们想要通过排序器对该类型的数组进行排序
*
* @author LuoRu
*/
public class Cat implements Comparable<Cat> {
int age;
int weight;
public Cat(int age, int weight) {
this.age = age;
this.weight = weight;
}
@Override
public int compareTo(Cat o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Cat{" +
"age=" + age +
", weight=" + weight +
'}';
}
}
然后我们便可以将排序器的代码改成下面这样,就可以对所有实现了Comparable接口的类型的数组进行排序了。
package com.rul.designpattern.strategy;
import java.util.Arrays;
/**
* 对实现了Comparable接口的对象进行排序
*
* @author LuoRu
*/
public class Sorter02 {
public void sort(Comparable[] array) {
for (int i = 1; i < array.length; i++) {
Comparable temp = array[i];
int j = i;
while (j > 0 && temp.compareTo(array[j - 1]) < 0) {
//元素向后移动
array[j] = array[j - 1];
j--;
}
//插入元素
array[j] = temp;
}
}
public static void main(String[] args) {
Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1),
new Cat(4, 4), new Cat(5, 5), new Cat(2, 2)};
Sorter02 sorter = new Sorter02();
sorter.sort(cats);
System.out.println(Arrays.toString(cats));
}
}
但是有一天,我们又突然想对Cat类型的数组按照weight而不是age进行排序了,怎么办?最容易想到的办法就是修改Cat中的compareTo方法的实现,但是这样违反了软件设计中的”开闭原则–对拓展开放,对修改关闭“,而且这样不能使两种排序策略在程序中同时存在,也就是说不能在同一程序中某处按照age排序,而在另外一处则使用weight排序。
我们可以通过策略模式很简单地实现前面的需求。首先定义一个泛型接口,compare规定两个对象比较大小的策略。
package com.rul.designpattern.strategy;
/**
* 比较策略接口
*
* @param 对需要进行比较对象的类型
* @author LuoRu
*/
@FunctionalInterface
public interface Comparator<T> {
int compare(T t1, T t2);
}
我们将Comparator实现类作为参数传入sort方法,这样每当我们想要按照新的排序规则进行排序的时候,只需要新创建一个Comparator接口的实现类就可以了。
package com.rul.designpattern.strategy;
import java.util.Arrays;
/**
* 将Comparator接口作为排序器的sort方法的参数
*
* @param 需排序的对象数组的类型
* @author LuoRu
*/
public class Sorter03<T> {
public void sort(T[] array, Comparator<T> comparator) {
for (int i = 1; i < array.length; i++) {
T temp = array[i];
int j = i;
while (j > 0 && comparator.compare(temp, array[j - 1]) < 0) {
//元素向后移动
array[j] = array[j - 1];
j--;
}
//插入元素
array[j] = temp;
}
}
public static void main(String[] args) {
Cat[] cats = new Cat[]{new Cat(3, 3), new Cat(1, 1),
new Cat(4, 4), new Cat(5, 5), new Cat(2, 2)};
Sorter03<Cat> sorter = new Sorter03<>();
sorter.sort(cats, ((t1, t2) -> t1.age - t2.age));
System.out.println("按照age从小到大排序:" + Arrays.toString(cats));
sorter.sort(cats, ((t1, t2) -> t2.weight - t1.weight));
System.out.println("按照weight从大到小排序:" + Arrays.toString(cats));
}
}
Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。对应上面实例中的Sorter03。
抽象策略角色,是对策略、算法的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。对应上面实例中的Comparator接口。
具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。对应上面实例中的Sorter03中main方法中的Lambda表达式。