Найти тему

Принципы SOLID, о которых должен знать каждый разработчик

Оглавление

Single responsibility (Принцип единственной ответственности)

Open–closed (Принцип открытости/закрытости)

Liskov substitution (Принцип подстановки Лисков)

Interface segregation (Принцип разделения интерфейса)

Dependency inversion (Принцип инверсии зависимостей)

В этой статье использовал материалы Ulbi TV
https://youtu.be/TxZwqVTaCmA

1. Парадигмы ООП

1.1. Инкапсуляция

1.2. Наследование

1.3. Полиморфизм

1.1. Инкапсуляция

Инкапсуляция — это механизм программирования, объединяющий вместе код и данные, которыми он манипулирует, исключая как вмешательство извне, так и не­ правильное использование данных. В объектно-ориентированном языке данные и код могут быть объединены в совершенно автономный черный ящик. Внутри такого ящика находятся все необходимые данные и код. Когда код и данные связываются вместе по­добным образом, создается объект.

Другими словами. инкапсуляция - это заключение данных и функциональности в оболочку.

В объектно-ориентированном программировании в роли оболочки выступают классы: они не только собирают переменные и методы в одном месте, но и защищают их от вмешательства извне (сокрытие).

Методы позволяют контролировать обращение к данным и предотвратить их удаление или некорректное изменение.

1.2. Наследование

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

Наследование является механизмом повторного использования кода (англ. code reuse) и способствует независимому расширению программного обеспечения через открытые классы (англ. public classes) и интерфейсы (англ. interfaces). Установка отношения наследования между классами порождает иерархию классов (англ. class hierarchy).

В объектно-ориентированном программировании, начиная с Simula 67, абстрактные типы данных называются классами.

Суперкласс (англ. super class), родительский класс (англ. parent class), предок, родитель или надкласс — класс, производящий наследование в подклассах, т. е. класс, от которого наследуются другие классы. Суперклассом может быть подкласс, базовый класс, абстрактный класс и интерфейс.

Подкласс (англ. subclass), производный класс (англ. derived class), дочерний класс (англ. child class), класс потомок, класс наследник или класс-реализатор — класс, наследуемый от суперкласса или интерфейса, т. е. класс определённый через наследование от другого класса или нескольких таких классов. Подклассом может быть суперкласс.

Базовый класс (англ. base class) — это класс, находящийся на вершине иерархии наследования классов и в основании дерева подклассов, т. е. не являющийся подклассом и не имеющий наследований от других суперклассов или интерфейсов. Базовым классом может быть абстрактный класс и интерфейс. Любой не базовый класс является подклассом.

Интерфейс (англ. interface) — это структура, определяющая чистый интерфейс класса, состоящий из абстрактных методов. Интерфейсы участвуют в иерархии наследований классов и интерфейсов.

Суперинтерфейс (англ. super interface) или интерфейс-предок — это аналог суперкласса в иерархии наследований, т. е. это интерфейс производящий наследование в подклассах и подинтерфейсах.

Интерфейс-потомок, интерфейс-наследник или производный интерфейс (англ. derived interface) — это аналог подкласса в иерархии наследований интерфейсов, т. е. это интерфейс наследуемый от одного или нескольких суперинтерфейсов.

Базовый интерфейс — это аналог базового класса в иерархии наследований интерфейсов, т. е. это интерфейс, находящийся на вершине иерархии наследования.

Иерархия наследования или иерархия классов — дерево, элементами которого являются классы и интерфейсы.

1.3. Полиморфизм

Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. И как следствие из предыдущего определения, полиморфизм — это способность обьекта использовать методы производного класса, который не существует на момент создания базового.

Полиморфизм даёт возможность использовать одни и те же методы для объектов разных классов.

Неважно, как эти объекты устроены, — в ООП можно сказать самолёту и квадрокоптеру: «Лети», и они будут делать это как умеют: квадрокоптер закрутит лопастями, а самолёт начнёт разгон по взлётно-посадочной полосе. Грубо говоря, полиморфизм — это диспетчер в аэропорту. Ему неважно, какую топливную систему предусмотрел авиаконструктор и как работает система форсажа — он просто даёт команду: «Взлёт разрешаю». После этого на лайнере начинают происходить какие-то свои внутренние процессы, на которые диспетчер уже не влияет.

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

Немного теории

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

2. Single responsibility (Принцип единственной ответственности)

1 класс = 1 задача
1 сущность = 1 задача

❌ Это неправильная конструкция.
❌ Это неправильная конструкция.

✅ Это - правильная. Здесь вынесены все методы в отдельные классы.
✅ Это - правильная. Здесь вынесены все методы в отдельные классы.

3. Open–closed (Принцип открытости/закрытости)

Сущности должны быть открыты для расширения и закрыты для изменения.

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

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

❌ Это не правильный подход.
❌ Это не правильный подход.
✅ А это правильный подход.
✅ А это правильный подход.

4. Liskov substitution (Принцип подстановки Лисков)

Сущности, используюущие родительский тип (класс), должны точно также работать и с дочерними типами (классами).

Другими словами, наследуемый класс должен дополнять, а не замещать, поведение базового класса.

Функция работает как с родительским классом так и с дочерним.
Функция работает как с родительским классом так и с дочерним.

Ниже на схеме наследуемые классы добавляют методы к родительскому классу. Красным показан неправильный подход, когда в наследуемом классе переопределяется метод родительского класса.

Наследуемые классы лишь добавляют методы.
Наследуемые классы лишь добавляют методы.

5. Interface segregation (Принцип разделения интерфейса)

Сущности не должны зависеть от методов, которые они не используют.

На схеме показан неправильный подход: классы второй и третий не используют часть методов родительского класса.

❌ Неправильный подход
❌ Неправильный подход

Ниже показан правильный подход. Нужно разработать три интерфейса и имплементировать их в созданные классы.

-9

6. Dependency inversion (Принцип инверсии зависимостей)

Модули высокого уровня не должны зависеть от модулей более низкого уровня. Все они должны зависеть от абстракций. А абстракции, в тоже время, не должны зависеть от деталей.

Представим завод, на котором есть три верхние сущности: работники, станки, электричество. Сущность станки имеет сущности более низкого порядка. Работники имеют соответствующие компетенции для работы с этими станками. Электричество поставляется 380V.

❌ Сущности более низкого порядка влияют на сущности верхнего порядка.
❌ Сущности более низкого порядка влияют на сущности верхнего порядка.

Но сломался один станок (сломалась деталь) и его поменяли на более новый станок, который потребляет теперь уже 220V. Кроме того, нет ни одного работника умеющего на нем работать.

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

Для решения этой проблемы должны быть сущности:
пульт управления (сущность - одинаковый интерфейс для всех станков), с которыми взаимодействуют работники,
трансформатор (сущность - на входе 380V, а на выходе оба вида напряжения).

✅ Сущности взаимодействуют через дополнительные интерфейсы. И на прямую не влияют друг на друга.
✅ Сущности взаимодействуют через дополнительные интерфейсы. И на прямую не влияют друг на друга.

ВСЁ ❗️😀