Открыт для расширения, закрыт для изменения
Принцип открытости/закрытости говорит: код должен быть открыт для расширения — можно добавлять новое поведение — но закрыт для изменения — существующий, работающий и покрытый тестами код ради этого не правят.
Каждая правка работающего кода — это шанс его сломать. Когда добавление возможности означает только добавление нового кода, старый код остаётся проверенным.
Инструмент PHP для этого — интерфейс: функция, которая зависит от интерфейса, никогда не потребует изменений, когда его реализует новый класс.
switch, который никогда не перестаёт расти
Этот калькулятор площадей использует switch по виду фигуры. Он работает — до появления следующей фигуры. Треугольник? Правим switch. Пятиугольник? Правим снова. Функция никогда не бывает готова, и каждая правка ставит под угрозу фигуры, которые уже работали.
switch по виду фигуры
totalArea обязана знать каждый вид фигуры. Добавление одного вида означает изменение этой проверенной тестами функции — именно то, от чего предостерегает принцип.
Вывод: PHP-скрипт
<?php
class Shape {
public function __construct(
public string $kind,
public float $width = 0,
public float $height = 0,
public float $radius = 0,
) {
}
}
// totalArea обязана знать каждый вид — и растёт с каждым новым.
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
switch ($s->kind) {
case "circle":
$total += M_PI * $s->radius * $s->radius;
break;
case "rectangle":
$total += $s->width * $s->height;
break;
}
}
return $total;
}
$total = totalArea(
new Shape(kind: "circle", radius: 2),
new Shape(kind: "rectangle", width: 3, height: 4),
);
printf("суммарная площадь: %.2f\n", $total);
Расширяем через интерфейс
Решение: каждая фигура знает свою площадь сама. Интерфейс Shape требует один метод, area(), а totalArea просто складывает всё, что получает — ей больше не важно, что это за фигуры.
Теперь посмотрите, как принцип работает: совершенно новый Triangle подключается, а totalArea остаётся нетронутой. Расширение без изменения.
Каждая фигура сама вычисляет свою площадь
totalArea зависит только от интерфейса Shape. switch исчез — а вместе с ним и необходимость когда-либо снова править эту функцию.
Вывод: PHP-скрипт
<?php
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(public float $radius) {
}
public function area(): float {
return M_PI * $this->radius * $this->radius;
}
}
class Rectangle implements Shape {
public function __construct(public float $width, public float $height) {
}
public function area(): float {
return $this->width * $this->height;
}
}
// totalArea зависит только от интерфейса — никакого switch, править нечего.
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
$total += $s->area();
}
return $total;
}
$total = totalArea(
new Circle(radius: 2),
new Rectangle(width: 3, height: 4),
);
printf("суммарная площадь: %.2f\n", $total);
Добавляем новую фигуру, не трогая ничего
Triangle — это чистый новый код: класс, который реализует area(). totalArea, Circle и Rectangle остались точно такими, какими были — открыты для расширения, закрыты для изменения.
Вывод: PHP-скрипт
<?php
interface Shape {
public function area(): float;
}
class Circle implements Shape {
public function __construct(public float $radius) {
}
public function area(): float {
return M_PI * $this->radius * $this->radius;
}
}
class Rectangle implements Shape {
public function __construct(public float $width, public float $height) {
}
public function area(): float {
return $this->width * $this->height;
}
}
// Triangle — это НОВЫЙ код: всё выше осталось без изменений.
class Triangle implements Shape {
public function __construct(public float $base, public float $height) {
}
public function area(): float {
return $this->base * $this->height / 2;
}
}
function totalArea(Shape ...$shapes): float {
$total = 0.0;
foreach ($shapes as $s) {
$total += $s->area();
}
return $total;
}
$total = totalArea(
new Circle(radius: 2),
new Rectangle(width: 3, height: 4),
new Triangle(base: 6, height: 5),
);
printf("суммарная площадь: %.2f\n", $total);