Как проще всего реализовать паттерн Strategy на JavaScript (и где его использовать в ноде)?nodejs-57

Паттерн Strategy позволяет определять семейство алгоритмов, инкапсулировать каждый из них и делать их взаимозаменяемыми. Это особенно полезно в Node.js для сценариев, где требуется гибкость в выборе поведения во время выполнения.

Простейшая реализация Strategy

Самый простой способ реализовать Strategy в JavaScript — использовать объект с набором функций:

const strategies = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b,
  multiply: (a, b) => a * b,
  divide: (a, b) => a / b
};

function calculate(strategyName, a, b) {
  if (!strategies[strategyName]) {
    throw new Error(`Unknown strategy: ${strategyName}`);
  }
  return strategies[strategyName](a, b);
}

// Использование
console.log(calculate('add', 5, 3));      // 8
console.log(calculate('multiply', 5, 3)); // 15

Более продвинутая реализация с классами

Для более сложных сценариев можно использовать классы:

class PaymentStrategy {
  pay(amount) {
    throw new Error('Method not implemented');
  }
}

class CreditCardStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying ${amount} using Credit Card`);
    // Логика обработки кредитной карты
  }
}

class PayPalStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying ${amount} using PayPal`);
    // Логика PayPal
  }
}

class CryptoStrategy extends PaymentStrategy {
  pay(amount) {
    console.log(`Paying ${amount} using Cryptocurrency`);
    // Логика криптоплатежа
  }
}

class PaymentProcessor {
  constructor() {
    this.strategy = null;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  processPayment(amount) {
    if (!this.strategy) {
      throw new Error('Payment strategy not set');
    }
    this.strategy.pay(amount);
  }
}

// Использование
const processor = new PaymentProcessor();

processor.setStrategy(new CreditCardStrategy());
processor.processPayment(100);

processor.setStrategy(new PayPalStrategy());
processor.processPayment(200);

Практические примеры использования в Node.js

1. Выбор адаптера для разных баз данных

const dbStrategies = {
  mongodb: require('./mongodb-adapter'),
  postgres: require('./postgres-adapter'),
  mysql: require('./mysql-adapter')
};

function getDatabaseAdapter(dbType) {
  return dbStrategies[dbType] || dbStrategies.mongodb; // fallback
}

const dbAdapter = getDatabaseAdapter(process.env.DB_TYPE);

2. Стратегии логирования

const logStrategies = {
  console: message => console.log(message),
  file: message => require('fs').appendFileSync('app.log', message + '\n'),
  remote: message => fetch('https://log-service.com', { method: 'POST', body: message })
};

function log(strategy, message) {
  logStrategies[strategy](new Date().toISOString() + ' ' + message);
}

3. Стратегии валидации запросов

const validationStrategies = {
  json: require('./json-validator'),
  xml: require('./xml-validator'),
  form: require('./form-validator')
};

function validateRequest(req, strategyName) {
  const validator = validationStrategies[strategyName];
  return validator.validate(req);
}

4. Стратегии кеширования

const cacheStrategies = {
  memory: require('./memory-cache'),
  redis: require('./redis-cache'),
  memcached: require('./memcached-cache')
};

function getCacheStrategy(strategyName) {
  return cacheStrategies[strategyName] || cacheStrategies.memory;
}

Преимущества использования Strategy в Node.js

  1. Гибкость: Легко добавлять новые стратегии без изменения существующего кода
  2. Тестируемость: Каждую стратегию можно тестировать изолированно
  3. Читаемость: Код становится более декларативным
  4. Отсутствие условных конструкций: Заменяет сложные switch/case или if/else

Когда использовать паттерн Strategy

  1. Когда есть несколько вариантов выполнения одной операции
  2. Когда нужно менять поведение во время выполнения
  3. Когда необходимо изолировать бизнес-логику от деталей реализации
  4. Когда есть множество родственных классов, отличающихся только поведением

Резюмируем:

В JavaScript/Node.js паттерн Strategy проще всего реализовать через объект с функциями или набор классов с общим интерфейсом. Этот паттерн особенно полезен в Node.js для обработки запросов, работы с разными СУБД, кеширования и логирования, где требуется гибкость в выборе поведения.