一.引例
编写一个展示各种鸭子信息的项目,具体要求如下:
- 有各种鸭子——野鸭、水鸭、玩具鸭
- 鸭子有各种技能——叫、游泳、飞行
- 野鸭上述三个技能都会,水鸭不会飞行,玩具鸭不会游泳、飞行
- 要求输出各种鸭子技能的掌握情况
二、传统方案实现
传统的设计方案:构建一个抽象父类 Duck,并让各个子类(即各种鸭子)去继承Duck
代码演示:
// 抽象父类
public abstract class Duck {
public Duck() {
}
public abstract void display(); // 输出鸭子信息
public void quack(){
System.out.println("鸭子会叫!!!");
}
public void swim(){
System.out.println("鸭子会游泳!!!");
}
public void fly(){
System.out.println("鸭子会飞!!!");
}
}
public class WildDuck extends Duck{
@Override
public void display() {
System.out.println("野鸭"); // 野鸭三个技能都会,因此不需要重写父类的三个方法
}
}
public class Teal extends Duck{
@Override
public void display() {
System.out.println("水鸭");
quack();
swim();
fly();
}
@Override
public void fly() {
System.out.println("鸭子不会飞!!!");
}
}
public class ToyDuck extends Duck{
@Override
public void display() {
System.out.println("玩具鸭");
quack();
swim();
fly();
}
@Override
public void swim() {
System.out.println("鸭子不会游泳");
}
@Override
public void fly() {
System.out.println("鸭子不会飞");
}
}
客户端使用:
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.display();
Teal teal = new Teal();
teal.display();
ToyDuck toyDuck = new ToyDuck();
toyDuck.display();
}
}
三、传统方案的问题分析
- 各种鸭子都继承了Duck类,所以fly()让所有鸭子都会飞,这是不正确的。
- 上面的问题是继承带来的,我们可以通过重写fly()来解决。
- 但如果又有一个木头鸭,就需要重写所有Duck类方法。
- 解决思路:策略模式
四、策略模式基本介绍
-
策略模式中,定义算法簇(策略组),分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
-
体现的设计原则:
1. 把变化的代码从不变的代码中分离出来;
2. 针对接口编程而不是具体类(定义了策略接口);
3. 多用组合/聚合,少用继承(客户端通过组合方式使用策略); -
策略模式的原理类图
五、策略模式解决鸭子问题
思路分析:分别封装行为接口,实现算法簇,超类里放行为接口对象,在子类里具体设定行为对象。原则是:分离变化部分,封装接口,基于接口编程各种功能。此模式让行为的变化独立于算法的使用者。
代码演示,仅演示飞行功能,其余功能类似。
public interface FlyBehavior {
void fly(); // 子类具体实现
}
public class GoodFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞行技术高超");
}
}
public class BadFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("飞行技术一般般");
}
}
public class NoFlyBehavior implements FlyBehavior{
@Override
public void fly() {
System.out.println("不会飞行");
}
}
public abstract class Duck {
//属性,策略接口
FlyBehavior flyBehavior;
public Duck() {
}
public abstract void display();
public void fly(){
try {
flyBehavior.fly();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
public class WildDuck extends Duck{
public WildDuck(){
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println("野鸭");
flyBehavior.fly();
}
}
public class ToyDuck extends Duck{
public ToyDuck(){
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
System.out.println("玩具鸭");
flyBehavior.fly();
}
}
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.display();
ToyDuck toyDuck = new ToyDuck();
toyDuck.display();
// 动态改变某个对象的行为
toyDuck.setFlyBehavior(new BadFlyBehavior());
toyDuck.display();
}
}
野鸭
飞行技术高超
玩具鸭
不会飞行
玩具鸭
飞行技术一般般
六、策略模式在JDK-Arrays中的应用
- JDK的Arrays的Comparator使用了策略模式
- 代码演示:
public class ArraysOfSort {
public static void main(String[] args) {
Integer[] data = {5, 3, 6, 1, 8};
System.out.println(Arrays.toString(data));
// 采用升序策略
Arrays.sort(data, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
System.out.println(Arrays.toString(data));
// 采用降序策略
Arrays.sort(data, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(data));
}
}
[5, 3, 6, 1, 8]
[1, 3, 5, 6, 8]
[8, 6, 5, 3, 1]
Arrays.sort()源码
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);
}
}
七、策略模式总结
- 策略模式的关键是:分析项目中变化部分与不变部分
- 核心思想是:多用组合、聚合,少用继承;用行为类组合,而不是行为的继承,这样更有弹性。
- 体现了“对修改关闭,对扩展开放的原则”,客户端增加行为不需要修改原有代码,只需要添加一种策略即可,避免了使用if else if else…语句
- 提供了可以替换继承关系的办法:策略模式将算法封装在独立的strategy类中,使得独立于context改变它,易于切换、理解、扩展
- 注意:每添加一个策略就要增加一个类,当策略过多时,会导致类的数目庞大