Принцип открытости-закрытости (от английского The Open Closed Principle, OCP) по своей сути является расширением базового понимания ООП – полиморфизма + касаются такого понятия как область видимости и того, что ты вряд ли знал до этой статьи — Interface. Давайте для начала сформулируем принцип открытости-закрытости:
Сущности программирования (классы, методы, модули) должны быть открыты для расширения, но закрыты для модификации.
От себя лично из глубин написанного годами говнокода могу добавить пару нюансов:
- Не нужно путать процесс разработки вашего приложения и собственно саму логику работы. Т.е. если вы решили переизобрести велосипед, написав свой собственный MVC для натяжки фронта и забыли добавить поддержку различных методов подключения к БД (mysql, postgre, sqlite…), не нужно пользоваться постоянно
extends
. Просто добавьте в само ядро вашего приложения. Это не будет нарушением принципа открытости-закрытости; - Жизненный цикл вашего ПО скорее всего будет малопредсказуемым и строго, на все 100%, следовать принципу вряд ли удастся;
- Если вы единственный программист на проекте и это не мега проект, помните про принцип «just works», KISS и пр. Используйте их куда чаще, чем SOLID. Безусловно круто похвастаться перед коллегами, что в вашем hello world есть отличный шаблонизатор и крутая защита от SQL injection, но в конечном итоге это все выльется в количество часов, которое вряд ли кто-то из клиентов захочет оплачивать. Такие телодвижения (в рамках hello world) приветствуются в академических целях, но не более;
- В современных паттернах программирования есть еще такие понятия как структурные шаблоны проектирования, например Facade или Decorator, которыми также можно структурировать функционал вашего веб приложения, если ядро уже написано и нет желания (возможности?) без особых проблем его переписать. На самом деле есть еще такие штуки как Adapter и даже Proxy, но все эти шаблоны заслуживают отдельных статей;
- К сожалению, все принципы SOLID, структурных шаблонов проектирования да и вообще ООП настолько непросты, что у 10 человек, которые пройдут одинаковый курс по ним в одной комнате у одного преподавателя сложится ровно 10 пониманий «как на самом деле все работает». Это приводит к тому, что могут возникать все новые и новые принципы и шаблоны (отложенный Facade, порождающая Factory, да мало ли что там еще есть), а старые приходится переосмысливать. Зато Вам всегда будет о чем поговорить с коллегами программистами за бутылкой пива, главное, чтобы обсуждение не переросло в драку 😅
Пример на PHP. Мне очень нравится пример с использованием Interface, потому что он не даст создать какой-то новый код без нужных методов. Еще разок, этот код будет работать и без Interface, но в этом случае, программист может случайно забыть написать нужный метод и упрется в глухую стену непонимания (если например, в классе будет магический метод __call()
, а если еще и __callStatic()
, то вообще жуть)
Суть задачи: Посчитать площадь круга, причем написать так размашисто, чтобы можно было всегда обращаться к этому функционалу и вдруг чего его даже дорабатывать (на секунду представим, что мы не знаем что такое стрелочные функции). Причем пишем код последовательно, если бы к нам требования поступали поэтапно.
Задача: посчитать площадь круга по радиусу.
<?php
class Circle {
public $radius;
public function __construct($radius) {
$this->radius = $radius;
}
public function getArea() {
return $this->radius * $this->radius * pi();
}
}
$circle = new Circle('25');
echo $circle->getArea(); //1963.4954084936
Но тут приходит к нам PM и говорит, а можно еще написать подсчет площади прямоугольника по заданным размерам сторон? Материмся и пишем дальше.
class Rectangle {
public $width;
public $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
$rectangle = new Rectangle(10, 20);
echo $rectangle->getArea(); //200
Не успели мы победоносно закрыть крышку любимого макбука и идти пить кофе да рубиться в плойку, как ПМ снова идет к нам к нам. Вы уже даже не моргаете…
– Слушай, а можно как-то унифицировать подсчет площади любой фигуры?
– Да господи ты боже мой!!!!!!! ;%:?*()_)(*?:%;, да какого ;%:?*()(*?:%!
Где-то на этом моменте в голову приходит мысль про OCP, а еще в ярком свете с неба спускается слово Interface
. Кофе принесет коллега, а мы перепишем наш код (заметьте – перепишем, без всякого стиснения и изобретения велосипеда).
Для начала мы видимим, что как бы там не сложилось, нам нужно для любой фигуры считать площадь, и в каждом Классе есть getArea()
. Так вот она основа для нашего интерфейса! Хрустим костяшками и пишем:
<?php
interface FigureInterface {
public function getArea();
}
class Circle implements FigureInterface {
public $radius;
public function __construct($radius) {
$this->radius = $radius;
}
public function getArea() {
return $this->radius * $this->radius * pi();
}
}
class Rectangle implements FigureInterface {
public $width;
public $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
class SquareCalculator {
public function calculator($figure) {
return $figure->getArea();
}
}
$circle = new Circle(5);
$rectangle = new Rectangle(10, 20);
$result = new SquareCalculator;
echo $result->calculator($circle);
echo $result->calculator($rectangle);
Вот относительно класса SquareCalculator
мы и выполнили принцип OCP, отныне он закрыт для модификаций, но открыт для расширений 😎