策略模式核心概念
- 策略模式 (Strategy Pattern) 是一种行为型设计模式,核心思想是:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换;
- 类图:
- 实现代码:
// 1. 抽象策略类(Strategy):定义折扣计算的公共接口 interface DiscountStrategy { /** * 计算折扣后金额 * @param amount 订单原价 * @returns 折扣后金额 */ calculate(amount: number): number; } // 2. 具体策略类 A:普通用户(无折扣) class NormalUserStrategy implements DiscountStrategy { calculate(amount: number): number { return amount; // 原价返回 } } // 2. 具体策略类 B:VIP用户(9折) class VipUserStrategy implements DiscountStrategy { calculate(amount: number): number { return amount * 0.9; } } // 2. 具体策略类 C:超级VIP用户(8折 + 满100减20) class SuperVipUserStrategy implements DiscountStrategy { calculate(amount: number): number { let finalAmount = amount * 0.8; // 满100减20 if (finalAmount >= 100) { finalAmount -= 20; } return finalAmount; } } // 3. 环境类(Context):订单处理类,委托策略计算折扣 class OrderContext { private strategy: DiscountStrategy; // 初始化时传入具体策略 constructor(strategy: DiscountStrategy) { this.strategy = strategy; } // 动态切换策略 setStrategy(strategy: DiscountStrategy): void { this.strategy = strategy; } // 执行策略(计算最终金额) calculateFinalAmount(amount: number): number { return this.strategy.calculate(amount); } } // 测试代码 const orderAmount = 200; // 订单原价200元 // 普通用户订单 const normalOrder = new OrderContext(new NormalUserStrategy()); console.log("普通用户最终金额:", normalOrder.calculateFinalAmount(orderAmount)); // 输出:200 // 切换为VIP用户策略 normalOrder.setStrategy(new VipUserStrategy()); console.log("VIP用户最终金额:", normalOrder.calculateFinalAmount(orderAmount)); // 输出:180 // 切换为超级VIP用户策略 normalOrder.setStrategy(new SuperVipUserStrategy()); console.log("超级VIP用户最终金额:", normalOrder.calculateFinalAmount(orderAmount)); // 输出:140(200*0.8=160,满100减20 → 140)
策略模式的常用场景
数据格式化
- 不同场景下数据格式要求不同 (如日期、金额、数字),用策略模式封装格式化逻辑:
- 日期:有的场景需要 YYYY-MM-DD,有的需要 MM/DD/YYYY;
- 金额:有的需要保留 2 位小数,有的需要显示为千分位 (如 1000 → 1,000);
- 实现代码:
// 1. 抽象策略:数据格式化接口 interface FormatStrategy { format(value: any): string; } // 2. 具体策略1:日期格式化 - YYYY-MM-DD class DateYMDFormat implements FormatStrategy { format(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } } // 2. 具体策略2:日期格式化 - MM/DD/YYYY class DateMDYFormat implements FormatStrategy { format(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${month}/${day}/${year}`; } } // 2. 具体策略3:金额格式化 - 千分位 + 保留2位小数 class MoneyThousandFormat implements FormatStrategy { format(amount: number): string { return amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } } // 3. 环境类:格式化工具 class FormatterContext { private strategy: FormatStrategy; constructor(strategy: FormatStrategy) { this.strategy = strategy; } setStrategy(strategy: FormatStrategy) { this.strategy = strategy; } executeFormat(value: any): string { return this.strategy.format(value); } } // 测试 const formatter = new FormatterContext(new DateYMDFormat()); console.log("日期(YYYY-MM-DD):", formatter.executeFormat(new Date(2026, 1, 26))); // 2026-02-26 formatter.setStrategy(new DateMDYFormat()); console.log("日期(MM/DD/YYYY):", formatter.executeFormat(new Date(2026, 1, 26))); // 02/26/2026 formatter.setStrategy(new MoneyThousandFormat()); console.log("金额(千分位):", formatter.executeFormat(123456.789)); // 123,456.79
支付方式选择
- 电商支付环节,不同支付方式 (微信、支付宝、银行卡) 的支付逻辑不同,用策略模式封装:
- 每种支付方式对应一个「支付策略类」,包含 pay 方法;
- 支付页面 (环境类) 根据用户选择,切换对应的支付策略,调用 pay 方法;
- 实现代码:
// 1. 抽象策略:支付接口 interface PayStrategy { pay(amount: number): Promise<string>; // 支付方法,返回支付结果 } // 2. 具体策略1:微信支付 class WxPayStrategy implements PayStrategy { async pay(amount: number): Promise<string> { // 模拟微信支付接口调用 await new Promise(resolve => setTimeout(resolve, 500)); return `微信支付成功,金额:${amount}元`; } } // 2. 具体策略2:支付宝支付 class AliPayStrategy implements PayStrategy { async pay(amount: number): Promise<string> { // 模拟支付宝支付接口调用 await new Promise(resolve => setTimeout(resolve, 500)); return `支付宝支付成功,金额:${amount}元`; } } // 2. 具体策略3:银行卡支付 class BankPayStrategy implements PayStrategy { async pay(amount: number): Promise<string> { // 模拟银行卡支付接口调用 await new Promise(resolve => setTimeout(resolve, 500)); return `银行卡支付成功,金额:${amount}元`; } } // 3. 环境类:支付管理器 class PaymentContext { private strategy: PayStrategy; constructor(strategy: PayStrategy) { this.strategy = strategy; } changePayMethod(strategy: PayStrategy) { this.strategy = strategy; } async doPay(amount: number): Promise<string> { return this.strategy.pay(amount); } } // 测试 (async () => { const payment = new PaymentContext(new WxPayStrategy()); console.log(await payment.doPay(100)); // 微信支付成功,金额:100元 payment.changePayMethod(new AliPayStrategy()); console.log(await payment.doPay(200)); // 支付宝支付成功,金额:200元 payment.changePayMethod(new BankPayStrategy()); console.log(await payment.doPay(300)); // 银行卡支付成功,金额:300元 })();
动画 / 样式切换
- 前端组件的动画效果 (如弹窗的淡入、滑入)、主题样式 (浅色、深色、自定义主题),可通过策略模式封装:
- 每种动画 / 样式对应一个策略类,包含 apply 方法;
- 组件 (环境类) 根据配置切换策略,执行 apply 方法应用动画 / 样式。
- 实现代码:
// 1. 抽象策略:动画策略接口 interface AnimationStrategy { apply(element: HTMLElement): void; // 给DOM元素应用动画 } // 2. 具体策略1:淡入动画 class FadeInAnimation implements AnimationStrategy { apply(element: HTMLElement): void { element.style.opacity = '0'; element.style.transition = 'opacity 0.5s ease'; setTimeout(() => { element.style.opacity = '1'; }, 0); } } // 2. 具体策略2:滑入动画(从下往上) class SlideInAnimation implements AnimationStrategy { apply(element: HTMLElement): void { element.style.transform = 'translateY(100px)'; element.style.opacity = '0'; element.style.transition = 'all 0.5s ease'; setTimeout(() => { element.style.transform = 'translateY(0)'; element.style.opacity = '1'; }, 0); } } // 2. 具体策略3:缩放动画 class ScaleAnimation implements AnimationStrategy { apply(element: HTMLElement): void { element.style.transform = 'scale(0.8)'; element.style.opacity = '0'; element.style.transition = 'all 0.5s ease'; setTimeout(() => { element.style.transform = 'scale(1)'; element.style.opacity = '1'; }, 0); } } // 3. 环境类:弹窗组件 class ModalContext { private element: HTMLElement; private animationStrategy: AnimationStrategy; constructor(elementId: string, strategy: AnimationStrategy) { this.element = document.getElementById(elementId)!; this.animationStrategy = strategy; } setAnimation(strategy: AnimationStrategy) { this.animationStrategy = strategy; } show() { this.element.style.display = 'block'; this.animationStrategy.apply(this.element); } } // 测试(需在浏览器环境运行,先创建id为modal的DOM元素) // <div id="modal" style="display: none; width: 200px; height: 200px; background: #f0f0f0; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);"></div> // const modal = new ModalContext('modal', new FadeInAnimation()); // modal.show(); // 淡入显示 // modal.setAnimation(new SlideInAnimation()); // modal.show(); // 滑入显示 // modal.setAnimation(new ScaleAnimation()); // modal.show(); // 缩放显示
网络请求适配
- 不同环境下的网络请求逻辑不同 (如开发环境走 mock 接口,生产环境走真实接口,测试环境走测试接口),用策略模式封装请求策略:
- 每种环境对应一个「请求策略类」,包含 request 方法;
- 请求工具类 (环境类) 根据环境变量切换策略,统一对外提供 request 方法;
- 实现代码:
// 1. 抽象策略:请求策略接口 interface RequestStrategy { request(url: string, data?: any): Promise<any>; } // 2. 具体策略1:开发环境 - Mock请求 class DevRequestStrategy implements RequestStrategy { async request(url: string, data?: any): Promise<any> { // 模拟Mock数据返回 const mockData = { code: 200, msg: 'success', data: { mock: true, url, data } }; await new Promise(resolve => setTimeout(resolve, 300)); return mockData; } } // 2. 具体策略2:测试环境 - 测试接口请求 class TestRequestStrategy implements RequestStrategy { async request(url: string, data?: any): Promise<any> { // 模拟测试接口请求(拼接测试域名) const testUrl = `https://test-api.com${url}`; await new Promise(resolve => setTimeout(resolve, 500)); return { code: 200, msg: 'success', data: { env: 'test', url: testUrl, data } }; } } // 2. 具体策略3:生产环境 - 生产接口请求 class ProdRequestStrategy implements RequestStrategy { async request(url: string, data?: any): Promise<any> { // 模拟生产接口请求(拼接生产域名) const prodUrl = `https://api.com${url}`; await new Promise(resolve => setTimeout(resolve, 500)); return { code: 200, msg: 'success', data: { env: 'prod', url: prodUrl, data } }; } } // 3. 环境类:请求工具 class RequestContext { private strategy: RequestStrategy; // 根据环境变量初始化策略 constructor(env: 'dev' | 'test' | 'prod') { switch (env) { case 'dev': this.strategy = new DevRequestStrategy(); break; case 'test': this.strategy = new TestRequestStrategy(); break; case 'prod': this.strategy = new ProdRequestStrategy(); break; default: this.strategy = new DevRequestStrategy(); } } setStrategy(strategy: RequestStrategy) { this.strategy = strategy; } async doRequest(url: string, data?: any): Promise<any> { return this.strategy.request(url, data); } } // 测试 (async () => { // 开发环境请求 const devRequest = new RequestContext('dev'); console.log(await devRequest.doRequest('/user', { id: 1 })); // { code: 200, msg: 'success', data: { mock: true, url: '/user', data: { id: 1 } } } // 切换到生产环境 devRequest.setStrategy(new ProdRequestStrategy()); console.log(await devRequest.doRequest('/user', { id: 1 })); // { code: 200, msg: 'success', data: { env: 'prod', url: 'https://api.com/user', data: { id: 1 } } } })();
策略模式的优势
- 开闭原则:可以在不修改原有代码的情况下增加新的策略;
- 避免多重条件语句:消除大量的 if-else 或 switch-case;
- 提高代码复用性:策略可以被不同的上下文复用;
- 策略可以自由切换:运行时可以动态改变对象的行为;
适用场景
- 表单验证 (不同字段需要不同验证规则);
- 数据格式化 (日期、货币、JSON等不同格式);
- 动画效果 (不同的缓动函数);
- 排序算法 (根据数据特点选择不同排序方式);
- 支付方式 (不同的支付渠道处理);
- 日志记录 (不同的日志级别和处理方式);
- 路由守卫 (不同的权限验证逻辑);
状态模式(行为型模式)
上一篇