模式补全
这一节按 八面剖析以理解一个概念 / ljg-learn 的思路整理为 Markdown 版本:先定锚,再用八个方向切开概念,最后压缩成公式、例子、类图和检验题。
定锚
策略模式(行为型)的通行定义:定义一组可互换算法,把它们封装起来,让客户端在运行时选择。
常见误解:策略不是把每个 if 都拆成类;它适合算法族稳定、选择规则清楚的场景。
核心词素:Strategy 是策略、方法。关键词是可替换算法,而不是状态迁移。
八刀
历史
它来自排序、压缩、支付、折扣等算法切换场景。GoF 用它对抗条件分支和继承复用。
辩证
反面是一个函数里塞满算法分支。更高理解是:算法是一等对象,可以被注入。
现象
同一笔订单,会员折扣、满减折扣、新人折扣都能套用同一个结算入口。
语言
Strategy 是策略、方法。关键词是可替换算法,而不是状态迁移。
形式
result = strategy.execute(input)。若算法之间需要共享复杂状态,策略边界会变模糊。
存在
它让人把“怎么做”从“谁在做”里拆出来。
美感
它美在换刀片:同一把刀架,根据材料换不同刀片。
元反思
策略隐喻容易让人想到高层计划。换成“算法插件”隐喻,更能把握它的代码形态。
内观
我是一个可替换算法。上下文把数据交给我,我给出结果。需要新算法时,别改上下文,换我就行。
八刀共同指向的深层结构:策略模式不是为了“炫技”,而是在某个变化点上建立边界,让稳定部分继续稳定,让变化部分有自己的位置。
压缩
公式:策略 = Strategy 接口 + 多个算法实现 + Context 注入
一句话:策略模式把可变算法封装成可替换对象。
结构图:
Context -> Strategy.execute()
StrategyA/StrategyB动机/意图
当同一问题有多种算法或策略,而客户端需要在运行时选择其中一种时,把所有分支写在上下文里会使代码越来越硬。策略模式的意图是把可变算法封装成独立对象,让上下文通过统一接口委托执行。
结构/角色
Strategy:策略接口,声明算法入口。ConcreteStrategy:具体策略,实现某一种算法。Context:上下文,持有策略对象,并把可变行为委托给策略。Client:客户端选择并注入合适策略。
典型 UML
classDiagram class Context { -strategy: Strategy +setStrategy(strategy: Strategy): void +execute(): void } class Strategy { <<interface>> +algorithm(): void } class ConcreteStrategyA class ConcreteStrategyB Context --> Strategy Strategy <|.. ConcreteStrategyA Strategy <|.. ConcreteStrategyB
使用场景
- 多种算法可互换,例如排序、计价、路由、压缩、校验。
- 需要消除上下文中的复杂条件分支。
- 算法需要在运行时切换。
- 希望把算法测试、扩展和维护从主流程中拆出来。
正例:TypeScript
interface SortStrategy {
sort(data: number[]): number[]
}
class BubbleSort implements SortStrategy {
sort(data: number[]) {
const result = [...data]
for (let i = 0; i < result.length - 1; i++) {
for (let j = 0; j < result.length - 1 - i; j++) {
if (result[j] > result[j + 1]) {
;[result[j], result[j + 1]] = [result[j + 1], result[j]]
}
}
}
return result
}
}
class SelectionSort implements SortStrategy {
sort(data: number[]) {
const result = [...data]
for (let i = 0; i < result.length - 1; i++) {
let minIndex = i
for (let j = i + 1; j < result.length; j++) {
if (result[j] < result[minIndex]) {
minIndex = j
}
}
if (minIndex !== i) {
;[result[i], result[minIndex]] = [result[minIndex], result[i]]
}
}
return result
}
}
class InsertionSort implements SortStrategy {
sort(data: number[]) {
const result = [...data]
for (let i = 1; i < result.length; i++) {
const current = result[i]
let j = i - 1
while (j >= 0 && result[j] > current) {
result[j + 1] = result[j]
j--
}
result[j + 1] = current
}
return result
}
}
class ArrayHandler {
constructor(private strategy: SortStrategy) {}
setStrategy(strategy: SortStrategy) {
this.strategy = strategy
}
sort(data: number[]) {
return this.strategy.sort(data)
}
}
const handler = new ArrayHandler(new BubbleSort())
console.log(handler.sort([5, 3, 8, 1, 2]))
handler.setStrategy(new SelectionSort())
console.log(handler.sort([5, 3, 8, 1, 2]))
handler.setStrategy(new InsertionSort())
console.log(handler.sort([5, 3, 8, 1, 2]))正例:UML 类图
classDiagram class SortStrategy { <<interface>> +sort(data: number[]): number[] } class BubbleSort { +sort(data: number[]): number[] } class SelectionSort { +sort(data: number[]): number[] } class InsertionSort { +sort(data: number[]): number[] } class ArrayHandler { -strategy: SortStrategy +setStrategy(strategy: SortStrategy): void +sort(data: number[]): number[] } SortStrategy <|.. BubbleSort SortStrategy <|.. SelectionSort SortStrategy <|.. InsertionSort ArrayHandler --> SortStrategy
反例:TypeScript
class ArrayHandler {
sort(type: "bubble" | "selection" | "insertion", data: number[]) {
const result = [...data]
if (type === "bubble") {
for (let i = 0; i < result.length - 1; i++) {
for (let j = 0; j < result.length - 1 - i; j++) {
if (result[j] > result[j + 1]) {
;[result[j], result[j + 1]] = [result[j + 1], result[j]]
}
}
}
} else if (type === "selection") {
for (let i = 0; i < result.length - 1; i++) {
let minIndex = i
for (let j = i + 1; j < result.length; j++) {
if (result[j] < result[minIndex]) {
minIndex = j
}
}
if (minIndex !== i) {
;[result[i], result[minIndex]] = [result[minIndex], result[i]]
}
}
} else if (type === "insertion") {
for (let i = 1; i < result.length; i++) {
const current = result[i]
let j = i - 1
while (j >= 0 && result[j] > current) {
result[j + 1] = result[j]
j--
}
result[j + 1] = current
}
}
return result
}
}反例:UML 类图
classDiagram class ArrayHandler { +sort(type, data): number[] } ArrayHandler ..> ArrayHandler : algorithm branches
案例
某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,也能够灵活地增加新的排序算法
旅游出行方式可以有多种,如可以乘坐飞机旅游,也可以乘火车旅游,如果有兴趣自行车游也是一种极具乐趣的出行方式。不同的旅游出行方式有不同的实现过程,客户根据自己的需要选择一种合适的旅游方式。在本实例中我们用策略模式来模拟这一过程。
掌握检验
- 策略模式中的 Context 应不应该知道具体策略类?
- 策略模式和状态模式最大的区别是什么?
- 如何在前端表单校验中使用策略模式?