在将策略模式之前,我们先复习之前所讲的TreeSet中的一个构造方法:
public TreeSet(Comparator<? super E> comparator)
在这个构造方法中 参数comparator的类型是一个Comparator接口
java.util
实际上,这边返回的并不是一个接口,而是实现这个接口的实例。也就是自定义一个比较器,实现这个接口,将这个比较器的实例传递过去而已。这就是一个策略模式。Interface Comparator<T>
1. 策略模式(Strategy)
1) 策略模式(Strategy Pattern)中体现了两个非常基本的面向对象设计的原则
a. 封装变化的概念
b. 编程中使用接口,而不是对接口的实现
2) 面向接口的编程
3) 策略模式的定义
a. 定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
b. 策略模式使这些算法在客户端调用它们的时候能够互不影响地变化
4) 策略模式的意义
a. 策略模式使开发人员能够开发出由许多可替换的部分组成的软件,并且各个部分之间是弱连接的关系。
b. 弱连接的特性使软件具有更强的可扩展性,易于维护;更重要的是,它大大提高了软件的可重用性
5) 策略模式的组成
a. 抽象策略角色:策略类,通常由一个接口或者抽象类实现[例如 Comparator接口]
b. 具体策略角色:包装了相关的算法和行为 [例如 实现Comparator这个接口的实现类]
c. 环境角色:持有一个策略类的引用,最终给客户端调用的。[环境角色例如TreeSet,客户端调用这个构造方法的对象]
6) 策略模式的实现
a. 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
b. 策略模式使得算法可以在不影响到客户端的情况下发生变化。使用策略模式可以把行为和环境分割开来。
c. 环境类负责维持和查询行为类,各种算法则在具体策略中提供。由于算法和环境独立开来,算法的修改都不会影响环境和客户端
7) 策略模式的编写步骤
a.对策略对象定义一个公共接口。
b.编写策略类,该类实现了上面的公共接口
c.在使用策略对象的类中保存一个对策略对象的引用。
d.在使用策略对象的类中,实现对策略对象的set和get方法(注入)或者使用构造方法完成赋值
2. 参看 Collections类的源代码
Collections()私有构造方法,跟踪带排序sort()方法。public class Collections { // Suppresses default constructor, ensuring non-instantiability. private Collections() { }
跟踪Arrays中的sort()方法。public static <T> void sort(List<T> list, Comparator<? super T> c) { Object[] a = list.toArray(); //list转化为Object类型数组a Arrays.sort(a, (Comparator)c); //利用Arrays中的排序方法sort,其中c为实现排序规则的类的实例 ListIterator i = list.listIterator(); for (int j=0; j<a.length; j++) { i.next(); i.set(a[j]); } }
传递进来的c不为空,所以执行else语句,继续跟踪mergeSort()方法public static <T> void sort(T[] a, Comparator<? super T> c) { T[] aux = (T[])a.clone(); if (c==null) mergeSort(aux, a, 0, a.length, 0); else mergeSort(aux, a, 0, a.length, 0, c); }
搜索带参数 c 的执行语句private static void mergeSort(Object[] src, Object[] dest, int low, int high, int off, Comparator c) { int length = high - low; // Insertion sort on smallest arrays if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--) swap(dest, j, j-1); return; } // Recursively sort halves of dest into src int destLow = low; int destHigh = high; low += off; high += off; int mid = (low + high) >>> 1; mergeSort(dest, src, low, mid, -off, c); mergeSort(dest, src, mid, high, -off, c); // If list is already sorted, just copy from src to dest. This is an // optimization that results in faster sorts for nearly ordered lists. if (c.compare(src[mid-1], src[mid]) <= 0) { System.arraycopy(src, low, dest, destLow, length); return; } // Merge sorted halves (now in src) into dest for(int i = destLow, p = low, q = mid; i < destHigh; i++) { if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0) dest[i] = src[p++]; else dest[i] = src[q++]; } }
for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
swap(dest, j, j-1);
从这里可以知道,compare方法会在我们实现接口的类中重写比较器compare()方法,所以这边使用的是我们自己定义的compare()方法。所以最终比较的还是使用自己的比较规则。这边就引用策略模式
3. 实现自己的策略模式
使用策略模式实现加减乘除的操作,加减乘除都是计算,但是具体完成加法还是减法或者其他方法都是定义在具体的策略类里面,用户想要完成什么功能,我们就穿给他什么策略,这就是实现一个简单的策略模式。
1) 定义抽象策略角色:算法策略
package com.ahuier3; /* * 抽象策略角色 * 定义一个抽象的策略类 */ public interface Strategy { public int calculate(int a, int b); }
2) 定义具体策略角色:加法策略类实现算法策略package com.ahuier3; /* * 具体策略角色 * 定义加法的具体类实现算法的抽象类接口 */ public class AddStrategy implements Strategy { public int calculate(int a, int b) { return a + b; } }
定义具体策略角色:减法策略类实现算法策略package com.ahuier3; /* * 具体策略角色 * 定义减法的具体类实现算法的抽象类接口 */ public class SubtractStrategy implements Strategy { public int calculate(int a, int b) { return a - b; } }
定义具体策略角色:乘法策略类实现算法策略
package com.ahuier3; /* * 具体策略角色 * 定义乘法的具体类实现算法的抽象类接口 */ public class MultiplyStrategy implements Strategy { public int calculate(int a, int b) { return a * b; } }
定义具体策略角色:除法策略类实现算法策略package com.ahuier3; /* * 具体策略角色 * 定义除法的具体类实现算法的抽象类接口 */ public class DivideStrategy implements Strategy { public int calculate(int a, int b) { return a / b; } }
3) 定义环境角色package com.ahuier3; public class Environment { private Strategy strategy; //定义的类型是一个抽象类型,这样才能传具体的算法,加减乘除. public Environment(Strategy strategy){ this.strategy = strategy; } //提供一个Set方法,可以改变将指定的策略传递进去,使用完成之后可以还可以换一种策略。 public void setStrategy(Strategy strategy){ this.strategy = strategy; } public Strategy getStrategy(){ return this.strategy; } /* * 这边的到底使用何种算法,是在这里完成的,通过传递strategy参数来决定到底使用何种算法。加减乘除 */ public int calculate(int a, int b){ return strategy.calculate(a, b); //不管calculate()方法是做什么的,传什么就用什么方法。 } }
4) 定义客户端,根据某种情况调用这些策略package com.ahuier3; /* * 客户端,调用那些策略 * 做为客户端,首先它是明确知道具体使用何种策略,才会去调这些策略 */ public class Client { public static void main(String[] args) { //传递加法策略,实现加法操作 AddStrategy addStrategy = new AddStrategy(); Environment environment = new Environment(addStrategy); System.out.println(environment.calculate(3, 4)); //传递减法策略,实现减法操作,这边使用了set方法将其策略直接转为减法策略,而不需要再去new这个环境了。 SubtractStrategy subtractStrategy = new SubtractStrategy(); environment.setStrategy(subtractStrategy); System.out.println(environment.calculate(3, 4)); //传递乘法策略,实现减法操作,这边使用了set方法将其策略直接转为减法策略,而不需要再去new这个环境了。 MultiplyStrategy multiplyStrategy = new MultiplyStrategy(); environment.setStrategy(multiplyStrategy); System.out.println(environment.calculate(3, 4)); //传递除法策略,实现减法操作,这边使用了set方法将其策略直接转为减法策略,而不需要再去new这个环境了。 DivideStrategy divideStrategy = new DivideStrategy(); environment.setStrategy(divideStrategy); System.out.println(environment.calculate(3, 4)); } }
4. 在Environment类中,getStrategy 和 setStrategy 这种写法在实际开发中是很常见的,所以Eclipse中有提供这样的功能把我们自动生成getter和setter方法,比较两种的不同:
Eclipse帮我们自动生成的方法,步骤 source --> Generate Getters and Setters -->选择自己想要设置的成员变量(属性),点击ok即可,如下图所示,及其生成的代码:
图51-2
Eclipse生成代码如下:
【说明】:如果这边的成员变量(例如:Flag)有一个类型是boolean类型的话,那生成的有可能是isFlag()和setFlag(),但是这边有getFlag()与isFlag()都是可以的。public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; }
5. 在环境角色Environment类中,我们想要查看 calculate(a, b); 算法是查不到真正具体实现类的算法的,真正具体实现的算法是通过传递何种策略的这个引用来决定的,但是再Eclipse中我们可以查看到有哪些策略,方法是: 将光标放在 calculate(a, b); 上,按Ctrl + T ,Eclipse会自动列出所有实现方法的具体是类。这在实际开发中很常见。
6. 策略模式的缺点
a.客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
b.造成很多的策略类。
解决方案:采用工厂方法
[这是一种设计模式:工厂模式 工厂方法有好多种,具体的有简单工厂方法,抽象工厂方法]
7. 作业
有这样一个类:
public class Person{
private int id;
private String name;
private int age;
}
// getter and setter
要求:假如有若干个类Person对象存在一个List当中,对他们进行排序,分别按照名字、年龄、id进行排序(要有正序与倒序两种排序方式)。假如年龄或者姓名重复,按照id的正序进行排序。要求使用策略模式进行。