js设计模式之策略模式


#【前言】

        在 JavaScript 前端开发中,随着代码规模的增长和项目的复杂性,我们常常需要处理各种不同的条件和情况,而这可能导致代码变得冗长、难以维护。这时,我们就需要一种强大而灵活的编程模式来应对这些复杂的逻辑,策略模式就是其中之一。

        独立的策略类的优势在于它们可以更加灵活地组织和管理不同的策略,每个策略类都可以有自己独立的实现。这使得添加新的策略变得更加简单,只需要创建一个新的策略类,并将其传递给计算函数即可。

        通过掌握策略模式,相信大家都将能够编写更加灵活、可扩展的代码,提高你的开发效率和代码质量。


#【举例】

        下面举个简单的例子:

// Step 1: 定义策略函数
const strategyA = function(arg) {  return arg * 2;};
const strategyB = function(arg) {  return arg * arg;};

// Step 2: 创建上下文对象
const context = {
  strategy: null, // 用于存储所选的策略函数
  setStrategy: function(strategy) {
    this.strategy = strategy;
  },
  executeStrategy: function(arg) {
    return this.strategy(arg);
  }
};

// Step 3: 实现选择策略的方法
context.setStrategy(strategyA); // 选择策略A
// Step 4: 调用所选的策略函数
const resultA = context.executeStrategy(3);
console.log(resultA); // 输出: 6

context.setStrategy(strategyB); // 选择策略B

const resultB = context.executeStrategy(3);
console.log(resultB); // 输出: 9

在上述例子中,有两个关键角色:

关键1:策略类:可以定义不同的策略类,统一策略类的结构和接口,确保每个策略类都实现了相同的方法,以及提供一些通用的行为。基类可以定义抽象方法,而具体的策略类则继承基类并实现这些抽象方法。

关键2:上下文对象(或者基类):上下文对象通常用于封装策略的选择和执行过程。它可以包含对策略对象的引用,并提供方法来设置和获取当前的策略,以及执行相关的操作。上下文对象可以根据需要在运行时切换策略,以及在不同的上下文中共享数据。

在策略模式中,使用上下文对象或基类是一种常见的实现方式,但并不是必须的。这些概念的使用可以提供一些额外的好处和便利性,但并非策略模式的核心要素。

下面是一个示例,展示了如何使用独立的策略类来计算订单的总价:

// 定义支付策略类

class PaymentStrategy {
  calculatePaymentAmount(order) {
    throw new Error("calculatePaymentAmount() method must be implemented.");
  }
}

// 定义具体的支付策略类

class CreditCardPaymentStrategy extends PaymentStrategy {
  calculatePaymentAmount(order) {
    // 根据信用卡支付的逻辑计算订单总价
    const subtotal = order.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
    const shippingFee = calculateShippingFee(order);
    const total = subtotal + shippingFee;

    return total;
  }
}

class PayPalPaymentStrategy extends PaymentStrategy {
  calculatePaymentAmount(order) {
    // 根据 PayPal 支付的逻辑计算订单总价
    const subtotal = order.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
    const tax = calculateTax(order);
    const total = subtotal + tax;

    return total;
  }
}

// 计算订单的函数

function calculateOrderTotal(order, paymentStrategy) {
  const total = paymentStrategy.calculatePaymentAmount(order);
  return total;
}

// 示例用法

const order = {
  items: [
    { name: "Product 1", price: 10, quantity: 2 },
    { name: "Product 2", price: 5, quantity: 3 },
  ],
};

const creditCardPayment = new CreditCardPaymentStrategy();
const payPalPayment = new PayPalPaymentStrategy();

const creditCardTotal = calculateOrderTotal(order, creditCardPayment);
const payPalTotal = calculateOrderTotal(order, payPalPayment);

console.log("Credit Card Total:", creditCardTotal);
console.log("PayPal Total:", payPalTotal);

在上述示例中,我们定义了一个 PaymentStrategy 基类,它包含一个抽象的 calculatePaymentAmount() 方法。然后,我们派生了两个具体的策略类 CreditCardPaymentStrategy 和 PayPalPaymentStrategy,它们分别实现了 calculatePaymentAmount() 方法以提供不同的支付逻辑。

接下来,我们定义了一个 calculateOrderTotal() 函数,它接受一个订单对象和一个支付策略对象作为参数,并使用指定的策略对象计算订单的总价。

最后,我们创建了一个示例订单对象,并使用不同的支付策略对象计算订单的总价。输出结果显示了使用信用卡支付和 PayPal 支付的订单总价。

在示例中,extends PaymentStrategy 表示 CreditCardPaymentStrategy  PayPalPaymentStrategy 类是 PaymentStrategy 类的子类(或称为派生类)。通过继承 PaymentStrategy 类,子类可以继承其属性和方法,并且可以根据需要进行自定义或重写。

使用上下文对象或基类并不是策略模式的必须要素。你可以单独编写多个策略类,而不必将它们作为基类的子类。

class CreditCardPaymentStrategy {
  calculatePaymentAmount(order) {
    // 根据信用卡支付的逻辑计算订单总价
    const subtotal = order.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
    const shippingFee = calculateShippingFee(order);
    const total = subtotal + shippingFee;

    return total;
  }
}

class PayPalPaymentStrategy {
  calculatePaymentAmount(order) {
    // 根据 PayPal 支付的逻辑计算订单总价
    const subtotal = order.items.reduce((acc, item) => acc + item.price * item.quantity, 0);
    const tax = calculateTax(order);
    const total = subtotal + tax;

    return total;
  }
}

// 计算订单的函数

function calculateOrderTotal(order, paymentStrategy) {
  const total = paymentStrategy.calculatePaymentAmount(order);
  return total;
}

// 示例用法

const order = {
  items: [
    { name: "Product 1", price: 10, quantity: 2 },
    { name: "Product 2", price: 5, quantity: 3 },
  ],
};

const creditCardPayment = new CreditCardPaymentStrategy();
const payPalPayment = new PayPalPaymentStrategy();

const creditCardTotal = calculateOrderTotal(order, creditCardPayment);
const payPalTotal = calculateOrderTotal(order, payPalPayment);

console.log("Credit Card Total:", creditCardTotal);
console.log("PayPal Total:", payPalTotal);

以上例子能有一样的效果作用。

上下文对象通常用于封装策略的选择和执行过程。它可以包含对策略对象的引用,并提供方法来设置和获取当前的策略,以及执行相关的操作。上下文对象可以根据需要在运行时切换策略,以及在不同的上下文中共享数据。

基类(或接口)的使用可以带来一些优势,例如统一策略类的结构和接口,确保每个策略类都实现了相同的方法,以及提供一些通用的行为。基类可以定义抽象方法,而具体的策略类则继承基类并实现这些抽象方法。

然而,使用上下文对象或基类并不是策略模式的必须要素。在最简单的形式下,策略模式只需要定义独立的策略类,它们实现了相同的接口或具有相同的方法。这些策略类可以直接在客户端代码中使用,或者通过某种方式进行管理和切换。

最终选择使用上下文对象、基类或其他组织方式取决于具体的需求和设计考虑。这些概念可以提供更高层次的抽象和灵活性,但在简单的情况下也可以被省略。



#【应用场景】

表单验证:策略模式可以用于表单验证,其中每个验证规则可以被封装为一个策略类。例如,可以有一个策略类用于检查是否为空、另一个策略类用于检查是否满足特定的正则表达式,以及其他各种验证规则。根据不同的验证需求,可以在运行时选择不同的验证策略。

class FormValidator {
  constructor() {
    this.validationStrategy = null;
  }

  setValidationStrategy(strategy) {
    this.validationStrategy = strategy;
  }

  validateInput(input) {
    if (this.validationStrategy) {
      return this.validationStrategy.validate(input);
    }
    return false;
  }
}
//RequiredFieldStrategy 用于检查字段是否为空
class RequiredFieldStrategy {
  validate(input) {
    return input.trim() !== '';
  }
}
//EmailFieldStrategy 用于检查字段是否为有效的电子邮件地址
class EmailFieldStrategy {
  validate(input) {
    const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
    return emailRegex.test(input);
  }
}
const formValidator = new FormValidator();

// 设置验证策略为必填字段验证
formValidator.setValidationStrategy(new RequiredFieldStrategy());
console.log(formValidator.validateInput('')); // false
console.log(formValidator.validateInput('John Doe')); // true

// 设置验证策略为电子邮件验证
formValidator.setValidationStrategy(new EmailFieldStrategy());
console.log(formValidator.validateInput('john@example.com')); // true
console.log(formValidator.validateInput('invalid-email')); // false

//FormValidator 类作为验证的上下文,通过调用 setValidationStrategy() 方法来设置不同的验证策略。
//然后,调用 validateInput() 方法来执行验证操作,并返回验证结果。


排序算法:策略模式可以用于排序算法的实现。不同的排序算法(如冒泡排序、快速排序、插入排序等)可以被封装为独立的策略类。根据运行时的需求,可以选择不同的排序策略进行排序操作。

class SortContext {
  constructor() {
    this.sortingStrategy = null;
  }

  setSortingStrategy(strategy) {
    this.sortingStrategy = strategy;
  }

  sortArray(array) {
    if (this.sortingStrategy) {
      return this.sortingStrategy.sort(array);
    }
    return array;
  }
}

class BubbleSortStrategy {
  sort(array) {
    // 冒泡排序算法实现
    // ...
    return sortedArray;
  }
}

class QuickSortStrategy {
  sort(array) {
    // 快速排序算法实现
    // ...
    return sortedArray;
  }
}

const sortContext = new SortContext();

const unsortedArray = [5, 3, 8, 1, 4];
console.log('Unsorted array:', unsortedArray);

// 设置排序策略为冒泡排序
sortContext.setSortingStrategy(new BubbleSortStrategy());
const sortedArray1 = sortContext.sortArray(unsortedArray);
console.log('Sorted array with bubble sort:', sortedArray1);

// 设置排序策略为快速排序
sortContext.setSortingStrategy(new QuickSortStrategy());
const sortedArray2 = sortContext.sortArray(unsortedArray);
console.log('Sorted array with quick sort:', sortedArray2);


计算器应用:策略模式可以用于实现计算器应用,其中每个操作符(如加法、减法、乘法、除法等)可以被封装为一个策略类。根据用户输入的操作符,可以动态选择相应的策略进行计算。

//定义一个计算器的上下文类 Calculator,它负责接收用户输入和选择计算策略
class Calculator {
  constructor() {
    this.calculationStrategy = null;
  }

  setCalculationStrategy(strategy) {
    this.calculationStrategy = strategy;
  }

  calculate(num1, num2) {
    if (this.calculationStrategy) {
      return this.calculationStrategy.calculate(num1, num2);
    }
    return NaN;
  }
}
//定义了四个策略类:AdditionStrategy 用于执行加法操作,SubtractionStrategy 用于执行减法操作,
//MultiplicationStrategy 用于执行乘法操作,DivisionStrategy 用于执行除法操作。
class AdditionStrategy {
  calculate(num1, num2) {
    return num1 + num2;
  }
}

class SubtractionStrategy {
  calculate(num1, num2) {
    return num1 - num2;
  }
}

class MultiplicationStrategy {
  calculate(num1, num2) {
    return num1 * num2;
  }
}

class DivisionStrategy {
  calculate(num1, num2) {
    if (num2 !== 0) {
      return num1 / num2;
    }
    return NaN;
  }
}
//策略类来进行计算
const calculator = new Calculator();

const num1 = 10;
const num2 = 5;

// 设置计算策略为加法
calculator.setCalculationStrategy(new AdditionStrategy());
console.log(`Addition: ${num1} + ${num2} = ${calculator.calculate(num1, num2)}`);

// 设置计算策略为减法
calculator.setCalculationStrategy(new SubtractionStrategy());
console.log(`Subtraction: ${num1} - ${num2} = ${calculator.calculate(num1, num2)}`);

// 设置计算策略为乘法
calculator.setCalculationStrategy(new MultiplicationStrategy());
console.log(`Multiplication: ${num1} * ${num2} = ${calculator.calculate(num1, num2)}`);

// 设置计算策略为除法
calculator.setCalculationStrategy(new DivisionStrategy());
console.log(`Division: ${num1} / ${num2} = ${calculator.calculate(num1, num2)}`);



缓存策略:策略模式可以用于实现缓存策略,其中每个缓存策略可以被封装为一个策略类。例如,可以有一个策略类用于基于时间的过期策略,另一个策略类用于基于大小的淘汰策略等。根据不同的缓存需求,可以在运行时选择不同的缓存策略。

//定义一个缓存管理器的上下文类 CacheManager,它负责接收缓存键值对和选择缓存策略
class CacheManager {
  constructor() {
    this.cacheStrategy = null;
    this.cache = {};
  }

  setCacheStrategy(strategy) {
    this.cacheStrategy = strategy;
  }

  getValue(key) {
    if (this.cacheStrategy && this.cacheStrategy.hasKey(key)) {
      return this.cacheStrategy.getValue(key);
    }
    return null;
  }

  setValue(key, value) {
    if (this.cacheStrategy) {
      this.cacheStrategy.setValue(key, value);
    }
  }
}
//LRUCacheStrategy 使用最近最少使用算法来管理缓存
class LRUCacheStrategy {
  constructor() {
    this.cache = new Map();
  }

  hasKey(key) {
    return this.cache.has(key);
  }

  getValue(key) {
    const value = this.cache.get(key);
    // 更新缓存项的访问顺序
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  setValue(key, value) {
    // 添加缓存项,并检查缓存容量
    this.cache.set(key, value);
    if (this.cache.size > 10) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
  }
}
//FIFOCacheStrategy 使用先进先出算法来管理缓存
class FIFOCacheStrategy {
  constructor() {
    this.cache = new Map();
  }

  hasKey(key) {
    return this.cache.has(key);
  }

  getValue(key) {
    return this.cache.get(key);
  }

  setValue(key, value) {
    // 添加缓存项,并检查缓存容量
    this.cache.set(key, value);
    if (this.cache.size > 10) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
  }
}
//最后,可以使用这些策略类来进行缓存操作
const cacheManager = new CacheManager();

// 设置缓存策略为LRU缓存
cacheManager.setCacheStrategy(new LRUCacheStrategy());
cacheManager.setValue('key1', 'value1');
cacheManager.setValue('key2', 'value2');
console.log(cacheManager.getValue('key1')); // 输出:value1
cacheManager.setValue('key3', 'value3'); // 添加新的缓存项,最旧的缓存项被淘汰

// 设置缓存策略为FIFO缓存
cacheManager.setCacheStrategy(new FIFOCacheStrategy());
cacheManager.setValue('key4', 'value4');
cacheManager.setValue('key5', 'value5');
console.log(cacheManager.getValue('key1')); // 输出:null,LRU缓存已被替换
console.log(cacheManager.getValue('key4')); // 输出:value4,FIFO缓存仍存在


日志记录:策略模式可以用于实现日志记录策略,其中每个日志记录策略可以被封装为一个策略类。例如,可以有一个策略类用于将日志记录到文件中,另一个策略类用于将日志记录到数据库中等。根据不同的日志记录需求,可以在运行时选择不同的日志记录策略。

//定义一个日志记录器的上下文类 Logger,它负责接收日志消息和选择日志记录策略
class Logger {
  constructor() {
    this.logStrategy = null;
  }

  setLogStrategy(strategy) {
    this.logStrategy = strategy;
  }

  log(message) {
    if (this.logStrategy) {
      this.logStrategy.log(message);
    }
  }
}

//定义了两个策略类:ConsoleLogStrategy 将日志消息输出到控制台,FileLogStrategy 将日志消息写入文件
class ConsoleLogStrategy {
  log(message) {
    console.log(`[Console Log] ${message}`);
  }
}

class FileLogStrategy {
  log(message) {
    // 将日志消息写入文件的逻辑
    console.log(`[File Log] ${message}`);
  }
}
//最后,可以使用这些策略类来进行日志记录
const logger = new Logger();

// 设置日志记录策略为控制台日志
logger.setLogStrategy(new ConsoleLogStrategy());
logger.log('Log message 1'); // 日志消息将输出到控制台

// 设置日志记录策略为文件日志
logger.setLogStrategy(new FileLogStrategy());
logger.log('Log message 2'); // 日志消息将写入文件



声明:BenBonBen博客|版权所有,违者必究|如未注明,均为原创

转载:转载请注明原文链接 - js设计模式之策略模式


过去太迟,未来太远,当下最好