Принципы SOLID – что это такое?

Принципы SOLID в программировании

SOLID (single responsibility, open-closed, Liskov substitution, interface segregation и dependency inversion) – это 5 принципов программной разработки, объектно-ориентированного программирования и проектирования. Когда разработчик пишет свой код, он должен провести анализ и сделать декомпозицию своей программы, разбить ее на множество отдельных простых частей, которые делают отдельные задачи, абстрагироваться от конкретной глобальной задачи и постараться сделать нечто абстрактное, легко модифицируемое и расширяемое.

Помните китайские ножи? Такие объекты делать не стоит, который и открывает консервы, и нож, и открывашка со штопором, и открывашка для пива, и даже плоскогубцы! Лучше было бы сделать несколько отдельных объектов и сложить их в один ящик, выглядело бы проще, красивее, да и использовать их удобнее. Принципы солид (SOLID) как раз и предназначены для того, чтобы разработчик умеющий их использовать писал простые, но при этом серьезные программы.

Single Responsibility Principle, SRP

В ООП принцип единственной ответственности (англ. The Single Responsibility Principle, SRP) обозначает, что каждый объект должен иметь одну ответственность и эта ответственность должна быть полностью инкапсулирована в класс. Все его поведения должны быть направлены исключительно на обеспечение этой ответственности.

A class should have only one reason to change.
Robert C. Martin

Представьте, у вас был нож, который позволял вам нарезать продукты, но вам понадобился нож с зазубринами на конце. Что бы вы сделали? Сделали зазубрины на старом ноже? Вы сломали бы его. С другой стороны прикрепили бы нож с зазубринами? Он был бы не удобен и можно было бы порезаться. В программировании так же, если ваш объект начинает обрастать массой методов которые не имеют никакого отношения к старому объекту, то не стоит этим заниматься, оставьте в покое старый объект и сделайте новый, может даже его расширение. Класс должен быть направлен на решение одной единственной цели, есть такой антипаттерн “Магический объект” – как раз принцип единственной ответственности позволяет его избежать.

Направленность классов на единственную цель делает их более “здоровыми”. Когда применять, а когда не применять этот принцип?

  • если при изменении кода, отвечающего за одну ответственность, в приложении появляются исправления кода, отвечающего за другую ответственность, то это первый сигнал о нарушении SRP (если сломались старые тесты)
  • если же изменения кода, отвечающего за одну ответственность, не вносят изменения в код, отвечающий за другую ответственность, то этот принцип можно не применять.

Методика разработки через тестирование (TDD) как раз помогает следовать данному принципу. Когда вы изначально описываете, что объект должен делать, пишите тесты на его функционал, а потом реализовываете это. Если старые тесты продолжают работать корректно совместно с новыми, то это значит что принцип не был нарушен. Но не стоит делать из вашего объекта китайский нож.

Open Closed Principle, OCP

Принцип открытости-закрытости очень важен в производственной среде, когда ваша программа уже имеет какой-то функционал, оттестированный и работающий. Принцип говорит о следующем: “Программы должны быть закрыты для модификации, но открыты для расширения”. Что это значит?

Открыты для расширения – это значит, что поведение сущности может быть расширено путем создания нового типа (подтипа) сущности, вспомните полиморфизм. Закрыты для модификации – значит что в результате расширения сущности не должны вноситься изменения в код, который эти сущности использует. А это значит, что при создании нового подтипа сущности, должен сохраниться интерфейс, новым будет лишь внутренний функционал новой сущности, входные и выходные данные должны быть того же типа и структуры.

Нарушение принципа открытости-закрытости нарушает вышеупомянутый принцип единственной ответственности.

 Liskov Substitution Principle, LSP

Принцип подстановки Барбары Лисков можно объяснить на примере этой картинки.

Крякает как утка, выглядит как утка, плавает как утка – значит это утка!

А ведь это действительно так! Вспомните пример, который я указывал при объяснении полиморфизма. Функции, которые используют базовый тип, могут использовать подтипы базового типа даже не зная об этом! Ведь базовым типом обычной утки и пластиковой утки является базовый класс “Утка”, они полностью соответствуют данному классу, имеют такие же проперти и методы, значит они могут быть взаимозаменяемы.

Принцип работает тогда, когда при замещение базового класса его подтипами, поведение программы использующей их не будет меняться. Программе не обязательно знать, что она использует, если новая ее часть работает так же как и старая, да и не важно что она внутри работает по другому, важно что она имеет тот же интерфейс и выдает такие же данные на выход как и предыдущая.

Interface Segregation Principle, ISP

Принцип разделения интерфейса. Не стоит делать один интерфейс под все, как с тем китайским ножом. Лучше сделать отдельные интерфейсы под каждый отдельный объект занимающийся конкретной задачей. Принцип говорит о том, что изменение интерфейса не должно затрагивать объекты, которые этот интерфейс не используют.

Допустим у вас был интерфейс который позволял использовать объект в цикле (интерфейс “итератор”), но теперь вам нужно чтобы объект давал вам количество элементов данных внутри “Count”, лучше сделать для этого отдельный интерфейс (Countable) и описать внутри его функции, которые должны быть в объекте с этим интерфейсом, это позволит вам не создавать пустые методы внутри объектов, использующих интерфейс “Итератор”, если бы вы дополнили этот интерфейс новыми методами.

Посмотрите пример на картинке с USB-кабелем, у нас есть переключатели, но один интерфейс под все, лучше сделать под каждый переключатель отдельный интерфейс, который будет разработан специально для него.

Dependency inversion principle, DIP

Принцип инверсии зависимостей. Модули верхних уровней не должны напрямую зависеть от модулей нижних уровней, оба типа модулей должны зависеть от абстракций. У лампочки свой интерфейс, она не зависит от того какие провода будет подведены к ней и как ее подключат к розетке, у розетки свой интерфейс, все это не будет работать пока вы не напишите софт под эти интерфейсы, как вы его напишите не важно, ведь зависимость построена на абстракциях, вы можете просто прикрутить провода к лампочке и воткнуть их в розетку, можете использовать патрон и вилку, но программы, которые стоят за этими интерфейсами это не волнует. Важно чтобы ток был как написано в интерфейсе и контакты подсоединили как указано в интерфейсе.

Зависимость на абстракциях позволяет писать программы не задумываясь о том, как работают верхние или нижние уровни, так как у вас есть соответствующие интерфейсы. Это очень удобно в командной разработке.


Если вы научитесь использовать принципы о которых я написал в этой статье, то вы станете высококлассным программистом, так как вы без труда сможете работать в команде, писать простые, но при этом серьезные программы, с хорошей архитектурой и легко расширяемые. Об этих принципах мы будем говорить и применять их на протяжении всего времени, ведь они являются ключевыми.

Подпишитесь на рассылку новых статей

Подпишитесь на рассылку свежих статей и присоединяйтесь к 7 остальным подписчикам.