news 2025/12/25 19:38:10

策略模式(Strategy Pattern)在 JS 中的应用:实现可替换的算法逻辑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
策略模式(Strategy Pattern)在 JS 中的应用:实现可替换的算法逻辑

编程专家讲座:策略模式(Strategy Pattern)在 JS 中的应用:实现可替换的算法逻辑

各位同仁,大家好!

今天,我们将深入探讨一个在软件设计中极为强大且实用的设计模式——策略模式(Strategy Pattern),并着重讲解它在 JavaScript 世界中的应用。随着前端和后端 JavaScript 应用的日益复杂,我们经常面临需要根据不同条件或配置执行不同算法的场景。在这种情况下,如果一味地使用大量的if/elseswitch语句来控制逻辑,代码将变得难以阅读、难以维护,并且违反了开放/封闭原则。策略模式正是解决这类问题的利器,它允许我们在运行时动态地替换算法,从而实现可插拔的逻辑。

1. 策略模式概述:解耦算法与上下文

1.1 什么是策略模式?

策略模式属于行为型设计模式,其核心思想是定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法独立于使用它的客户端而变化。

简单来说,当您有一个类,它需要根据某些条件执行不同的行为时,您可以将这些行为(算法)抽象出来,分别封装到独立的“策略”对象中。然后,这个类(我们称之为“上下文”)不再直接实现这些行为,而是持有一个对某个策略对象的引用,并将具体的行为委托给这个策略对象来执行。这样,上下文就可以在运行时根据需要切换不同的策略,而无需修改自身的代码。

1.2 为什么我们需要策略模式?

考虑一个常见的场景:一个电商平台需要根据用户的会员等级、促销活动或支付方式来计算最终价格。如果直接在订单处理逻辑中堆砌大量的if...else if...elseswitch语句来判断并执行不同的计算规则,代码会迅速膨胀,变得脆弱。

  • 代码难以维护:每次新增或修改一个计算规则,都需要修改核心的订单处理逻辑。
  • 违反开放/封闭原则:核心逻辑对扩展是关闭的(不能在不修改其代码的情况下添加新功能),对修改是开放的(每次新增规则都需要修改)。
  • 代码冗余:不同的计算规则可能包含一些相似的逻辑,但因为纠缠在一起,难以复用。
  • 测试复杂:难以对单一的计算规则进行独立测试,必须通过整个订单处理流程来验证。

策略模式正是为了解决这些问题而生。它将算法的定义、选择和使用过程分离,让代码更加模块化、可维护和可扩展。

1.3 策略模式的核心组件

策略模式通常包含以下三个核心组件:

  1. 策略(Strategy)接口/抽象策略:

    • 定义了一个公共接口,所有具体的策略类都必须实现这个接口。
    • 这个接口声明了上下文类用于调用具体策略的方法。
    • 在 JavaScript 中,这通常是一个隐式的契约(即一组函数签名),或者通过定义一个基类/抽象类来实现。
  2. 具体策略(Concrete Strategy):

    • 实现了策略接口/抽象策略,封装了具体的算法或行为。
    • 每个具体策略类都实现了一种特定的算法。
  3. 上下文(Context):

    • 持有一个对策略对象的引用。
    • 上下文不直接实现算法,而是将算法的执行委托给它所持有的策略对象。
    • 客户端通常通过上下文来使用策略模式,上下文负责根据需要配置(设置)具体的策略对象。

下图展示了策略模式的UML类图概念(尽管在JS中更多是对象和函数而非严格的类):

+------------------+ +---------------------+ | Context | | <<interface>> | |------------------| | Strategy | | - strategy: Strategy |-------> +---------------------+ |------------------| | + executeAlgorithm()| | + setStrategy(s) | +----------^----------+ | + execute() | | +------------------+ | implements | +----------------------+----------------------+ | | | +---------------------+ +---------------------+ +---------------------+ | ConcreteStrategyA | | ConcreteStrategyB | | ConcreteStrategyC | |---------------------| |---------------------| |---------------------| | + executeAlgorithm()| | + executeAlgorithm()| | + executeAlgorithm()| +---------------------+ +---------------------+ +---------------------+

2. JavaScript 中的策略模式:函数与对象的灵活运用

JavaScript 作为一门动态、函数优先的语言,为策略模式的实现提供了极大的灵活性。我们不一定需要严格的类和接口,常常可以直接利用函数或普通对象来充当策略。

2.1 JS 实现策略模式的优势
  • 函数作为一等公民:JavaScript 中的函数可以像普通值一样被传递、赋值和作为参数,这使得将算法封装为函数变得非常自然和高效。
  • 对象字面量:简单的策略可以直接定义为包含方法的对象字面量。
  • 动态性:运行时可以轻松地替换策略对象或函数。
2.2 基本实现示例:电商价格计算器

让我们通过一个电商场景来具体实现策略模式。假设我们需要为商品计算最终价格,根据不同的促销活动,折扣方式会有所不同:

  1. 无折扣
  2. 百分比折扣
  3. 固定金额折扣
  4. 买X送Y折扣

问题:不使用策略模式的传统实现

// 假设商品价格和数量 const itemPrice = 100; const quantity = 2; const totalAmount = itemPrice * quantity; // 初始总金额 200 function calculateFinalPriceWithoutStrategy(initialAmount, discountType, discountValue) { let finalPrice = initialAmount; if (discountType === 'none') { // 无折扣 finalPrice = initialAmount; } else if (discountType === 'percentage') { // 百分比折扣,discountValue 是折扣百分比(如 0.1 表示 10%) finalPrice = initialAmount * (1 - discountValue); } else if (discountType === 'fixedAmount') { // 固定金额折扣,discountValue 是折扣金额 finalPrice = initialAmount - discountValue; if (finalPrice < 0) finalPrice = 0; // 价格不能为负 } else if (discountType === 'buyXGetYFree') { // 买X送Y,简化为每X个商品赠送1个同类商品,discountValue 为X // 比如买2送1,discountValue = 2 const freeItemsCount = Math.floor(quantity / (discountValue + 1)); // 假设买X送1,实际支付X件商品费用 const payableItemsCount = quantity - freeItemsCount; finalPrice = itemPrice * payableItemsCount; } else { console.warn('未知折扣类型:', discountType); finalPrice = initialAmount; } return finalPrice; } console.log("--- 传统计算方式 ---"); console.log("无折扣:", calculateFinalPriceWithoutStrategy(totalAmount, 'none')); // 200 console.log("10%折扣:", calculateFinalPriceWithoutStrategy(totalAmount, 'percentage', 0.1)); // 180 console.log("减免20元:", calculateFinalPriceWithoutStrategy(totalAmount, 'fixedAmount', 20)); // 180 console.log("买2送1 (商品价格100, 购买2件):", calculateFinalPriceWithoutStrategy(100 * 2, 'buyXGetYFree', 1)); // 购买2件,买1送1,实际支付1件价格 = 100 console.log("买2送1 (商品价格100, 购买3件):", calculateFinalPriceWithoutStrategy(100 * 3, 'buyXGetYFree', 1)); // 购买3件,买1送1,实际支付2件价格 = 200 console.log("买2送1 (商品价格100, 购买4件):", calculateFinalPriceWithoutStrategy(100 * 4, 'buyXGetYFree', 1)); // 购买4件,买1送1,实际支付2件价格 = 200 (这里简化了,实际应为支付2件,得到4件) // 假设我们购买了2件价格100的商品,初始总价200 const initialAmount = 200; console.log("无折扣:", calculateFinalPriceWithoutStrategy(initialAmount, 'none', 0)); // 200 console.log("百分比折扣 (10%):", calculateFinalPriceWithoutStrategy(initialAmount, 'percentage', 0.1)); // 180 console.log("固定金额折扣 (减20元):", calculateFinalPriceWithoutStrategy(initialAmount, 'fixedAmount', 20)); // 180

可以看到,calculateFinalPriceWithoutStrategy函数内部包含了大量的条件判断。如果未来需要增加新的折扣类型(如满减、阶梯折扣),这个函数将不得不继续膨胀,变得越来越难以管理。

解决方案:使用策略模式

  1. 定义策略接口(隐式):
    在 JavaScript 中,我们可以约定所有折扣策略都必须实现一个calculate(initialAmount, discountValue, itemPrice, quantity)方法(或函数),该方法接收初始金额、折扣值、单价和数量,并返回最终金额。

  2. 具体策略(Concrete Strategies):
    我们将每种折扣算法封装成一个独立的函数或对象。

    // 2.1 定义具体折扣策略对象 const discountStrategies = { // 无折扣策略 none: { calculate: (initialAmount, discountValue, itemPrice, quantity) => { console.log("应用:无折扣"); return initialAmount; } }, // 百分比折扣策略 percentage: { calculate: (initialAmount, discountPercentage, itemPrice, quantity) => { console.log(`应用:百分比折扣 ${discountPercentage * 100}%`); if (discountPercentage < 0 || discountPercentage > 1) { console.warn("折扣百分比应在0到1之间。"); return initialAmount; } return initialAmount * (1 - discountPercentage); } }, // 固定金额折扣策略 fixedAmount: { calculate: (initialAmount, fixedDiscountAmount, itemPrice, quantity) => { console.log(`应用:固定金额折扣 减${fixedDiscountAmount}元`); let finalPrice = initialAmount - fixedDiscountAmount; return finalPrice < 0 ? 0 : finalPrice; // 确保最终价格不为负 } }, // 满减折扣策略 (新增的复杂策略) fullReduction: { calculate: (initialAmount, thresholds, itemPrice, quantity) => { console.log(`应用:满减折扣 (满${thresholds.full}减${thresholds.reduction})`); if (initialAmount >= thresholds.full) { return initialAmount - thresholds.reduction; } return initialAmount; } }, // 买X送Y折扣策略 (这里简化实现,假设买X送1) buyXGetYFree: { calculate: (initialAmount, buyXCount, itemPrice, quantity) => { console.log(`应用:买${buyXCount}送1`); if (quantity <= buyXCount) { return initialAmount; // 没达到买X的数量 } const freeItemsCount = Math.floor(quantity / (buyXCount + 1)); // 简化:每买X+1件,送1件 const payableItemsCount = quantity - freeItemsCount; return itemPrice * payableItemsCount; } }, // 会员专属折扣 (假设会员等级越高折扣越多) memberDiscount: { calculate: (initialAmount, memberLevel, itemPrice, quantity) => { let discountRate = 0; switch (memberLevel) { case 'bronze': discountRate = 0.05; // 5% off break; case 'silver': discountRate = 0.1; // 10% off break; case 'gold': discountRate = 0.15; // 15% off break; default: discountRate = 0; } console.log(`应用:${memberLevel}会员折扣 ${discountRate * 100}%`); return initialAmount * (1 - discountRate); } } };
  3. 上下文(Context)类:
    创建一个PriceCalculator类,它将持有一个对当前折扣策略的引用,并提供一个方法来执行计算。

    // 2.2 定义上下文类 class PriceCalculator { constructor(itemPrice, quantity) { this.itemPrice = itemPrice; this.quantity = quantity; this.initialAmount = itemPrice * quantity; this.currentStrategy = discountStrategies.none; // 默认策略为无折扣 this.discountValue = null; // 策略可能需要的额外参数 } // 设置折扣策略的方法 setStrategy(strategyName, discountValue = null) { const strategy = discountStrategies[strategyName]; if (!strategy || !strategy.calculate || typeof strategy.calculate !== 'function') { console.error(`未知或无效的折扣策略: ${strategyName}. 使用默认无折扣策略.`); this.currentStrategy = discountStrategies.none; this.discountValue = null; return; } this.currentStrategy = strategy; this.discountValue = discountValue; } // 执行计算的方法 calculateFinalPrice() { // 将计算委托给当前设置的策略 return this.currentStrategy.calculate( this.initialAmount, this.discountValue, this.itemPrice, this.quantity ); } }

使用示例:

console.log("n--- 策略模式计算方式 ---"); const productPrice = 100; const productQuantity = 2; const calculator = new PriceCalculator(productPrice, productQuantity); console.log(`商品单价: ${productPrice}, 数量: ${productQuantity}, 初始总价: ${calculator.initialAmount}`); // 1. 应用无折扣 calculator.setStrategy('none'); console.log("最终价格 (无折扣):", calculator.calculateFinalPrice()); // 200 // 2. 应用百分比折扣 (10% off) calculator.setStrategy('percentage', 0.1); console.log("最终价格 (10% 折扣):", calculator.calculateFinalPrice()); // 180 // 3. 应用固定金额折扣 (减20元) calculator.setStrategy('fixedAmount', 20); console.log("最终价格 (减20元):", calculator.calculateFinalPrice()); // 180 // 4. 应用满减折扣 (满150减30) calculator.setStrategy('fullReduction', { full: 150, reduction: 30 }); console.log("最终价格 (满150减30):", calculator.calculateFinalPrice()); // 170 (初始200,满足150,减30) // 5. 应用买X送Y折扣 (买1送1) const calculatorBuyXGetY = new PriceCalculator(100, 3); // 购买3件,单价100,初始300 console.log(`n商品单价: ${calculatorBuyXGetY.itemPrice}, 数量: ${calculatorBuyXGetY.quantity}, 初始总价: ${calculatorBuyXGetY.initialAmount}`); calculatorBuyXGetY.setStrategy('buyXGetYFree', 1); // 买1送1 console.log("最终价格 (买1送1,购买3件):", calculatorBuyXGetY.calculateFinalPrice()); // 200 (支付2件价格) const calculatorBuyXGetY_2 = new PriceCalculator(100, 4); // 购买4件,单价100,初始400 console.log(`n商品单价: ${calculatorBuyXGetY_2.itemPrice}, 数量: ${calculatorBuyXGetY_2.quantity}, 初始总价: ${calculatorBuyXGetY_2.initialAmount}`); calculatorBuyXGetY_2.setStrategy('buyXGetYFree', 1); // 买1送1 console.log("最终价格 (买1送1,购买4件):", calculatorBuyXGetY_2.calculateFinalPrice()); // 200 (支付2件价格) // 6. 应用会员折扣 (白银会员) const memberCalculator = new PriceCalculator(productPrice, productQuantity); // 初始200 console.log(`n商品单价: ${memberCalculator.itemPrice}, 数量: ${memberCalculator.quantity}, 初始总价: ${memberCalculator.initialAmount}`); memberCalculator.setStrategy('memberDiscount', 'silver'); console.log("最终价格 (白银会员):", memberCalculator.calculateFinalPrice()); // 180 // 动态切换策略 console.log("n--- 动态切换策略 ---"); const dynamicCalculator = new PriceCalculator(productPrice, productQuantity); dynamicCalculator.setStrategy('percentage', 0.2); // 先设置20%折扣 console.log("当前价格 (20%折扣):", dynamicCalculator.calculateFinalPrice()); // 160 dynamicCalculator.setStrategy('fixedAmount', 50); // 运行时切换为减50元 console.log("切换后价格 (减50元):", dynamicCalculator.calculateFinalPrice()); // 150

通过这个例子,我们可以清晰地看到策略模式的优势:

  • 分离关注点:每种折扣算法都独立封装在一个策略对象中,PriceCalculator上下文只负责调用当前策略,而不关心其内部实现。
  • 易于扩展:如果需要添加新的折扣类型(例如“第二件半价”),我们只需要创建一个新的策略对象并实现其calculate方法,无需修改PriceCalculator类。
  • 消除条件语句:PriceCalculator内部不再有复杂的if/else if/else结构来判断折扣类型。
  • 可测试性:每个折扣策略都可以独立进行单元测试。

3. 进阶应用场景

策略模式的应用范围非常广泛,凡是涉及根据不同条件执行不同行为的场景,都可以考虑使用它。

3.1 数据验证

在表单提交或数据处理中,我们经常需要对数据进行多种验证(必填、最小长度、邮箱格式、数字范围等)。

传统方式:

function validateUser(user) { if (!user.name) return "Name is required."; if (user.name.length < 3) return "Name must be at least 3 characters."; if (!user.email) return "Email is required."; if (!/S+@S+.S+/.test(user.email)) return "Invalid email format."; if (user.age && (user.age < 18 || user.age > 99)) return "Age must be between 18 and 99."; return null; // No errors }

策略模式实现:

// 3.1.1 定义验证策略 const validationStrategies = { isNonEmpty: { validate: (value) => value !== null && value.trim() !== '', message: '不能为空。' }, minLength: { validate: (value, min) => value.length >= min, message: (min) => `长度不能少于 ${min} 个字符。` }, isEmail: { validate: (value) => /S+@S+.S+/.test(value), message: '邮箱格式不正确。' }, isNumber: { validate: (value) => !isNaN(Number(value)) && isFinite(value), message: '必须是数字。' }, inRange: { validate: (value, min, max) => Number(value) >= min && Number(value) <= max, message: (min, max) => `必须在 ${min} 到 ${max} 之间。` } }; // 3.1.2 定义上下文 (Validator) class Validator { constructor() { this.rules = {}; // 存储每个字段的验证规则 } // 添加验证规则 // fieldName: 字段名称 (e.g., 'name', 'email') // strategyName: 策略名称 (e.g., 'isNonEmpty', 'minLength') // args: 传递给策略的额外参数 (e.g., minLength 的 '3') addRule(fieldName, strategyName, ...args) { if (!this.rules[fieldName]) { this.rules[fieldName] = []; } const strategy = validationStrategies[strategyName]; if (!strategy || typeof strategy.validate !== 'function') { console.warn(`未知或无效的验证策略: ${strategyName} for field ${fieldName}`); return; } this.rules[fieldName].push({ strategy, args }); } // 验证数据 validate(data) { const errors = {}; for (const fieldName in this.rules) { const fieldRules = this.rules[fieldName]; const value = data[fieldName]; for (const { strategy, args } of fieldRules) { if (!strategy.validate(value, ...args)) { let errorMessage = strategy.message; if (typeof errorMessage === 'function') { errorMessage = errorMessage(...args); } errors[fieldName] = (errors[fieldName] || []).concat(`${fieldName}${errorMessage}`); // 可以在这里选择是收集所有错误还是遇到第一个错误就停止 // break; // 如果只想显示每个字段的第一个错误 } } } return Object.keys(errors).length === 0 ? null : errors; } } // 使用示例 console.log("n--- 数据验证 (策略模式) ---"); const userValidator = new Validator(); userValidator.addRule('username', 'isNonEmpty'); userValidator.addRule('username', 'minLength', 5); userValidator.addRule('email', 'isNonEmpty'); userValidator.addRule('email', 'isEmail'); userValidator.addRule('age', 'isNumber'); userValidator.addRule('age', 'inRange', 18, 60); const userData1 = { username: 'john_doe', email: 'john.doe@example.com', age: 30 }; const errors1 = userValidator.validate(userData1); console.log("UserData1 验证结果:", errors1); // null (无错误) const userData2 = { username: 'jo', // Too short email: 'invalid-email', // Invalid format age: 15 // Out of range }; const errors2 = userValidator.validate(userData2); console.log("UserData2 验证结果:", errors2); /* Output for userData2: { username: [ 'username长度不能少于 5 个字符。' ], email: [ 'email邮箱格式不正确。' ], age: [ 'age必须在 18 到 60 之间。' ] } */ const userData3 = { username: 'alice', email: '', // Empty age: 'abc' // Not a number }; const errors3 = userValidator.validate(userData3); console.log("UserData3 验证结果:", errors3); /* Output for userData3: { email: [ 'email不能为空。' ], age: [ 'age必须是数字。', 'age必须在 18 到 60 之间。' ] } */

这个验证器非常灵活,可以轻松地添加新的验证规则,或者为不同的表单组合不同的验证规则,而无需修改Validator类的核心逻辑。

3.2 支付处理

一个电商系统需要支持多种支付方式:信用卡、PayPal、支付宝、微信支付等。每种支付方式的接口和处理流程可能都不同。

策略模式实现:

// 3.2.1 定义支付策略 const paymentStrategies = { creditCard: { processPayment: (amount, paymentDetails) => { console.log(`使用信用卡支付 ${amount} 元`); // 模拟信用卡支付逻辑,可能调用第三方API if (paymentDetails.cardNumber && paymentDetails.expiry && paymentDetails.cvv) { console.log(`信用卡号: **** **** **** ${paymentDetails.cardNumber.slice(-4)}`); return { success: true, message: '信用卡支付成功。' }; } return { success: false, message: '信用卡信息不完整。' }; } }, paypal: { processPayment: (amount, paymentDetails) => { console.log(`使用PayPal支付 ${amount} 元`); // 模拟PayPal支付逻辑 if (paymentDetails.paypalAccount) { console.log(`PayPal账户: ${paymentDetails.paypalAccount}`); return { success: true, message: 'PayPal支付成功。' }; } return { success: false, message: 'PayPal账户信息缺失。' }; } }, alipay: { processPayment: (amount, paymentDetails) => { console.log(`使用支付宝支付 ${amount} 元`); // 模拟支付宝支付逻辑 if (paymentDetails.alipayId) { console.log(`支付宝ID: ${paymentDetails.alipayId}`); // 实际会生成二维码或跳转到支付宝APP return { success: true, message: '支付宝支付待扫码确认。' }; } return { success: false, message: '支付宝ID缺失。' }; } }, wechatPay: { processPayment: (amount, paymentDetails) => { console.log(`使用微信支付 ${amount} 元`); // 模拟微信支付逻辑 if (paymentDetails.wechatOpenId) { console.log(`微信OpenID: ${paymentDetails.wechatOpenId}`); // 实际会生成二维码或跳转到微信APP return { success: true, message: '微信支付待确认。' }; } return { success: false, message: '微信OpenID缺失。' }; } } }; // 3.2.2 定义上下文 (PaymentProcessor) class PaymentProcessor { constructor() { this.currentStrategy = null; } setPaymentStrategy(strategyName) { const strategy = paymentStrategies[strategyName]; if (!strategy || typeof strategy.processPayment !== 'function') { throw new Error(`未知或无效的支付策略: ${strategyName}`); } this.currentStrategy = strategy; } executePayment(amount, paymentDetails) { if (!this.currentStrategy) { throw new Error('未设置支付策略。'); } return this.currentStrategy.processPayment(amount, paymentDetails); } } // 使用示例 console.log("n--- 支付处理 (策略模式) ---"); const processor = new PaymentProcessor(); const orderAmount = 250.75; // 使用信用卡支付 processor.setPaymentStrategy('creditCard'); const ccResult = processor.executePayment(orderAmount, { cardNumber: '1234567890123456', expiry: '12/25', cvv: '123' }); console.log("信用卡支付结果:", ccResult); // 使用PayPal支付 processor.setPaymentStrategy('paypal'); const paypalResult = processor.executePayment(orderAmount, { paypalAccount: 'user@example.com' }); console.log("PayPal支付结果:", paypalResult); // 使用支付宝支付 processor.setPaymentStrategy('alipay'); const alipayResult = processor.executePayment(orderAmount, { alipayId: 'alipay_user_123' }); console.log("支付宝支付结果:", alipayResult); // 尝试使用不存在的策略 try { processor.setPaymentStrategy('bankTransfer'); } catch (error) { console.error("设置支付策略失败:", error.message); }

这个例子将每种支付方式的复杂逻辑封装在各自的策略中,PaymentProcessor只负责接收请求并转发给当前的策略,极大地简化了支付系统的扩展和维护。

3.3 日志记录

一个应用可能需要在不同的环境中以不同的方式记录日志:开发环境输出到控制台,生产环境写入文件或发送到远程日志服务。

// 3.3.1 定义日志策略 const loggerStrategies = { consoleLogger: { log: (level, message, timestamp = new Date().toISOString()) => { console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`); }, error: (message, timestamp = new Date().toISOString()) => { console.error(`[${timestamp}] [ERROR] ${message}`); }, warn: (message, timestamp = new Date().toISOString()) => { console.warn(`[${timestamp}] [WARN] ${message}`); } }, // 模拟文件日志,实际可能需要Node.js的fs模块 fileLogger: { log: (level, message, timestamp = new Date().toISOString()) => { const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}n`; // fs.appendFileSync('app.log', logEntry); // 生产环境实际的文件写入 console.log(`[FILE_LOG] ${logEntry.trim()}`); // 模拟输出到控制台 }, error: (message, timestamp = new Date().toISOString()) => { const logEntry = `[${timestamp}] [ERROR] ${message}n`; // fs.appendFileSync('error.log', logEntry); console.error(`[FILE_ERROR_LOG] ${logEntry.trim()}`); }, warn: (message, timestamp = new Date().toISOString()) => { const logEntry = `[${timestamp}] [WARN] ${message}n`; // fs.appendFileSync('app.log', logEntry); console.warn(`[FILE_WARN_LOG] ${logEntry.trim()}`); } }, // 模拟远程日志服务,实际可能发送HTTP请求 remoteLogger: { log: (level, message, timestamp = new Date().toISOString()) => { const logData = { level, message, timestamp, source: 'webapp' }; // fetch('/api/log', { method: 'POST', body: JSON.stringify(logData) }); console.log(`[REMOTE_LOG_SENT] ${JSON.stringify(logData)}`); }, error: (message, timestamp = new Date().toISOString()) => { const logData = { level: 'error', message, timestamp, source: 'webapp' }; // fetch('/api/log', { method: 'POST', body: JSON.stringify(logData) }); console.error(`[REMOTE_ERROR_SENT] ${JSON.stringify(logData)}`); }, warn: (message, timestamp = new Date().toISOString()) => { const logData = { level: 'warn', message, timestamp, source: 'webapp' }; // fetch('/api/log', { method: 'POST', body: JSON.stringify(logData) }); console.warn(`[REMOTE_WARN_SENT] ${JSON.stringify(logData)}`); } } }; // 3.3.2 定义上下文 (Logger) class Logger { constructor(initialStrategyName = 'consoleLogger') { this.setStrategy(initialStrategyName); } setStrategy(strategyName) { const strategy = loggerStrategies[strategyName]; if (!strategy || typeof strategy.log !== 'function') { console.warn(`未知或无效的日志策略: ${strategyName}. 切换回控制台日志.`); this.currentStrategy = loggerStrategies.consoleLogger; return; } this.currentStrategy = strategy; console.log(`日志策略已切换为: ${strategyName}`); } log(message) { this.currentStrategy.log('info', message); } error(message) { this.currentStrategy.error(message); } warn(message) { this.currentStrategy.warn(message); } } // 使用示例 console.log("n--- 日志记录 (策略模式) ---"); const appLogger = new Logger(); appLogger.log("应用程序启动成功。"); appLogger.warn("检测到潜在问题。"); appLogger.error("关键服务连接失败!"); appLogger.setStrategy('fileLogger'); // 运行时切换日志策略 appLogger.log("用户数据已保存。"); appLogger.error("数据库写入失败!"); appLogger.setStrategy('remoteLogger'); appLogger.log("前端事件捕获。");

通过策略模式,我们可以轻松地在开发、测试和生产环境中切换不同的日志记录行为,而无需修改应用程序中调用日志记录的代码。

3.4 排序算法

需要对数据集合进行不同方式的排序(升序、降序、按特定属性排序)。

// 3.4.1 定义排序策略 const sortStrategies = { // 默认升序 asc: { sort: (arr) => [...arr].sort((a, b) => a - b) }, // 降序 desc: { sort: (arr) => [...arr].sort((a, b) => b - a) }, // 按指定属性升序 byPropertyAsc: { sort: (arr, prop) => [...arr].sort((a, b) => { if (a[prop] < b[prop]) return -1; if (a[prop] > b[prop]) return 1; return 0; }) }, // 按指定属性降序 byPropertyDesc: { sort: (arr, prop) => [...arr].sort((a, b) => { if (a[prop] > b[prop]) return -1; if (a[prop] < b[prop]) return 1; return 0; }) } }; // 3.4.2 定义上下文 (Sorter) class Sorter { constructor(initialStrategyName = 'asc') { this.setStrategy(initialStrategyName); } setStrategy(strategyName) { const strategy = sortStrategies[strategyName]; if (!strategy || typeof strategy.sort !== 'function') { console.warn(`未知或无效的排序策略: ${strategyName}. 切换回默认升序.`); this.currentStrategy = sortStrategies.asc; return; } this.currentStrategy = strategy; } executeSort(arr, ...args) { return this.currentStrategy.sort(arr, ...args); } } // 使用示例 console.log("n--- 排序算法 (策略模式) ---"); const numbers = [5, 2, 8, 1, 9, 3]; const objects = [ { name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }, { name: 'Charlie', age: 35 } ]; const sorter = new Sorter(); // 默认升序 console.log("原始数字数组:", numbers); console.log("升序排序:", sorter.executeSort(numbers)); // [1, 2, 3, 5, 8, 9] // 降序 sorter.setStrategy('desc'); console.log("降序排序:", sorter.executeSort(numbers)); // [9, 8, 5, 3, 2, 1] // 按年龄升序 console.log("n原始对象数组:", objects); sorter.setStrategy('byPropertyAsc'); console.log("按年龄升序:", sorter.executeSort(objects, 'age')); // [{ name: 'Bob', age: 25 }, { name: 'Alice', age: 30 }, { name: 'Charlie', age: 35 }] // 按年龄降序 sorter.setStrategy('byPropertyDesc'); console.log("按年龄降序:", sorter.executeSort(objects, 'age')); // [{ name: 'Charlie', age: 35 }, { name: 'Alice', age: 30 }, { name: 'Bob', age: 25 }] // 尝试使用不存在的策略 sorter.setStrategy('randomSort'); // 会警告并切换回默认升序 console.log("尝试随机排序 (回退到升序):", sorter.executeSort(numbers));

这个例子展示了如何将不同的排序逻辑封装为策略,使得Sorter上下文可以灵活地处理各种排序需求。

4. 策略模式的优势与考量

4.1 策略模式的显著优势
  • 符合开放/封闭原则 (Open/Closed Principle – OCP):

    • 对扩展开放:可以轻松添加新的策略,而无需修改现有代码。
    • 对修改封闭:上下文类无需因为新策略的引入而修改。这是策略模式最核心的优势之一。
  • 符合单一职责原则 (Single Responsibility Principle – SRP):

    • 每个策略只负责一个算法。
    • 上下文只负责管理策略的切换和执行,不包含具体的算法逻辑。
    • 职责分离使得代码更清晰、更易于理解和维护。
  • 消除大量的条件语句:

    • 避免了if/else if/elseswitch语句块的膨胀,使代码结构更扁平。
  • 提高代码的复用性:

    • 策略可以被不同的上下文或在不同的场景中复用。
  • 增强可测试性:

    • 每个策略都是独立的单元,可以独立进行单元测试,从而更容易发现和修复问题。
  • 运行时动态切换行为:

    • 客户端可以在运行时根据需要选择或改变上下文的行为。
  • 提升代码可读性:

    • 通过将复杂的逻辑分解为更小、更专注的策略,代码意图更加清晰。
4.2 策略模式的潜在劣势与考量
  • 增加对象数量:

    • 每增加一个算法,就需要增加一个对应的策略对象。当算法数量非常多时,可能会导致类的数量剧增,增加项目的复杂性。
  • 客户端需要了解所有策略:

    • 客户端通常需要知道所有可用的具体策略,以便选择合适的策略传递给上下文。这可以通过结合工厂模式来缓解,让工厂负责创建和提供策略对象。
  • 过度设计 (Over-engineering) 的风险:

    • 如果算法逻辑非常简单,或者预计未来不会有太多变化,引入策略模式可能会增加不必要的复杂性和样板代码。在这种情况下,简单的条件语句可能更直接。
    • 在考虑使用策略模式时,应评估算法变化的可能性和复杂性。
  • 性能开销(微乎其微):

    • 额外的函数调用和对象创建可能会带来微小的性能开销,但在绝大多数现代应用中,这种开销几乎可以忽略不计。

5. 策略模式与其他设计模式的关系

策略模式并不是孤立存在的,它经常与其他设计模式结合使用,以发挥更大的作用。

5.1 策略模式与工厂模式 (Factory Pattern)
  • 结合点:客户端通常需要知道所有具体策略的名称才能选择它们。当策略的数量很大或者策略的创建逻辑比较复杂时,这会成为一个负担。
  • 解决方案:可以使用工厂模式来封装策略对象的创建过程。客户端不再直接创建策略对象,而是通过工厂来获取。工厂可以根据传入的参数(如策略类型字符串)返回对应的具体策略实例。
  • 好处:进一步解耦了客户端和具体策略,客户端只需要知道工厂的接口和策略的标识符,而无需关心具体策略类的名称和创建细节。

示例:结合工厂模式的折扣计算器

// 5.1.1 定义策略(同上) const discountStrategiesFactory = { none: { calculate: (initialAmount, discountValue, itemPrice, quantity) => { console.log("工厂策略:无折扣"); return initialAmount; } }, percentage: { calculate: (initialAmount, discountPercentage, itemPrice, quantity) => { console.log(`工厂策略:百分比折扣 ${discountPercentage * 100}%`); return initialAmount * (1 - discountPercentage); } }, fixedAmount: { calculate: (initialAmount, fixedDiscountAmount, itemPrice, quantity) => { console.log(`工厂策略:固定金额折扣 减${fixedDiscountAmount}元`); let finalPrice = initialAmount - fixedDiscountAmount; return finalPrice < 0 ? 0 : finalPrice; } } // ... 其他策略 }; // 5.1.2 策略工厂 class DiscountStrategyFactory { static getStrategy(strategyName) { const strategy = discountStrategiesFactory[strategyName]; if (!strategy || typeof strategy.calculate !== 'function') { console.warn(`工厂模式:未知或无效的折扣策略: ${strategyName}. 返回默认无折扣策略.`); return discountStrategiesFactory.none; } return strategy; } } // 5.1.3 上下文(与之前相同,但设置策略时通过工厂获取) class PriceCalculatorWithFactory { constructor(itemPrice, quantity) { this.itemPrice = itemPrice; this.quantity = quantity; this.initialAmount = itemPrice * quantity; this.currentStrategy = DiscountStrategyFactory.getStrategy('none'); // 默认策略 this.discountValue = null; } setStrategy(strategyName, discountValue = null) { this.currentStrategy = DiscountStrategyFactory.getStrategy(strategyName); this.discountValue = discountValue; } calculateFinalPrice() { return this.currentStrategy.calculate( this.initialAmount, this.discountValue, this.itemPrice, this.quantity ); } } // 使用示例 console.log("n--- 策略模式与工厂模式结合 ---"); const productPriceF = 120; const productQuantityF = 3; const calculatorF = new PriceCalculatorWithFactory(productPriceF, productQuantityF); console.log(`初始总价: ${calculatorF.initialAmount}`); // 360 calculatorF.setStrategy('percentage', 0.15); // 15% off console.log("最终价格 (15% 折扣):", calculatorF.calculateFinalPrice()); // 360 * 0.85 = 306 calculatorF.setStrategy('fixedAmount', 50); // 减50元 console.log("最终价格 (减50元):", calculatorF.calculateFinalPrice()); // 360 - 50 = 310 calculatorF.setStrategy('unknownStrategy'); // 使用不存在的策略 console.log("最终价格 (未知策略,回退到无折扣):", calculatorF.calculateFinalPrice()); // 360

现在,PriceCalculatorWithFactorysetStrategy方法不再直接访问discountStrategies对象,而是委托给DiscountStrategyFactory,从而将策略的创建/获取逻辑进一步抽象。

5.2 策略模式与模板方法模式 (Template Method Pattern)
  • 区别与联系:
    • 策略模式:关注“做什么”(封装不同的算法)。它通过将整个算法替换掉来改变行为。
    • 模板方法模式:关注“如何做”(定义算法的骨架,将某些步骤延迟到子类)。它通过允许子类覆盖特定步骤来改变行为,但算法的总体结构保持不变。
  • 结合使用:如果不同的策略中存在一些共同的、固定的步骤,而只有少数步骤是可变的,那么可以在策略内部使用模板方法模式。例如,所有支付策略可能都有“验证凭证”和“记录交易日志”的固定步骤,但“实际扣款”步骤则各不相同。此时,可以定义一个抽象支付策略,其中包含模板方法,具体的支付策略继承并实现其可变步骤。

6. JavaScript 中的实现变体

在 JavaScript 中,策略模式的实现可以非常灵活,不拘泥于传统的面向对象类结构。

6.1 使用纯函数作为策略

这是最简洁也最“JavaScript-native”的方式。每个策略就是一个函数。

// 策略集合 const mathOperations = { add: (a, b) => a + b, subtract: (a, b) => a - b, multiply: (a, b) => a * b, divide: (a, b) => { if (b === 0) throw new Error("Cannot divide by zero."); return a / b; } }; // 上下文 class Calculator { constructor() { this.currentOperation = mathOperations.add; // 默认加法 } setOperation(operationName) { const operation = mathOperations[operationName]; if (typeof operation !== 'function') { throw new Error(`Invalid operation: ${operationName}`); } this.currentOperation = operation; } execute(a, b) { return this.currentOperation(a, b); } } // 使用 console.log("n--- JS纯函数策略 ---"); const calc = new Calculator(); console.log("默认加法 (5 + 3):", calc.execute(5, 3)); // 8 calc.setOperation('multiply'); console.log("乘法 (5 * 3):", calc.execute(5, 3)); // 15 calc.setOperation('divide'); console.log("除法 (10 / 2):", calc.execute(10, 2)); // 5

这种方式特别适合策略逻辑简单、不需要维护内部状态的情况。

6.2 使用 ES6 Class 作为策略

当策略需要维护一些内部状态,或者需要更复杂的初始化逻辑时,使用 ES6 Class 是一个不错的选择。

// 策略接口 (隐式) // class DiscountStrategy { // calculate(initialAmount, discountValue, itemPrice, quantity) { // throw new Error("Method 'calculate()' must be implemented."); // } // } // 具体策略类 class NoDiscountStrategy { calculate(initialAmount) { console.log("Class策略:无折扣"); return initialAmount; } } class PercentageDiscountStrategy { constructor(percentage) { this.percentage = percentage; } calculate(initialAmount) { console.log(`Class策略:百分比折扣 ${this.percentage * 100}%`); return initialAmount * (1 - this.percentage); } } class FixedAmountDiscountStrategy { constructor(amount) { this.amount = amount; } calculate(initialAmount) { console.log(`Class策略:固定金额折扣 减${this.amount}元`); let finalPrice = initialAmount - this.amount; return finalPrice < 0 ? 0 : finalPrice; } } // 策略映射(结合工厂模式) const classDiscountStrategies = { none: NoDiscountStrategy, percentage: PercentageDiscountStrategy, fixedAmount: FixedAmountDiscountStrategy }; // 上下文 class PriceCalculatorWithClassStrategy { constructor(itemPrice, quantity) { this.itemPrice = itemPrice; this.quantity = quantity; this.initialAmount = itemPrice * quantity; this.currentStrategy = new NoDiscountStrategy(); // 默认策略实例 } setStrategy(strategyName, ...args) { const StrategyClass = classDiscountStrategies[strategyName]; if (!StrategyClass) { console.warn(`Class策略:未知或无效的折扣策略: ${strategyName}. 使用默认无折扣策略.`); this.currentStrategy = new NoDiscountStrategy(); return; } this.currentStrategy = new StrategyClass(...args); // 实例化策略 } calculateFinalPrice() { return this.currentStrategy.calculate(this.initialAmount); } } // 使用 console.log("n--- JS Class策略 ---"); const productPriceC = 80; const productQuantityC = 4; // 初始总价 320 const calculatorC = new PriceCalculatorWithClassStrategy(productPriceC, productQuantityC); calculatorC.setStrategy('none'); console.log("最终价格 (无折扣):", calculatorC.calculateFinalPrice()); // 320 calculatorC.setStrategy('percentage', 0.2); // 20% off console.log("最终价格 (20% 折扣):", calculatorC.calculateFinalPrice()); // 256 calculatorC.setStrategy('fixedAmount', 70); // 减70元 console.log("最终价格 (减70元):", calculatorC.calculateFinalPrice()); // 250

这种方式提供了更强的封装性和可维护性,特别是在策略逻辑复杂或需要内部状态时。

6.3 使用 ES6 Modules 组织策略

随着项目规模的增长,将每个策略放在一个独立的模块文件中是最佳实践,可以提高代码的可维护性和可复用性。

// discount-strategies/NoDiscountStrategy.js /* export class NoDiscountStrategy { calculate(initialAmount) { console.log("模块策略:无折扣"); return initialAmount; } } */ // discount-strategies/PercentageDiscountStrategy.js /* export class PercentageDiscountStrategy { constructor(percentage) { this.percentage = percentage; } calculate(initialAmount) { console.log(`模块策略:百分比折扣 ${this.percentage * 100}%`); return initialAmount * (1 - this.percentage); } } */ // discount-strategies/index.js (策略集合和导出) /* import { NoDiscountStrategy } from './NoDiscountStrategy.js'; import { PercentageDiscountStrategy } from './PercentageDiscountStrategy.js'; // ... 导入其他策略 export const discountStrategiesModule = { none: NoDiscountStrategy, percentage: PercentageDiscountStrategy, // ... }; */ // main.js (使用) /* import { discountStrategiesModule } from './discount-strategies/index.js'; class PriceCalculatorWithModules { // ... 构造函数和方法同上,但在 setStrategy 中使用 discountStrategiesModule setStrategy(strategyName, ...args) { const StrategyClass = discountStrategiesModule[strategyName]; if (!StrategyClass) { this.currentStrategy = new discountStrategiesModule.none(); return; } this.currentStrategy = new StrategyClass(...args); } // ... } */ 通过模块化,每个策略文件职责单一,易于查找、修改和测试。`index.js` 文件作为策略的入口,方便上下文统一导入和使用。 ### 7. 结语 策略模式是设计模式中的基石之一,它通过将算法从其使用上下文中分离出来,提供了一种优雅的、可替换的算法逻辑实现方式。无论是在前端的交互逻辑、数据验证,还是在后端的业务规则、支付处理、日志记录等场景,策略模式都能显著提升代码的灵活性、可维护性和可扩展性。掌握并熟练运用策略模式,将使您的 JavaScript 代码更加健壮、更具弹性,更好地应对不断变化的需求。在实践中,我们应根据具体场景和策略的复杂程度,灵活选择使用纯函数、对象字面量或 ES6 Class 来实现策略,并可以结合工厂模式等其他模式来进一步优化设计。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/25 13:40:55

Web Worker 与 SharedWorker 的区别:实现跨 Tab 页的 WebSocket 连接共享

各位技术同仁&#xff0c;大家好&#xff01;今天我们将深入探讨Web Worker和SharedWorker这两种强大的Web API&#xff0c;并着重讲解它们在实现跨多个浏览器Tab页共享WebSocket连接这一复杂场景中的应用。在现代Web应用中&#xff0c;实时通信已成为标配&#xff0c;而WebSoc…

作者头像 李华
网站建设 2025/12/16 22:54:33

YashanDB数据库的跨平台迁移策略及实操经验

YashanDB 是一个相对较新的数据库&#xff0c;关于它的跨平台迁移策略和实操经验的文档和资料可能不如一些成熟的数据库系统丰富&#xff0c;但可以参考一些通用数据库迁移的策略和经验&#xff0c;以下是一些关键点&#xff1a;跨平台迁移策略1. 评估现有环境&#xff1a;- 确…

作者头像 李华
网站建设 2025/12/16 22:54:00

Http概述

文章目录Web基础-HTTP1、什么是项目2、什么是架构&#xff1f;3、架构所需关键词4、什么是集群&#xff1f;5、什么是负载均衡&#xff1f;6、http概述6.1、Web状态访问码6.2、Web的结构组成6.3、有哪些Web资源&#xff1f;6.4、HTTP的工作原理6.5、HTTP请求响应6.6、HTTP相关术…

作者头像 李华
网站建设 2025/12/16 22:53:08

线性系统(非线性系统)

线性系统&#xff08;非线性系统&#xff09; 若任意x(t)–系统–>y(t) &#xff0c;则有ax(t)–系统–>ay(t)x1(t)–系统–>y1(t) x2(t)–系统–>y2(t) > x1(t) x2(t) --系统–> y1(t) y2(t)同时满足12 则是线性系统 齐次性 叠加性线性系统举例&#…

作者头像 李华
网站建设 2025/12/16 22:52:26

LaTeX公式转换终极指南:从网页到Word的完整解决方案

在学术写作和科研工作中&#xff0c;LaTeX公式与Word文档的格式转换一直是研究人员面临的常见挑战。传统方法需要手动重新输入复杂的数学表达式&#xff0c;不仅耗时费力&#xff0c;还容易引入错误。LaTeX2Word-Equation作为一款专业的Chrome扩展工具&#xff0c;完美解决了这…

作者头像 李华