第二种: Strategy - 策略模式
设计模式真的很不错!
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
下面我们看一个简单的例子,来引出策略模式。
1. 引例
package strategy
public class Sorter { //这里有一个排序类,采用的是选择排序算法
public void sort(int[] arr) {
for(int i=0; i<arr.length-1; i++){
int minPos = i;
for(int j=i+1; j<arr.length; j++){
minPos = arr[j] < arr[minPos]? j:minPos;
}
swap(arr,i,minPos);
}
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public class Main {
public static void main(String[] args) {
int [] a ={9,2,3,5,7,1,4};
Sorter sorter = new Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
//输出排序结果
}
}
上面是针对的是 int 数组的一个排序,且输出正确。
那么问题来了,如果我想对一个 double 数组排序,或者 float 数组排序,我们是不是要再写一遍这个排序算法呢?
2. 思考一下,如果我们要对下面这个 Cat 排序呢?
public class Cat {
int weight,height;
public Cat(int weight, int height){
this.weight = weight;
this.height = height;
}
}
对猫排序也很简单,需要两步:
第一步,定义猫之间怎么比较大小。第二步,把对外的 int 数组改成 Cat 。
因此我们修改上面的代码:
public class Cat {
int weight,height;
public Cat(int weight, int height){
this.weight = weight;
this.height = height;
}
public int compareTo(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 "Cat [weight=" + weight + ", height=" + height + "]";
}
}
public class Sorter {
public void sort(Cat[] arr) {//把的 int 数组改成 Cat 类型 。
for(int i=0; i<arr.length-1; i++){
int minPos = i;
for(int j=i+1; j<arr.length; j++){
minPos = arr[j].compareTo(arr[minPos])==-1 ? j:minPos;
}
swap(arr,i,minPos);
}
}
private static void swap(Cat[] arr, int i, int j) {
Cat temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public class Main {
public static void main(String[] args) {
//int [] a ={9,2,3,5,7,1,4};
Cat[] a ={new Cat(3,3), new Cat(8,8),new Cat(6,6),new Cat(1,1)};
Sorter sorter = new Sorter();
sorter.sort(a);
System.out.println(Arrays.toString(a));
}
}
来测试一下,生成4个Cat来比较大小。
结果正确!
3. 继续思考,如果我们要对其他动物,对车,对人进行排序呢?
因此我们不能把这个类型写死,那我们这里把它写成 Comparable ,Comparable方法里写上这个compareTo的方法。我们就可以写一个包含compareTo方法的Comparable的接口,不管对什么排序,只要实现这个接口,我们就可以轻易做到对其进行比较。如下:
public interface Comparable<T> {//这里使用泛型更简单
public int compareTo(T o);
}
public class Dog implements Comparable<Dog>{//比较狗的大小
int food;
public Dog(int food){
this.food = food;
}
public int compareTo(Dog d){//吃得少的在前面
if(this.food < d.food) return -1;
else if(this.food > d.food) return 0;
else return 0;
}
@Override
public String toString() {
return "Dog [food=" + food + "]";
}
}
测试也是ok的:
4. 这才刚刚开始有点策略的意思!
我们的策略仅仅是实现了对任何东西进行排序,但是还是不够灵活,拿下面这个Cat类为例:如果我们想改便对猫的比较策略,想对猫的身高排序怎么办?如果我们直接更改compareTo方法,显然违反了OCP(开闭原则:对扩展开放,对修改关闭)。
public class Cat implements Comparable<Cat>{
int weight,height;
public Cat(int weight, int height){
this.weight = weight;
this.height = height;
}
public int compareTo(Cat c){
if(this.weight < c.weight) return -1;
else if(this.weight > c.weight) return 1;
else return 0;
}
看来这个comparable不能满足我们的需求了,我们这时需要一个comparator比较器。更改sort方法,不仅传递进来一个数组,还要加上这个比较器来表明我们的比较策略。现在我们可以有CatWeightComparator和CatHeightComparator等等多个策略,需要哪个传哪个,会非常方便!
如下:
public interface Comparator<T> {
int compara(T o1,T o2);
}
public class CatWeightComparator implements Comparator<Cat>{
@Override
public int compara(Cat o1, Cat o2) {//体重轻的在前面
if(o1.weight < o2.weight) return -1;
else if(o1.weight > o2.weight) return 1;
else return 0;
}
}
public class CatHeightComparator implements Comparator<Cat>{
@Override
public int compara(Cat o1, Cat o2) {//个高的在前面
if(o1.height > o2.height) return -1;
else if(o1.height < o2.height) return 1;
else return 0;
}
}
public class Sorter<T> {
public void sort(T[] arr, Comparator<T> comparator) {
for(int i=0; i<arr.length-1; i++){
int minPos = i;
for(int j=i+1; j<arr.length; j++){
minPos = comparator.compara(arr[j],arr[minPos])==-1 ? j:minPos;
}
swap(arr,i,minPos);
}
}
private void swap(T[] arr, int i, int j) {
T temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
public class Main {
public static void main(String[] args) {
//int [] a ={9,2,3,5,7,1,4};
Cat[] a ={new Cat(3,3), new Cat(8,8),new Cat(6,6),new Cat(1,1)};
//Dog[] a ={new Dog(3),new Dog(5),new Dog(1),new Dog(2)};
Sorter<Cat> sorter = new Sorter<Cat>();
sorter.sort(a, new CatHeightComparator());//选择高度的比较器
System.out.println(Arrays.toString(a));
}
}
测试一下ok的:
5. 类图:
6. 策略模式的应用场景及优缺点
应用场景: (例子是:鸭子具有飞和叫的行为,不同的鸭子飞和叫的行为不同,来源于《head first 设计模式》)
- 多个类只区别在 表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。(例如FlyBehavior和QuackBehavior)
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如FlyBehavior和QuackBehavior的具体实现可任意变化或扩充)
- 对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。
优点:
- 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。
- 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
- 遵守大部分GRASP原则和常用设计原则, 高内聚、低偶合。
缺点:
因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。