Open-Closed Principle

Open-Closed Principle (OCP) — второй принцип SOLID: программные сущности должны быть открыты для расширения, но закрыты для изменения — добавление нового поведения не должно требовать изменения существующего кода.

Зачем нужно

Нарушение OCP означает, что каждое новое требование вынуждает изменять рабочий код, ломая существующую функциональность и требуя повторного тестирования. Соблюдение OCP через абстракции (полиморфизм, strategy, плагины) позволяет расширять систему без риска регрессий.

Где используется

  • Плагинные системы (добавление новых обработчиков без изменения ядра)
  • Strategy Pattern (замена алгоритма без изменения контекста)
  • Middleware и декораторы
  • Компонентные библиотеки (расширение через props, children, slots)

Основной контент

Нарушение OCP

// Плохо: каждый новый тип требует изменения функции
function calculateDiscount(type, price) {
  if (type === 'regular') {
    return price * 0.05;
  } else if (type === 'premium') {
    return price * 0.10;
  } else if (type === 'vip') {
    return price * 0.20;
  }
  // Для нового типа нужно менять эту функцию!
  return 0;
}

Соблюдение OCP через полиморфизм

// Хорошо: каждая стратегия — отдельный класс
class RegularDiscount {
  calculate(price) { return price * 0.05; }
}

class PremiumDiscount {
  calculate(price) { return price * 0.10; }
}

class VIPDiscount {
  calculate(price) { return price * 0.20; }
}

// Контекст закрыт для изменений
class PriceCalculator {
  constructor(discountStrategy) {
    this.strategy = discountStrategy;
  }

  getPrice(price) {
    return price - this.strategy.calculate(price);
  }
}

// Новый тип — новый класс, не изменяем существующий код
class StudentDiscount {
  calculate(price) { return price * 0.15; }
}

const calc = new PriceCalculator(new VIPDiscount);
console.log(calc.getPrice(1000)); // 800

const studentCalc = new PriceCalculator(new StudentDiscount);
console.log(studentCalc.getPrice(1000)); // 850

OCP через функции высшего порядка

// Реестр обработчиков вместо if/else цепочки
const handlers = {};

function register(type, handler) {
  handlers[type] = handler;
}

function process(type, data) {
  const handler = handlers[type];
  if (!handler) throw new Error(`Неизвестный тип: ${type}`);
  return handler(data);
}

// Расширение без изменения process
register('json', data => JSON.parse(data));
register('csv', data => data.split(',').map(s => s.trim()));
register('tsv', data => data.split('\t'));

console.log(process('json', '{"a":1}')); // { a: 1 }
console.log(process('csv', 'a, b, c')); // ['a', 'b', 'c']

OCP через декораторы

// Базовая функция закрыта для изменений
function fetchData(url) {
  return fetch(url).then(r => r.json());
}

// Расширение через декораторы
function withLogging(fn) {
  return function(...args) {
    console.log('Запрос:', args[0]);
    return fn(...args).then(result => {
      console.log('Результат получен');
      return result;
    });
  };
}

function withCache(fn, cache = new Map) {
  return function(url) {
    if (cache.has(url)) return Promise.resolve(cache.get(url));
    return fn(url).then(data => {
      cache.set(url, data);
      return data;
    });
  };
}

const enhancedFetch = withCache(withLogging(fetchData));

Частые ошибки

  • Преждевременная абстракция — OCP не означает «всегда создавать абстракцию». Абстрагируйте только когда видите реальную точку изменения (rule of three).
  • Слишком большая иерархия классов — излишнее следование OCP через наследование создаёт глубокие иерархии. Предпочитайте composition и функции высшего порядка.
  • Путаница OCP с immutability — OCP не про неизменяемость данных, а про неизменяемость исходного кода при расширении поведения.

🎓 Источники

  • 🎓 [SOLID OCP — Open-Closed Principle for JavaScript] · 2024-12-03 · YouTube
    • Тезисы: OCP — про обратную совместимость и контракты. Сущности должны быть открыты для расширения и закрыты для модификации. OCP тесно связан с LSP: подставлять подтипы можно, только когда OCP соблюдён — подтип не меняет, а расширяет контракт.
    • «Сущности, классы, модули, функции, любые программные абстракции должны быть открыты для расширения и закрыты для модификации».

Связанные темы

Ресурсы