← Назад к вопросам

Приведи пример использования принципа open/closed

2.0 Middle🔥 192 комментариев
#JavaScript Core

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример применения принципа open/closed в TypeScript

Принцип open/closed (открыт для расширения, закрыт для изменения) — один из ключевых принципов SOLID, который гласит: программные сущности должны быть открыты для расширения, но закрыты для изменения. Это означает, что мы можем добавлять новую функциональность, не меняя существующий код.

Проблемный код (нарушение OCP)

Рассмотрим типичную ситуацию — систему расчета площади различных фигур:

class AreaCalculator {
  calculateArea(shape: any): number {
    if (shape.type === 'circle') {
      return Math.PI * shape.radius * shape.radius;
    }
    
    if (shape.type === 'rectangle') {
      return shape.width * shape.height;
    }
    
    if (shape.type === 'triangle') {
      return (shape.base * shape.height) / 2;
    }
    
    throw new Error('Unknown shape type');
  }
}

// Использование
const calculator = new AreaCalculator();
console.log(calculator.calculateArea({ type: 'circle', radius: 5 })); // 78.54

Проблемы этого подхода:

  • При добавлении новой фигуры нужно изменять метод calculateArea
  • Нарушается принцип единственной ответственности — класс знает о расчетах всех фигур
  • Сложно тестировать — нужно проверять все ветки условий
  • Высокая связность — класс зависит от деталей реализации каждой фигуры

Рефакторинг с соблюдением OCP

// 1. Создаем абстракцию (интерфейс)
interface Shape {
  calculateArea(): number;
}

// 2. Реализуем конкретные фигуры
class Circle implements Shape {
  constructor(private radius: number) {}
  
  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  
  calculateArea(): number {
    return this.width * this.height;
  }
}

class Triangle implements Shape {
  constructor(private base: number, private height: number) {}
  
  calculateArea(): number {
    return (this.base * this.height) / 2;
  }
}

// 3. Модифицируем калькулятор
class AreaCalculator {
  calculateArea(shape: Shape): number {
    return shape.calculateArea();
  }
  
  // Дополнительно: можем легко добавить функциональность
  calculateTotalArea(shapes: Shape[]): number {
    return shapes.reduce((total, shape) => total + shape.calculateArea(), 0);
  }
}

// 4. Использование
const calculator = new AreaCalculator();
const circle = new Circle(5);
const rectangle = new Rectangle(4, 6);
const triangle = new Triangle(3, 4);

console.log(calculator.calculateArea(circle)); // 78.54
console.log(calculator.calculateTotalArea([circle, rectangle, triangle])); // Сумма площадей

Расширение без изменения существующего кода

Теперь добавим новую фигуру, не изменяя AreaCalculator:

// Добавляем новую фигуру
class Ellipse implements Shape {
  constructor(private a: number, private b: number) {}
  
  calculateArea(): number {
    return Math.PI * this.a * this.b;
  }
}

// Или фигуру с нестандартной логикой
class CompositeShape implements Shape {
  constructor(private shapes: Shape[]) {}
  
  calculateArea(): number {
    return this.shapes.reduce((total, shape) => total + shape.calculateArea(), 0);
  }
}

// Используем новые фигуры без изменений в калькуляторе
const ellipse = new Ellipse(5, 3);
const composite = new CompositeShape([circle, rectangle]);
console.log(calculator.calculateArea(ellipse));
console.log(calculator.calculateArea(composite));

Ключевые преимущества решения:

1. Устойчивость к изменениям:

  • Добавление новых фигур не требует изменения AreaCalculator
  • Существующий код остается стабильным и протестированным

2. Гибкость архитектуры:

  • Легко добавлять новые типы фигур
  • Можно создавать декораторы для расширения функциональности:
// Декоратор для логирования
class LoggingShapeDecorator implements Shape {
  constructor(private shape: Shape, private name: string) {}
  
  calculateArea(): number {
    const area = this.shape.calculateArea();
    console.log(`Area of ${this.name}: ${area}`);
    return area;
  }
}

const loggedCircle = new LoggingShapeDecorator(circle, 'Circle');
calculator.calculateArea(loggedCircle); // Выведет в консоль

3. Упрощенное тестирование:

  • Каждый класс фигуры тестируется изолированно
  • AreaCalculator требует только одного теста — проверки вызова метода

4. Соответствие другим принципам SOLID:

  • Принцип единственной ответственности — каждая фигура отвечает только за свой расчет
  • Принцип подстановки Барбары Лисков — все фигуры взаимозаменяемы через интерфейс
  • Принцип разделения интерфейса — интерфейс Shape минимален и специфичен

Практические рекомендации:

Когда применять OCP:

  • В модулях, которые likely будут расширяться
  • В ядре приложения (domain layer)
  • При разработке библиотек и фреймворков

Инструменты для реализации:

  • Интерфейсы и абстрактные классы — для определения контрактов
  • Паттерн Стратегия — для взаимозаменяемых алгоритмов
  • Паттерн Декоратор — для динамического расширения
  • Зависимость от абстракций (Dependency Inversion)

Важное замечание: Не стоит слепо применять OCP везде — это может привести к избыточной сложности. Принцип особенно полезен в модулях, где действительно ожидаются частые изменения и расширения. В простых случаях или прототипах прямое изменение кода может быть более практичным.

Такой подход особенно критичен в крупных проектах и командной разработке, где изменения в одном модуле могут непредсказуемо повлиять на другие части системы, и где важна стабильность уже протестированного кода.

Приведи пример использования принципа open/closed | PrepBro