1、鸭子问题
有各种鸭子(比如野鸭、北京鸭、玩具鸭、水鸭等,鸭子有各种行为,比如叫、飞行等)
显示鸭子的信息
2、传统方案问题
-
其它鸭子,都继承了
Duck
类,所以fly让所有子类都会飞了,这是不正确的 -
上面说的1的问题,其实是继承带来的问题:对类的局部改动,尤其超类的局部改动,会影响其他部分。会有溢出效应
-
为了改进1问题,我们可以通过覆盖
fly
方法来解决=>覆盖解决 -
问题又来了,如果我们有一个玩具鸭子
ToyDuck
,这样就需要ToyDuck
去覆盖Duck
的所有实现的方法- 违反了里氏替换原则
- =>解决思路 策 略 模 式 \color{red}策略模式 策略模式
public class ToyDuck extends Duck {
@Override
public void display() {
System.out.println("玩具鸭");
}
//需要重写父类的所有方法
@Override
public void quack() {
System.out.println("玩具鸭不能叫");
}
@Override
public void swim() {
System.out.println("玩具鸭不会游泳");
}
@Override
public void fly() {
System.out.println("玩具鸭不能会飞翔");
}
}
3、基本介绍
-
策略模式(
Strategy Pattern
)中,定义算法族,分别封装起来,让他们之间可以互相替换,- 此模式让算法的变化独立于使用算法的客户
-
这算法体现了几个设计原则,
- 第一、把变化的代码从不变的代码中分离出来;
- 第二、针对接口编程而不是具体类(定义了策略接口);
- 第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
-
桥 接 调 整 属 性 , 策 略 调 整 行 为 \color{red}桥接调整属性,策略调整行为 桥接调整属性,策略调整行为
4、原理类图
说明:
- 可以看出,客户
context
拥有成员变量strategy
或者其他的策略接口strategyB
- 具体使用到哪个策略,我们可以再构造器中指定
5、案例解决
public class Client {
public static void main(String[] args) {
Duck wildDuck = new WildDuck();
wildDuck.setFlyBehavior(new GoodFlyBehavior());
wildDuck.fly();//飞翔技术高超
Duck pekingDuck = new PekingDuck();
pekingDuck.setFlyBehavior(new BadFlyBehavior());
pekingDuck.fly();//飞翔技术不行
//动态的改变某个对象的行为 北京鸭,不能飞翔
pekingDuck.setFlyBehavior(new NoFlyBehavior());
pekingDuck.fly();//不能飞翔
}
}
Duck
public abstract class Duck {
/**
* 属性,策略接口
*/
FlyBehavior flyBehavior;
//其他属性<->策略接口
/**
* 传入相应的fly行为,传入FlyBehavior 对象
*/
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
/**
* 显示鸭子的信息
*/
public abstract void display();
public void fly() {
if (null != flyBehavior) {
flyBehavior.fly();
}
}
}
WildDuck
public class WildDuck extends Duck {
public WildDuck() {
}
@Override
public void display() {
System.out.println("这是野鸭");
}
}
PekingDuck
public class PekingDuck extends Duck {
public PekingDuck() {
}
@Override
public void display() {
System.out.println("~北京鸭~");
}
}
FlyBehavior
public interface FlyBehavior {
void fly();
}
NoFlyBehavior
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("不能飞翔");
}
}
6、策略模式-Arrays
JDK
的Arrays
的Comparator
就使用到了策略模式
Comparator
策略接口
@FunctionalInterface
public interface Comparator<T> {
//...
int compare(T o1, T o2);//指定具体的处理方式
}
实现Comparator
接口来指定不同的方式(行为)进行比较
对于 INSERTIONSORT_THRESHOLD = 7
调整参数:列表大小等于或低于该大小,优先使用直接插入排序而不是归并排序。将在将来的版本中删除。
public class Arrays {
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c); //使用策略对象
else
//使用策略对象
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
//进入legacyMergeSort(a, c);
private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
T[] aux = a.clone();
if (c==null)//我们实现了比较器,null != c
mergeSort(aux, a, 0, a.length, 0);
else
//进入该方法(归并排序)
mergeSort(aux, a, 0, a.length, 0, c);
}
//mergeSort(aux, a, 0, a.length, 0, c);
private static void mergeSort(Object[] src, //原数组,即 a 克隆后
Object[] dest, //排序后的数组,即原先的a
int low, int high, int off,
Comparator c) {
int length = high - low;
//长度小于7,则使用直接插入排序,避免递归调用
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
//c.compare(dest[j-1], dest[j])>0 这里调用我们自己实现的compare 方法
for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// 递归地将 dest 的一半排序到 src 中
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1; //w
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.
//如果列表已经排序,只需从 src 复制到 dest。 这是一种优化,可加快对近序列表的排序速度。
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
//将排序的两半(现在在 src 中)合并到 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++];
}
}
}
归并排序示意图
7、注意事项与细节
-
策略模式的关键是:
- 分析项目中变化部公与不变部分
-
策略模式的核心思想是:
- 多用组合/聚合少用继承;
- 用行为类组合,而不是行为的继承。
- 更有弹性
-
体现了“对修改关闭,对扩展开放”原则,
- 客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,
- 避免了使用多重转移语句(
if..else if..else
)
-
提供了可以替换继承关系的办法:
- 策略模式将算法封装在独立的Strategy类中使得你可以独立于其
Context
改变它,使它易于切换、易于理解、易于扩展
- 策略模式将算法封装在独立的Strategy类中使得你可以独立于其
-
需要注意的是:
- 每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大