策略模式¶
分类:行为型模式。来源:《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 "什么都不做,不会叫"
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。
而我们可以新增一些行为,不会影响到既有的行为,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为¶
在子类中设定flyBehavior和quackBehavior。
动态设定行为¶
1.在Duck类中,加入两个新方法:
2.制造一个新的鸭子类型:模型鸭(ModelDuck)
3.建立一个新的FlyBehavior类型
4.改变测试类,加上模型鸭,并使模型鸭具有火箭动力。
**设计原则**多用组合,少用继承。
**策略模式(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
协作过程¶
MiniDuckSimulator创建ModelDuck;模型鸭构造器安装初始行为。- 第一次调用
performFly(),实际执行FlyNoWay.fly()。 - 调用
setFlyBehavior(new FlyRocketPowered())替换策略引用。 - 再次调用同一个
performFly(),实际执行火箭飞行;鸣叫策略不受影响。
配套源码(2020 年第二版,固定版本):