跳转至

策略模式

分类:行为型模式。来源:《Head First 设计模式》第 1 章。

类结构

classDiagram
    direction TB
    class Client
    class Context {
        +setStrategy(Strategy strategy)
        +executeStrategy()
    }
    class Strategy {
        <<interface>>
        +algorithm()
    }
    class ConcreteStrategyA {
        +algorithm()
    }
    class ConcreteStrategyB {
        +algorithm()
    }
    Client ..> Context : 配置策略
    Client ..> ConcreteStrategyA : 选择
    Client ..> ConcreteStrategyB : 选择
    Context --> Strategy : 委托算法
    Strategy <|.. ConcreteStrategyA
    Strategy <|.. ConcreteStrategyB

角色定义

角色 定义
Client(客户) 根据需要选择具体策略,并将其配置到上下文。
Context(上下文) 持有策略接口引用,将可变化的算法步骤委托给策略对象。
Strategy(策略接口) 定义同一算法族的公共调用方式。
ConcreteStrategyA / ConcreteStrategyB(具体策略) 分别实现可相互替换的算法,供上下文选择和使用。

原书笔记

模拟鸭子应用

模拟鸭子游戏SimUDuck。游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。此系统内部设计使用了标准的OO技术。设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类。

鸭子的继承结构

classDiagram
    direction TB
    class Duck {
        <<abstract>>
        +quack()
        +swim()
        +display()*
    }
    class MallardDuck {
        +display()
    }
    class RedheadDuck {
        +display()
    }
    Duck <|-- MallardDuck
    Duck <|-- RedheadDuck
    note for MallardDuck "外观是绿头鸭"
    note for RedheadDuck "外观是红头鸭"

新需求让鸭子能飞,在超类中添加fly()方法,但是,并非Duck所有的子类都会飞,比如橡皮鸭,在超类中添加新的行为,会使得某些并不适合该行为的子类也具有该行为。

利用继承将橡皮鸭的fly()方法覆盖掉变成什么事都不做,如果以后加入诱饵鸭(DecoyDuck),诱饵鸭是木头鸭子,不会飞也不会叫,需要覆盖fly()方法和quack()方法。而且,当有新的鸭子子类出现,就要被迫检查并可能需要覆盖fly()和quark()。

所以,需要一个更清晰的方法,让某些鸭子可飞或可叫。

用接口区分鸭子的能力

classDiagram
    direction TB
    class Duck {
        <<abstract>>
        +swim()
        +display()*
    }
    class Flyable {
        <<interface>>
        +fly()
    }
    class Quackable {
        <<interface>>
        +quack()
    }
    class MallardDuck {
        +display()
        +fly()
        +quack()
    }
    class RedheadDuck {
        +display()
        +fly()
        +quack()
    }
    class RubberDuck {
        +display()
        +quack()
    }
    class DecoyDuck {
        +display()
    }
    Duck <|-- MallardDuck
    Duck <|-- RedheadDuck
    Duck <|-- RubberDuck
    Duck <|-- DecoyDuck
    Flyable <|.. MallardDuck
    Flyable <|.. RedheadDuck
    Quackable <|.. MallardDuck
    Quackable <|.. RedheadDuck
    Quackable <|.. RubberDuck

实线空心三角箭头表示继承,虚线空心三角箭头表示接口实现。图中只让会飞的绿头鸭、红头鸭实现 Flyable;橡皮鸭只实现鸣叫接口,诱饵鸭不实现这两个能力接口。

虽然Flyable与Quackable可以解决一部分问题,但是却造成**代码无法复用**。

使用继承并不能很好地解决问题。

**设计原则**找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

把鸭子的行为从Duck类中取出来。Duck类内的fly()和quack()会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

**设计原则**针对接口编程,而不是针对实现编程。

我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。

飞行行为

classDiagram
    direction TB
    class FlyBehavior {
        <<interface>>
        +fly()
    }
    class FlyWithWings {
        +fly()
    }
    class FlyNoWay {
        +fly()
    }
    FlyBehavior <|.. FlyWithWings
    FlyBehavior <|.. FlyNoWay
    note for FlyWithWings "实现鸭子飞行"
    note for FlyNoWay "什么都不做,不会飞"

鸣叫行为

classDiagram
    direction TB
    class QuackBehavior {
        <<interface>>
        +quack()
    }
    class Quack {
        +quack()
    }
    class Squeak {
        +quack()
    }
    class MuteQuack {
        +quack()
    }
    QuackBehavior <|.. Quack
    QuackBehavior <|.. Squeak
    QuackBehavior <|.. MuteQuack
    note for Quack "实现鸭子呱呱叫"
    note for Squeak "实现橡皮鸭吱吱叫"
    note for MuteQuack "什么都不做,不会叫"

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。

而我们可以新增一些行为,不会影响到既有的行为,也不会影响“使用”到飞行行为的鸭子类。

整合鸭子的行为

public abstract class Duck {
    QuackBehavior quackBehavior;
    FlyBehavior flyBehavior;
    public void performQuack(){
        quackBehavior.quack();
    }
    public void performFly(){
        flyBehavior.fly();
    }
    public void swim(){

    }
    public abstract display();
    public void swim(){
        System.out.println(" All ducks float,even decoys!");
    }
}
public interface FlyBehavior{
    public void fly();
}
public class FlyWithWings implements FlyBehavior{
    public void fly(){
        System.out.println("I'm flying!!");
    }
}

public class FlyNoWay implements FlyBehavior{
    public void fly(){
        System.out.println("I can't fly");
    }
}
public interface QuackBehavior{
    public void quack();
}
public class Quack implements QuackBehavior{
    public void quack(){
        System.out.println("Quack");
    }
}

public class MuteQuack implements QuackBehavior{
    public void quack(){
        System.out.println("<< Slience >>");
    }
}
public class Squeak implements QuackBehavior{
    public void quack(){
        System.out.println("Squeak");
    }
}

在子类中设定flyBehavior和quackBehavior。

public class MallardDuck extends Duck{
    public MallardDuck(){
        quackBehavior = new Quack();
        flyBehavior = new FlyBehavior();
    }

    public void display(){
        System.out.println("I'm a real Mallard duck");
    }
}
1
2
3
4
5
6
7
public class MiniDuckSimulator{
    public abstract void main(String[] args){
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();
    }
}

动态设定行为

1.在Duck类中,加入两个新方法:

1
2
3
4
5
6
public void setFlyBehavior(FlyBehavior fb){
    flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb){
    quackBehavior = qb;
}

2.制造一个新的鸭子类型:模型鸭(ModelDuck)

1
2
3
4
5
6
7
8
9
public class ModelDuck extends Duck {
    public ModelDuck(){
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }
    public void display(){
        System.out.println("I'm a model duck");
    }
}

3.建立一个新的FlyBehavior类型

1
2
3
4
5
public class FlyRocketPowered implements FlyBehavior {
    public void fly(){
        System.out.println("I'm flying with a rocket!");
    }
}

4.改变测试类,加上模型鸭,并使模型鸭具有火箭动力。

public class MiniDuckSimulator{
    public abstract void main(String[] args){
        Duck mallard = new MallardDuck();
        mallard.performQuack();
        mallard.performFly();
        Duck model = new ModelDuck();
        model.performFly();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

**设计原则**多用组合,少用继承。

**策略模式(Strategy Pattern)**定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

Head First 示例:模拟鸭子的可替换行为

第 1 章的 SimUDuck 把“鸭子是什么”与“怎样飞、怎样叫”分开。下面按作者 2020 年第二版配套源码,选取 ModelDuck 展示运行时替换飞行行为:它最初不会飞,随后可以换成火箭飞行。

图中保留模型鸭、两种飞行策略和一种鸣叫策略,省略其他鸭子与行为实现。Duck 同时持有两个独立的策略接口,performFly()、performQuack() 分别把工作委托给它们。

示例类型 模式角色 职责
Duck 上下文 持有飞行、鸣叫行为,并提供委托方法及行为替换入口。
ModelDuck 具体上下文 继承鸭子,构造时配置 FlyNoWay 与 Quack。
FlyBehavior / QuackBehavior 策略接口 分别定义飞行、鸣叫这两个算法族的调用方式。
FlyNoWay / FlyRocketPowered 具体飞行策略 分别表示不会飞、依靠火箭飞行。
Quack 具体鸣叫策略 实现鸭子的呱呱叫行为。

示例 UML

classDiagram
    direction TB
    class Duck {
        <<abstract>>
        FlyBehavior flyBehavior
        QuackBehavior quackBehavior
        +performFly()
        +performQuack()
        +setFlyBehavior(FlyBehavior fb)
        +setQuackBehavior(QuackBehavior qb)
        display()*
    }
    class ModelDuck {
        +display()
    }
    class FlyBehavior {
        <<interface>>
        +fly()
    }
    class FlyNoWay {
        +fly()
    }
    class FlyRocketPowered {
        +fly()
    }
    class QuackBehavior {
        <<interface>>
        +quack()
    }
    class Quack {
        +quack()
    }
    Duck <|-- ModelDuck
    Duck --> FlyBehavior : 委托飞行
    Duck --> QuackBehavior : 委托鸣叫
    FlyBehavior <|.. FlyNoWay
    FlyBehavior <|.. FlyRocketPowered
    QuackBehavior <|.. Quack

协作过程

  1. MiniDuckSimulator 创建 ModelDuck;模型鸭构造器安装初始行为。
  2. 第一次调用 performFly(),实际执行 FlyNoWay.fly()。
  3. 调用 setFlyBehavior(new FlyRocketPowered()) 替换策略引用。
  4. 再次调用同一个 performFly(),实际执行火箭飞行;鸣叫策略不受影响。

配套源码(2020 年第二版,固定版本):

评论