Давайте изучим принципы SOLID на одном большом примере. Это также самый распространенный вопрос на собеседованиях.
Во-первых, нам нужно разобрать аббревиатуру.

S. Принцип единой ответственности

O. Открытый закрытый принцип

Принцип подстановки Л. Лискова

I. Принцип разделения интерфейсов

D. Принцип обращения зависимостей

Довольно круто, да? давайте рассмотрим каждый из них.

1. Принцип единой ответственности: класс должен иметь определенную ответственность и ничего больше. Это должно измениться только по одной причине.
Таким образом, ответственность на самом деле инкапсулирована внутри класса, говоря, что наш класс HAS-A Responsibility.

Как вы можете видеть в этом UML: класс IPhone имеет свойства, которые могут определять IPhone. Но подождите, что здесь делает CalculateTotal_Sale()?

Вы видите проблему? Расчет общего объема продаж не входит в обязанности класса IPhone.

Проблема:что, если завтра у меня будет класс iPad, мне придется перекодировать тот же метод и для этого класса.

Решение. Вместо этого мы можем передать ответственность за расчет общего объема продаж новому классу "Продажа". И iPhone может инкапсулировать продажи для расчета общей суммы продаж.

Здесь, теперь Sale отвечает за вычисление TotalSale(). И наш класс IPhone просто повторно использует эту ответственность, а не создает ее сам. Следовательно, у iPhone есть функция TotalSale().

2. Принцип открытого-закрытого: открытый для расширения, закрытый для модификации. Программные сущности (объекты, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это просто означает, что объекты должны быть легко расширяемы без изменения самих себя.

Проблема. Мы знаем, что разные модели iPhone имеют разные характеристики, верно? iPhone XR имеет другие характеристики, чем iPhone X.

Решение. Мы должны абстрагироваться от нашей проблемы, чтобы наш класс iPhone мог делегировать свою реализацию на основе типа модели.

Здесь класс IPhone закрыт для модификации, но открыт для расширения. Обратите внимание, что класс IPhone теперь имеет инкапсулированную модель IModel. Таким образом, свойство ModelName больше не является обязанностью класса IPhone. И снова мы используем наш первый принцип проектирования (единая ответственность). И наша абстракция достаточно универсальна, чтобы соответствовать любой модели, когда-либо созданной Apple.

Наш клиент: iPhone не нужно менять только потому, что Apple решила выпустить новую модель (закрыта для модификации). Apple теперь может иметь «n» моделей (открыто для расширения).

3. Принцип подстановки Лискова: родительские классы должны легко заменяться их дочерними классами.

В нашем UML выше, как мы видим, мы создали интерфейс IModel, теперь любой класс, который наследует IModel, должен определить все свои методы, чтобы удовлетворить отношение IS-A, иначе вы получите ошибку времени компиляции. Но вашего компилятора не будет, когда вы наследуете классы, а не интерфейсы.

Проблема. Вы когда-нибудь видели iPhone со Snapdragon? Черт возьми, нет!!!, верно?

Поэтому, когда мы попытаемся собрать процессор Snapdragon для нашего класса IPhone, он выдаст ошибку, как вы можете видеть в классе Snapdragon865, он выдает нереализованное исключение. Потому что iPhone X и XR совместимы только с чипами Bionic.

Решение. У нас не должно быть дочерних классов, которые не полностью реализуют поведение своих родителей.

4. Принцип разделения интерфейсов: Помните принцип единой ответственности? Это тот же принцип, но вместо этого он применяется к интерфейсам.

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

Вместо этого у нас должно быть много клиентских интерфейсов, а не один общий интерфейс.

У нас есть 2 iPhone XR и X: IPhone XR имеет одну камеру, а IPhone X имеет двойную камеру. Так что было бы неправильно для IPhoneXR определять DualCamera, поскольку у него нет двойной камеры. Здесь IPhone XR определяет только одиночную камеру, двойная камера выдает исключение.

Решение. Как уже говорилось, у нас не должно быть универсального интерфейса. У нас может быть абстракция верхнего уровня, но реализация должна быть разбита на мелкие части в соответствии с их требованиями.

IPhone класса имеет камеру, которая может быть одинарной или двойной камерой в зависимости от типа модели. для XR это установка с одной камерой, а для X — установка с двумя камерами.

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

Проблема. У нас тесная связь между IOSUpdate и iPhone. Так что, если завтра Apple потребуется обновление IOS для iPad, тогда нам придется создать еще одну тесную связь, и поддерживать ее становится все труднее по мере роста приложения.

Решение. Абстракция. Вместо прямого общения с конкретным классом должен быть слабосвязанный класс.

Как вы можете видеть выше, IUpdate — это интерфейс, который имеет 2 конкретных класса IPhoneUpdate и IPadUpdate. И когда наш клиент, которым в данном случае является iPhone, запрашивает обновление, мы можем просто внедрить зависимость IPhoneUpdate в конструктор или метод.

Для iPad он внедряет зависимость IPadUpdate.

Поэтому в будущем, когда Apple понадобится новый продукт, скажем, MacBook, мы можем просто создать еще один класс MacBookUpdate, который будет конкретным представлением интерфейса IUpdate. И клиентский MacBook может разрешить зависимость «IUpdate, MacBookUpdate».

Инверсия зависимостей сама по себе является обширной темой для изучения.

Я рассказал об этом в другом своем блоге, не стесняйтесь посетить, если хотите понять, как он работает:



Свяжитесь со мной:

https://github.com/RikamPalkar

https://www.linkedin.com/in/rikampalkar/

https://www.c-sharpcorner.com/members/rikam-palkar

https://twitter.com/rikam_cz