S.O.L.I.D. и OpenCart

S.O.L.I.D. - это принципы программирования, которые сильно помогают при написании больших приложений, делая их более гибкими, расширяемыми, уменьшая при этом количество ошибок и конфликтов, а также время и деньги на поддержку и сопровождение этого приложения в будущем. 

Принципы были описал еще в 2000 году Робертом Мартином, вы скорее всего слышали о его книгах, таких как "Чистый код" и "Чистая архитектура".
Эти принципы настолько важны, что многие работодатели даже указывают в вакансиях их знание как обязательное условие при устройстве на работу.

Попробую описать эти принципы своими словами, максимально просто и понятно.

Как можно было догадаться из названия - принципов всего 5 по первым буквам названия:
S - Single Responsibility Principle - Принцип единой ответственности
O - Open-closed Principle - Принцип открытости/закрытости
L - Liskov Substitution Principle - Принцип подстановки Барбары Лисков
I - Interface Segregation Principle - Принцип разделения интерфейсов
D - Dependency Inversion Principle - Принцип инверсии зависимостей

Single Responsibility Principle - Принцип единой ответственности
Достаточно простой, понятный но в тоже время очень важный, фундаментальный, принцип.
Он говорит о том, что любая программная сущность (класс, метод, функция итд.) должна иметь только одну ответственность, то есть решать только одну задачу. Правда Роберт Мартин изначально вкладывал в этот принцип немного другой смысл. 
Этот принцип очень часто нарушается в OpenCart, когда в контроллер добавляют все, что только можно - тут и валидация данных и создание ошибок, хлебных крошек, пагинации, картинок и вообще вся логика, так как в модели только получение данных из базы, а все остальное - в контроллере. Из-за этого мы получаем довольно распространенную ошибку MVC шаблона - очень большой контроллер, что является неправильным, контроллер должен быть очень простым и легким, его основная задача - получить запрос от пользователя (Request), передать его кому-то другому на обработку и результа вернуть назад пользователю (Response), но делать вообще всю работу он никак не должен.

Open-closed Principle - Принцип открытости/закрытости
Еще один очень важные принцип, который также полностью нашурается опенкартом.
Он говорит о том, что программные сущности должны быть открыты для расширения и закрыты для изменения (модификации кода)
То есть, эти сущности должны иметь возможность расширить их функционал, но никак не через непосредственное изменение их кода, а через другие методы, например через наследование, интерфейсы, внедрение зависимостей, Events или в некоторых системах hooks, модули и плагины для CMS и так далее.
В OpenCart же по сути вообще вся система расширений построена на модификаторах (ocmod) или на изменении кода самого движка, то есть на полном нарушении этого принципа. 

Liskov Substitution Principle - Принцип подстановки Барбары Лисков
Он говорит о том, что если какая-то программная сущность работает с объектом класса родителя то она точно также без каких-либо изменений кода должна работать и со всеми потомками этого родителя. То есть, чтобы мы могли вместо класса родителя подставить класс любого его потомка и при этом наша программа работала точно также как и работала до этого. Это также улучшает расширяемость приложения и переплетается со вторым принципом - открытости/закрытости.
Как это достигается? Например через контроль всех возвращаемых данных методами классов, если метод родителя возвращает массив определенного формата то и все потомки должны точно также возвращать точно такой же формат массива. Или есть в родителе реализован какой-то метод то этот метод должен быть обязательно реализован и всеми потомками и так далее.

Interface Segregation Principle - Принцип разделения интерфейсов
Он говорит о том, что если какой-то класс реализовывает интерфейс, то он должен реализовывать абсолютно все его методы, а не только какие-то из них.
Например, если нам нужно создать какой-то класс и мы видим, что у нас уже есть интерфейс, который очень похож на тот, который нам нужен, например там есть 10 методов из которых нам нужны 8, то использовать этот интерфейс для этого класса мы не можем, нужно разделить этот интерфейс на несколько более мелких и использовать именно то, что нам нужно (тем более что PHP поддерживает реализацию классом нескольких интерфейсов).
Иначе получится ситуация, когда какой-то наш метод работая с этим интерфейсом будет предполагать, что в любом классе, который его реализует обязательно должен быть реализован какой-то метод, но в одних классах он будет реализован, а в других будет например выбрасываться исключение и нам придется в нашей программе в куче мест писать кучу условий и держать в голове всю логику в каком классе какие методы есть а каких нету.. зачем? если сама идея интерфейсов и состоить в том, чтобы оградить нас от всех этих проблем.

Dependency Inversion Principle - Принцип инверсии зависимостей
Еще один крайне важный принцип, который отлично реализован в Laravel через сервис контейнеры.
Упрощенно он говорит о том, что все зависимости в приложении должны строится на базе интерфейсов, а не через конкретные реализации.
Объясню лучше на примере.
У нас есть больше приложение, которое сохраняет данные в базе данных. Для этого у нас есть класс DB и методы select() для получения данных из базы и insert() для сохранения. Все отлично работает, но в один момент нам (или заказчику) понадобилось реализовать сохранение данных в, например, файлах. И у нас даже есть готовый класс для этого FileStorage с методами read() и write() соответственно. Но беда в том, что нам теперь нужно переписать гору кода в десятках файлов чтобы заменить не только все названия методов но и их аргументы и возвращаемые значения.. А теперь представьте насколько было бы проще, если бы все было реализовано через интерфейсы и у нас был интерфейс StorageInterface, в котором бы были методы например get() и set() и везде в коде мы использует именно эти методы. Теперь, если нам нужно сохранять данные в базе, мы создаем класс DB который реализовывает наш интерфейс StorageInterface с определенными методами, а для файлов создаем другой класс, который реализовывает тот же самый интерфейс с теми же самыми методами. Если в будущем нам понадобиться работать с LLM и сохранять данные в какой-то векторной базе данных напр. Chroma или Pinecone, без проблем, мы просто реализуем новым классом VectorStorage наш интерфейс и без проблем сможет использовать этот класс в своем приложении и у нас ничего не поломается.
Мы сможем даже подключать разные классы работы с данными динамически в зависимости от условий, например в зависимости от переменных окружения, если мы в режиме разработки то использовать один класс данных, если в продакшене то другой, а если в режиме тестирования то какой-нибудь FakeStorage и все будет замечательно работать без каких-либо изменений в коде.


Надеюсь, у меня получилось донести что такое принципы S.O.L.I.D., как они работают и почему так важно их использовать в работе.
Плохо, что основной разработчик OpenCart, похоже, вообще ничего о них не слышал и поэтому у нас сейчас такой бардак в OpenCart, который похоже никогда не закончится..
И OpenCart это как раз очень наглядный пример как незнание или игнорирования базовых принципов программирования при построении больших систем может со временем парализовать развитие всего проекта. 

Тэги: 

Добавить комментарий

CAPTCHA
Защита от спама
Target Image