设计模式-5(策略模式与状态模式)
目录
1.策略模式
1. 策略模式核心思想
- 本质:将「一系列可替换的算法/逻辑」封装成独立的“策略”,通过“策略分发器”动态选择使用哪种策略,替代臃肿的
if-else/switch判断。 - 核心目标:解耦“策略的定义”与“策略的使用”,满足单一职责原则(每个策略只做一件事)和开放封闭原则(新增策略无需修改原有代码,仅需扩展)。
- 适用场景:当业务中存在多个“同类逻辑分支”(如不同计算规则、不同验证方式、不同处理流程),且可能频繁新增/修改分支时(如商品定价、登录验证、支付方式)。
2. 文章核心场景:商品差异化询价
原始需求
大促期间商品需按不同“价格标签”计算最终价,规则如下:
- 预售价(
pre):满100减20,不满100打9折 - 大促价(
onSale):满100减30,不满100打8折 - 返场价(
back):满200减50,不叠加 - 尝鲜价(
fresh):直接打5折
// 询价函数:一个函数处理4种逻辑,违背单一职责
function askPrice(tag, originPrice) {
if (tag === 'pre') {
return originPrice >= 100 ? originPrice - 20 : originPrice * 0.9;
}
if (tag === 'onSale') {
return originPrice >= 100 ? originPrice - 30 : originPrice * 0.8;
}
if (tag === 'back') {
return originPrice >= 200 ? originPrice - 50 : originPrice;
}
if (tag === 'fresh') {
return originPrice * 0.5;
}
}
原始实现的问题
- 违背单一职责:一个函数承载4种询价逻辑,Bug定位难、复用性差(其他地方需用“预售价”逻辑时,只能复制代码)。
- 违背开放封闭:新增价格标签(如“新人价”)时,必须修改
askPrice函数,可能引入风险(需回归测试所有询价逻辑)。
3. 策略模式重构步骤
将每种询价逻辑拆分为独立“策略函数”,每个函数只处理一种规则:
// 策略1:预售价
function prePrice(originPrice) {
return originPrice >= 100 ? originPrice - 20 : originPrice * 0.9;
}
// 策略2:大促价
function onSalePrice(originPrice) {
return originPrice >= 100 ? originPrice - 30 : originPrice * 0.8;
}
// 策略3:返场价
function backPrice(originPrice) {
return originPrice >= 200 ? originPrice - 50 : originPrice;
}
// 策略4:尝鲜价
function freshPrice(originPrice) {
return originPrice * 0.5;
}
// 分发器:仅负责“选择策略”,不处理具体逻辑
function askPrice(tag, originPrice) {
if (tag === 'pre') return prePrice(originPrice);
if (tag === 'onSale') return onSalePrice(originPrice);
if (tag === 'back') return backPrice(originPrice);
if (tag === 'fresh') return freshPrice(originPrice);
}
用“对象映射”存储「标签-策略」的对应关系,彻底消除 if-else,新增策略时仅需扩展对象,无需修改分发器:
// 策略集合:存储所有询价策略(键=标签,值=策略函数)
const priceStrategies = {
pre: (originPrice) => originPrice >= 100 ? originPrice - 20 : originPrice * 0.9,
onSale: (originPrice) => originPrice >= 100 ? originPrice - 30 : originPrice * 0.8,
back: (originPrice) => originPrice >= 200 ? originPrice - 50 : originPrice,
fresh: (originPrice) => originPrice * 0.5
};
// 分发器:通过对象映射动态选择策略,代码永久不变
function askPrice(tag, originPrice) {
// 若标签不存在,返回原价(容错处理)
return priceStrategies[tag]?.(originPrice) || originPrice;
}
// 新增“新人价”策略:仅需扩展策略集合,无需修改askPrice
priceStrategies.newUser = (originPrice) => originPrice >= 100 ? originPrice - 50 : originPrice;
策略模式的正式定义
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
- “算法”:不仅指数学计算,也可指业务逻辑(如询价规则、登录验证逻辑)。
- “封装”:每个策略独立成函数/类,与其他策略解耦。
- “可替换”:通过分发器(如对象映射)动态切换策略,无需修改使用逻辑。
4. 案例练习:用户登录方式验证
场景描述
实现用户登录验证功能,支持3种登录方式,每种方式的验证规则不同:
- 账号密码登录(
account):验证用户名不为空 + 密码长度≥6位 - 手机号验证码登录(
phone):验证手机号格式(11位数字) + 验证码为6位数字 - 第三方登录(
thirdParty):验证第三方平台标识(如wechat/github)不为空 + 授权码不为空
要求:新增登录方式时无需修改原有验证逻辑,且避免 if-else。
// 1. 定义所有登录策略(封装独立规则)
const loginStrategies = {
// 策略1:账号密码登录
account: (data) => {
if (!data.username) return { valid: false, msg: '用户名不能为空' };
if (data.password.length < 6) return { valid: false, msg: '密码长度需≥6位' };
return { valid: true, msg: '账号密码验证通过' };
},
// 策略2:手机号验证码登录
phone: (data) => {
const phoneReg = /^1\d{10}$/;
if (!phoneReg.test(data.phone)) return { valid: false, msg: '手机号格式错误' };
if (!/^\d{6}$/.test(data.code)) return { valid: false, msg: '验证码需为6位数字' };
return { valid: true, msg: '手机号验证码验证通过' };
},
// 策略3:第三方登录
thirdParty: (data) => {
if (!['wechat', 'github', 'google'].includes(data.platform)) return { valid: false, msg: '不支持的第三方平台' };
if (!data.authCode) return { valid: false, msg: '授权码不能为空' };
return { valid: true, msg: '第三方登录验证通过' };
}
};
// 2. 登录验证分发器(使用策略)
function verifyLogin(type, data) {
// 容错:若登录方式不存在,直接返回无效
if (!loginStrategies[type]) return { valid: false, msg: '不支持的登录方式' };
// 调用对应策略进行验证
return loginStrategies[type](data);
}
// 3. 测试
console.log(verifyLogin('account', { username: 'zhangsan', password: '123456' }));
// { valid: true, msg: '账号密码验证通过' }
console.log(verifyLogin('phone', { phone: '13800138000', code: '123456' }));
// { valid: true, msg: '手机号验证码验证通过' }
// 新增“邮箱登录”策略(无需修改verifyLogin)
loginStrategies.email = (data) => {
const emailReg = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+$/;
if (!emailReg.test(data.email)) return { valid: false, msg: '邮箱格式错误' };
return { valid: true, msg: '邮箱登录验证通过' };
};
console.log(verifyLogin('email', { email: 'test@example.com' }));
// { valid: true, msg: '邮箱登录验证通过' }
// 1. 定义类型约束:明确登录数据结构(避免传错参数)
interface AccountLoginData {
username: string;
password: string;
}
interface PhoneLoginData {
phone: string;
code: string;
}
interface ThirdPartyLoginData {
platform: 'wechat' | 'github' | 'google'; // 限定第三方平台范围
authCode: string;
}
interface EmailLoginData {
email: string;
}
// 2. 定义策略接口:规范所有登录策略的“输入输出”(强制统一结构)
interface LoginStrategy<T> {
verify: (data: T) => { valid: boolean; msg: string };
}
// 3. 定义登录类型枚举:限定仅支持的登录方式(避免传错字符串,如"accout"拼写错误)
enum LoginType {
ACCOUNT = 'account',
PHONE = 'phone',
THIRD_PARTY = 'thirdParty',
EMAIL = 'email'
}
// 4. 实现具体登录策略(TS自动校验:必须符合LoginStrategy接口)
const AccountStrategy: LoginStrategy<AccountLoginData> = {
verify: (data) => {
if (!data.username) return { valid: false, msg: '用户名不能为空' };
if (data.password.length < 6) return { valid: false, msg: '密码长度需≥6位' };
return { valid: true, msg: '账号密码验证通过' };
}
};
const PhoneStrategy: LoginStrategy<PhoneLoginData> = {
verify: (data) => {
const phoneReg = /^1\d{10}$/;
if (!phoneReg.test(data.phone)) return { valid: false, msg: '手机号格式错误' };
if (!/^\d{6}$/.test(data.code)) return { valid: false, msg: '验证码需为6位数字' };
return { valid: true, msg: '手机号验证码验证通过' };
}
};
const ThirdPartyStrategy: LoginStrategy<ThirdPartyLoginData> = {
verify: (data) => {
if (!data.platform) return { valid: false, msg: '第三方平台不能为空' };
if (!data.authCode) return { valid: false, msg: '授权码不能为空' };
return { valid: true, msg: '第三方登录验证通过' };
}
};
// 5. 策略集合:类型明确(键=LoginType,值=对应策略)
const loginStrategyMap: Record<LoginType, LoginStrategy<any>> = {
[LoginType.ACCOUNT]: AccountStrategy,
[LoginType.PHONE]: PhoneStrategy,
[LoginType.THIRD_PARTY]: ThirdPartyStrategy,
// 初始未定义EMAIL,TS会提示“属性EMAIL缺失”(编译时校验)
};
// 6. 登录验证分发器(类型安全:参数和返回值明确)
function verifyLogin<T>(type: LoginType, data: T): { valid: boolean; msg: string } {
const strategy = loginStrategyMap[type];
if (!strategy) return { valid: false, msg: '不支持的登录方式' };
// TS自动校验:strategy.verify的参数必须是T类型
return strategy.verify(data);
}
// 7. 测试(TS实时提示参数错误,编译时拦截问题)
// 正确案例:账号密码登录(参数符合AccountLoginData)
console.log(verifyLogin(LoginType.ACCOUNT, { username: 'zhangsan', password: '123456' }));
// 错误案例1:手机号登录少传code(TS提示“Property 'code' is missing”)
// console.log(verifyLogin(LoginType.PHONE, { phone: '13800138000' }));
// 错误案例2:第三方登录传无效platform(TS提示“Type 'alipay' is not assignable to type 'wechat' | 'github' | 'google'”)
// console.log(verifyLogin(LoginType.THIRD_PARTY, { platform: 'alipay', authCode: 'xxx' }));
// 8. 新增“邮箱登录”策略(仅需扩展,无需修改原有代码)
const EmailStrategy: LoginStrategy<EmailLoginData> = {
verify: (data) => {
const emailReg = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z]+$/;
if (!emailReg.test(data.email)) return { valid: false, msg: '邮箱格式错误' };
return { valid: true, msg: '邮箱登录验证通过' };
}
};
// 扩展策略集合(TS自动校验:必须符合LoginStrategy<EmailLoginData>)
loginStrategyMap[LoginType.EMAIL] = EmailStrategy;
// 测试邮箱登录
console.log(verifyLogin(LoginType.EMAIL, { email: 'test@example.com' }));
TS 实现的优越性体现
- 类型安全:通过接口(
LoginStrategy)、枚举(LoginType)和类型注解,强制约束“策略的输入输出”和“登录方式范围”,避免拼写错误(如"accout")、少传参数(如手机号登录少传code)、传无效值(如第三方平台传alipay),这些错误在编译时即可发现,而JS需运行时才暴露。 - 开发体验:IDE会提供实时自动提示(如输入
LoginType.时,自动显示所有支持的登录方式;调用verifyLogin时,提示参数结构),无需记忆参数格式,降低协作成本。 - 可维护性:类型定义清晰描述了每个策略的依赖(如
AccountLoginData需username和password),新开发者无需阅读业务逻辑即可理解参数含义,且新增策略时TS会自动校验是否符合规范(如必须实现verify方法)。
2.状态模式
1. 核心场景:从“咖啡机需求”切入
文章以“李雷实现咖啡机功能”为案例,明确状态模式的适用场景:
当一个对象(如咖啡机)有多种互斥状态(美式、拿铁、香草拿铁、摩卡),且不同状态对应不同行为逻辑(制作咖啡的步骤),同时状态间需要灵活切换时,适合用状态模式解决。
2. 代码演进:告别 if-else,落地设计原则
状态模式的实现核心是“将状态判断逻辑拆分,让行为与状态解耦”,文章通过3版代码逐步落地:
直接在状态切换函数中用 if-else 嵌套所有行为逻辑,存在两大问题:
- 行为逻辑无法复用(如香草拿铁依赖拿铁的“加奶”步骤,却需重复写);
- 违反“开放封闭原则”(新增咖啡品种需修改
changeState函数)。
class CoffeeMaker {
constructor() {
this.state = 'init'; // 初始状态
}
changeState(state) {
this.state = state;
if (state === 'american') console.log('只吐黑咖啡');
else if (state === 'latte') console.log('黑咖啡加点奶');
else if (state === 'vanillaLatte') console.log('黑咖啡加点奶再加香草糖浆');
else if (state === 'mocha') console.log('黑咖啡加点奶再加巧克力');
}
}
将“咖啡制作行为”抽离为独立方法,在 changeState 中调用方法,实现行为复用(如 vanillaLatteProcress 可调用 latteProcress),但仍未解决 if-else 判断问题。
class CoffeeMaker {
constructor() { this.state = 'init'; }
changeState(state) {
this.state = state;
state === 'american' && this.americanProcess();
state === 'latte' && this.latteProcress();
// ...其他状态判断
}
// 抽离行为方法
americanProcess() { console.log('只吐黑咖啡'); }
latteProcress() { this.americanProcess(); console.log('加点奶'); }
vanillaLatteProcress() { this.latteProcress(); console.log('再加香草糖浆'); }
}
用“状态-行为映射对象”替代 if-else,同时让映射对象关联实例(通过 that: this),解决“行为感知实例内部状态”的问题(如读取咖啡机牛奶储量),完全符合开放封闭原则(新增品种只需在映射对象中加方法)。
class CoffeeMaker {
constructor() {
this.state = 'init';
this.leftMilk = '500ml'; // 实例内部状态(牛奶储量)
// 状态-行为映射对象(关联实例)
this.stateToProcessor = {
that: this,
american() {
console.log('牛奶储量:', this.that.leftMilk);
console.log('只吐黑咖啡');
},
latte() { this.american(); console.log('加点奶'); },
vanillaLatte() { this.latte(); console.log('再加香草糖浆'); },
mocha() { this.latte(); console.log('再加巧克力'); }
};
}
changeState(state) {
this.state = state;
this.stateToProcessor[state]?.(); // 委托映射对象执行行为
}
}
3. 关键辨析:状态模式 vs 策略模式(孪生兄弟的区别)
两者都封装行为、通过委托分发逻辑,但核心差异在行为与主体的关联:
| 维度 | 策略模式 | 状态模式 |
| 行为独立性 | 行为(策略)互相平行,不依赖主体 | 行为(状态逻辑)依赖主体,需感知主体内部状态 |
| 行为目的 | 解决“多种算法选择”(如不同排序算法) | 解决“多种状态切换”(如咖啡机不同制作流程) |
| 主体与行为关系 | 主体主动选择策略 | 主体状态变化触发行为切换 |
状态模式核心定义
- 官方定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- 通俗理解:把对象的“状态判断”拆成独立的行为逻辑,让状态变化时自动切换行为,而非用
if-else硬编码。
4. 案例练习:自动门控制(状态模式实践)
需求场景
设计一个自动门系统,包含以下状态和行为:
- 状态:关闭状态(默认)、开启状态、延迟关闭状态(门开后停留3秒再关);
- 触发事件:
-
- 有人靠近(关闭状态 → 开启状态);
- 开门后超时(开启状态 → 延迟关闭状态 → 关闭状态);
- 紧急按钮(任何状态 → 关闭状态)。
class AutoDoor {
constructor() {
this.currentState = 'closed'; // 初始状态:关闭
this.closeDelay = 3000; // 延迟关闭时间(3秒)
this.timer = null; // 超时定时器
// 状态-行为映射对象(关联实例)
this.stateToAction = {
that: this,
// 关闭状态:仅响应“有人靠近”
closed() {
console.log('门已关闭,等待触发...');
this.that.onPersonApproach = () => {
this.that.switchState('opened');
};
},
// 开启状态:触发延迟关闭
opened() {
clearTimeout(this.that.timer);
console.log('门已开启,3秒后自动关闭...');
this.that.timer = setTimeout(() => {
this.that.switchState('delayedClose');
}, this.that.closeDelay);
// 紧急按钮:强制关闭
this.that.onEmergency = () => {
this.that.switchState('closed');
};
},
// 延迟关闭状态:执行关闭
delayedClose() {
clearTimeout(this.that.timer);
console.log('延迟时间到,门正在关闭...');
this.that.switchState('closed');
}
};
// 初始化状态
this.switchState('closed');
}
// 状态切换方法
switchState(state) {
this.currentState = state;
this.stateToAction[state]?.();
}
// 模拟外部触发:有人靠近
triggerApproach() {
this.onPersonApproach?.();
}
// 模拟外部触发:紧急按钮
triggerEmergency() {
this.onEmergency?.();
}
}
// 测试
const door = new AutoDoor(); // 输出:门已关闭,等待触发...
door.triggerApproach(); // 输出:门已开启,3秒后自动关闭...
// 3秒后输出:延迟时间到,门正在关闭... → 门已关闭,等待触发...
// door.triggerEmergency(); // 紧急按钮:立即关闭
TS 优势体现在:类型约束(避免魔法字符串/错误调用)、枚举(状态语义化)、接口(行为结构统一),编译时即可发现错误。
// 1. 枚举:状态语义化,避免魔法字符串
enum DoorState {
CLOSED = 'closed',
OPENED = 'opened',
DELAYED_CLOSE = 'delayedClose'
}
// 2. 接口:约束状态-行为映射对象的结构,确保每个状态有对应方法
interface DoorActionMap {
[key in DoorState]: () => void;
that: AutoDoor; // 关联实例
}
class AutoDoor {
// 3. 类型标注:明确属性类型,避免类型错误
public currentState: DoorState = DoorState.CLOSED;
public closeDelay: number = 3000;
public timer: NodeJS.Timeout | null = null;
// 4. 类型标注:行为触发函数(明确参数/返回值类型)
public onPersonApproach?: () => void;
public onEmergency?: () => void;
// 状态-行为映射对象(TS 自动校验结构是否符合 DoorActionMap)
public stateToAction: DoorActionMap = {
that: this,
[DoorState.CLOSED]: () => {
console.log('门已关闭,等待触发...');
this.onPersonApproach = () => {
this.switchState(DoorState.OPENED); // 只能传入 DoorState 枚举值
};
},
[DoorState.OPENED]: () => {
clearTimeout(this.timer!); // 非空断言(TS 知道 timer 已初始化)
console.log('门已开启,3秒后自动关闭...');
this.timer = setTimeout(() => {
this.switchState(DoorState.DELAYED_CLOSE);
}, this.closeDelay);
this.onEmergency = () => {
this.switchState(DoorState.CLOSED);
};
},
[DoorState.DELAYED_CLOSE]: () => {
clearTimeout(this.timer!);
console.log('延迟时间到,门正在关闭...');
this.switchState(DoorState.CLOSED);
}
};
constructor() {
this.switchState(DoorState.CLOSED);
}
// 状态切换方法(参数只能是 DoorState 类型)
public switchState(state: DoorState): void {
this.currentState = state;
this.stateToAction[state](); // TS 确保 state 一定在映射对象中存在
}
// 外部触发方法(类型明确,避免错误调用)
public triggerApproach(): void {
this.onPersonApproach?.();
}
public triggerEmergency(): void {
this.onEmergency?.();
}
}
// 测试(TS 自动提示枚举值,避免传错状态)
const door = new AutoDoor();
door.triggerApproach();
// door.switchState('error'); // 编译报错:'error' 不是 DoorState 类型
// door.triggerEmergency(123); // 编译报错:triggerEmergency 无参数
TS 实现的核心优势总结
- 枚举(DoorState):限定状态取值范围,避免传入无效字符串(如
'error'); - 接口(DoorActionMap):强制状态-行为映射对象的结构,确保每个状态有对应方法;
- 类型标注:明确属性/方法的类型(如
timer: NodeJS.Timeout | null),编译时发现类型错误; - 语义化:代码可读性更高,协作时他人可通过类型快速理解状态和行为的关联。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)