一、策略模式定义
《Head First Design Patterns》:The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them
interchangeable. Strategy lets the algorithm vary independently from clients that use it.
即:策略模式定义了算法族,把算法族封装起来,使得它们之间可以切换。策略让算法独立于使用策略模式的客户端而变化。
简单说,就是可以来回切换算法。
二、策略模式案例
案例:有父类鸭子,子类红头鸭、绿头鸭......实现鸭子的继承树,所有鸭子都会游泳,部分鸭子会飞,部分鸭子会叫。
版本一:单用继承
public abstract class Duck {
public void quack() {
System.out.println("鸭子叫!");
}
public void swim() {
System.out.println("鸭子游泳!");
}
public void fly() {
System.out.println("鸭子飞!");
}
public abstract void display();
}
//绿头鸭
class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("绿头鸭");
}
}
//红头鸭
class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("红头鸭");
}
}
分析:并不是所有鸭子都会飞,如家鸭,也并不是所有鸭子都会叫,如大黄鸭、小黄鸭。因此,单用继承不满足条件。
版本二:封装变化(面向对象设计原则一),将易变的部分从不变的地方分离出来,即将飞(Flyable)和叫(Quackable)作为接口从Duck类中分离出来,即面向接口编程(面向对象设计原则二),使得只有会飞的鸭子才实现Flyable接口,只有会叫的鸭子才实现Quackable接口。
public abstract class Duck {
public void swim() {
System.out.println("鸭子游泳!");
}
public abstract void display();
}
//飞的行为,接口
interface Flyable {
void fly();
}
//叫的行为,接口
interface Quackable {
void quack();
}
//水鸭子可以飞、游泳、叫
class WaterDuck extends Duck implements Flyable,Quackable {
@Override
public void quack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void display() {
// TODO Auto-generated method stub
}
}
//普通鸭子会游泳和叫
class NormalDuck extends Duck implements Quackable {
@Override
public void quack() {
// TODO Auto-generated method stub
}
@Override
public void display() {
// TODO Auto-generated method stub
}
}
分析:通过将飞的行为和叫的行为分离成接口,只有会飞的鸭子才实现飞的接口,只有会叫的鸭子才实现叫的接口,解决了“不是所有鸭子都会飞,不是所有鸭子都会叫”的问题。
然而,又遇到了新的问题,每只会飞的鸭子都要实现fly()方法,每只会叫的鸭子都要实现quack()方法,假设有两只鸭子都会飞,并且飞行方法都一样,但它们都要实现fly()方法,就出现代码的重复,也就是代码复用性不高,其次,若两只会飞的鸭子方法需要发生改变时,那么要修改多处,不能做到“一改全改”,改变时也容易引入新的bug。
版本三:创建Flyable模板(即实现该接口),创建Quackable模板,在Duck类中持有Flyable接口和Quackable接口的引用(面向对象原则三:组合优于继承),同时有set方法来更换飞行方式和叫的方式,set方法就是策略模式的核心,它可以实现飞行方式的切换、叫的方式切换。
public abstract class Duck {
// 持有飞行行为的引用
private Flyable fly = null;
// 持有叫的行为的引用
private Quackable quack = null;
//构造方法,子类必须调用该方法
public Duck(Flyable f, Quackable q){
fly=f;
quack=q;
}
// 改变飞行行为
public void setFly(Flyable f) {
fly = f;
}
// 改变叫的行为
public void setQuack(Quackable q) {
quack = q;
}
public void fly() {
fly.fly();
}
public void quack() {
quack.quack();
}
public void swim() {
System.out.println("鸭子游泳!");
}
public abstract void display();
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
duck.swim();
duck.fly();
duck.quack();
duck.display();
//set方法使得程序在运行期间,可以切换行为
System.out.println("改变飞的行为、叫的行为后:");
duck.setFly(new RocketPoweredFly());//改变飞的行为
duck.setQuack(new LowVoiceQuack());//改变叫的行为
duck.fly();
duck.quack();
}
}
//飞行接口
interface Flyable {
void fly();
}
//不会飞
class FlyNoWay implements Flyable {
@Override
public void fly() {
// do nothing
}
}
//低空飞行
class LowHeightFly implements Flyable {
@Override
public void fly() {
System.out.println("低空飞");
}
}
//高空飞行
class HighHeightFly implements Flyable {
@Override
public void fly() {
System.out.println("高空飞");
}
}
//火箭式飞行
class RocketPoweredFly implements Flyable {
@Override
public void fly() {
System.out.println("火箭升空");
}
}
//叫的行为
interface Quackable {
void quack();
}
//不会叫
class QuackNoWay implements Quackable {
@Override
public void quack() {
// do nothing
}
}
//尖叫
class ScreamQuack implements Quackable {
@Override
public void quack() {
System.out.println("尖叫嘎嘎");
}
}
//低声叫
class LowVoiceQuack implements Quackable {
@Override
public void quack() {
System.out.println("低声嘎嘎");
}
}
//绿头鸭
class MallardDuck extends Duck {
public MallardDuck() {
//高空飞,尖叫
super(new HighHeightFly(),new ScreamQuack());
}
@Override
public void display() {
System.out.println("绿头鸭");
}
}
//红头鸭
class RedHeadDuck extends Duck {
public RedHeadDuck(){
//不会飞,尖叫嘎嘎
super(new FlyNoWay(), new ScreamQuack());
}
@Override
public void display() {
System.out.println("红头鸭");
}
}
类图如下:
分析:
- 将容易变化的部分即飞行和叫行为从不变Duck类中分离出来做成接口(封装变化);
- 飞行和叫有多个不同的实现,以增强代码的复用性(面向接口编程,提供模板以增加复用);
- Duck父类持有飞行和叫行为的引用,即使用组合(组合优于继承),同时提供setter方法,以实现不同行为的切换。
策略模式的应用场景
策略模式可以实现不同算法之间的来回切换,应用场景如,在游戏中,玩家可以任意切换武器或其他装备;在旅行过程中,可以中途换一种交通工具,公交-火车-飞机-taxi。总之,运行过程中,切换算法,就用策略模式,策略模式,用了的都说好(广告^v^)。