Открыт для расширения, закрыт для изменения
Принцип открытости/закрытости гласит: код должен быть открыт для расширения — новое поведение можно добавить, — но закрыт для изменения — существующий, работающий и проверенный код ради этого не правят.
Каждая правка работающего кода — это шанс его сломать. Когда добавление возможности означает только добавление нового кода, старый код остаётся проверенным.
Инструмент TypeScript для этого — интерфейс: функция, которая зависит от интерфейса, никогда не нуждается в изменении, когда её реализует новый класс.
switch, который никогда не перестаёт расти
Этот калькулятор площади использует switch по виду фигуры. Он работает — пока не появится следующая фигура. Треугольник? Правим switch. Пятиугольник? Правим снова. Функция никогда не завершена, и каждая правка ставит под угрозу фигуры, которые уже работали.
switch по виду фигуры
totalArea обязана знать каждый вид фигуры. Добавление одного вида означает изменение этой проверенной функции — ровно то, от чего предостерегает принцип.
Вывод: программа на TypeScript и захваченный вывод
interface ShapeData {
kind: string
width: number
height: number
radius: number
}
// totalArea обязана знать каждый вид фигуры — и растёт с каждым новым.
function totalArea(shapes: ShapeData[]): number {
let total = 0
for (const s of shapes) {
switch (s.kind) {
case "circle":
total += Math.PI * s.radius * s.radius
break
case "rectangle":
total += s.width * s.height
break
}
}
return total
}
const shapes: ShapeData[] = [
{ kind: "circle", width: 0, height: 0, radius: 2 },
{ kind: "rectangle", width: 3, height: 4, radius: 0 },
]
console.log("суммарная площадь:", totalArea(shapes).toFixed(2))
Расширение через интерфейс
Решение: каждая фигура сама знает свою площадь. Интерфейс Shape требует один метод, area(), а totalArea просто складывает всё, что получает, — ей больше неважно, что это за фигуры.
Теперь посмотрите, как работает принцип: совершенно новый класс Triangle подключается, а totalArea не тронута. Расширение без изменения.
Каждая фигура вычисляет свою площадь сама
totalArea зависит только от интерфейса Shape. switch исчез — а вместе с ним и необходимость когда-либо снова править эту функцию.
Вывод: программа на TypeScript и захваченный вывод
interface Shape {
area(): number
}
class Circle implements Shape {
radius: number
constructor(radius: number) {
this.radius = radius
}
area(): number {
return Math.PI * this.radius * this.radius
}
}
class Rectangle implements Shape {
width: number
height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
area(): number {
return this.width * this.height
}
}
// totalArea зависит только от интерфейса — никакого switch, нечего править.
function totalArea(shapes: Shape[]): number {
let total = 0
for (const s of shapes) {
total += s.area()
}
return total
}
const shapes: Shape[] = [new Circle(2), new Rectangle(3, 4)]
console.log("суммарная площадь:", totalArea(shapes).toFixed(2))
Добавьте новую фигуру, ничего не трогая
Triangle — это чистый новый код: класс и его метод area(). totalArea, Circle и Rectangle остались ровно такими, какими были, — открыты для расширения, закрыты для изменения.
Вывод: программа на TypeScript и захваченный вывод
interface Shape {
area(): number
}
class Circle implements Shape {
radius: number
constructor(radius: number) {
this.radius = radius
}
area(): number {
return Math.PI * this.radius * this.radius
}
}
class Rectangle implements Shape {
width: number
height: number
constructor(width: number, height: number) {
this.width = width
this.height = height
}
area(): number {
return this.width * this.height
}
}
// Triangle — это НОВЫЙ код, всё, что выше, осталось нетронутым.
class Triangle implements Shape {
base: number
height: number
constructor(base: number, height: number) {
this.base = base
this.height = height
}
area(): number {
return (this.base * this.height) / 2
}
}
function totalArea(shapes: Shape[]): number {
let total = 0
for (const s of shapes) {
total += s.area()
}
return total
}
const shapes: Shape[] = [new Circle(2), new Rectangle(3, 4), new Triangle(6, 5)]
console.log("суммарная площадь:", totalArea(shapes).toFixed(2))