Приведи пример использования принципа open/closed
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример применения принципа 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 везде — это может привести к избыточной сложности. Принцип особенно полезен в модулях, где действительно ожидаются частые изменения и расширения. В простых случаях или прототипах прямое изменение кода может быть более практичным.
Такой подход особенно критичен в крупных проектах и командной разработке, где изменения в одном модуле могут непредсказуемо повлиять на другие части системы, и где важна стабильность уже протестированного кода.