Изучите принципы проектирования SOLID на Java, написав код


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


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

 

1. Что такое SOLID и почему вам стоит его использовать


В своей статье «Принципы проектирования и шаблоны проектирования» Роберт С. Мартин представил эти принципы, а позже Майкл Фезерс ввел эту аббревиатуру.


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

 

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


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


Это значит, что только при изменении функциональности нашего класса он должен измениться.


У вас могут быть преимущества, например, будет проще адаптация новых участников, тестирование и т. д.


Многие фреймворки и библиотеки следуют этому принципу. Например, CrudRepository из Spring Data, Validation API, Date/Time API.

 

Вариант использования


Представьте, что есть класс ProductService, который имеет две задачи и обязанности:


Манипулирование грубыми операциями над продуктом.
Отправка SMS и EMAIL-уведомлений на основе грубых операций.
SingleResponsibility.java


Что произойдет, если требования этого класса изменятся, и теперь есть текстовое электронное письмо для отправки и электронное письмо в формате HTML, которые требуют различных реализаций метода sendEmail()?


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


После этого ProductService класс изменяется в зависимости от причин, связанных с уведомлением, и причин, связанных с продуктом.

 

Было бы лучше разделить эти проблемы:


SingleResponsibility.java


Мы создаем разные методы для каждого изменения требований, идея состоит в том ещё, чтобы разделить проблемы здесь в solid c#, а решения для решения такого рода проблем — это совершенно другая тема.


Общие обсуждения


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


Главное не переусердствовать. Попробуйте подумать об одной ответственности, даже если методы выполняют разные операции, работают ли они с одной и той же целью?

 

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


Этот принцип гласит, программные объекты (классы, модули, функции) должны быть открыты для расширения, и закрыты для модификации.


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


Вариант использования


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


AreaCalculator.java


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


Мы можем использовать простую абстракцию и полиморфизм, чтобы справиться с этим.


AreaCalculator.java


Таким образом, AreaCalculator он не будет знать, какие и сколько форм нужно обрабатывать, и свою собственную реализацию.


Теперь этот класс открыт для расширения ( Triangle, Circle, Rectangle) или закрыт для модификации (не будет метода с добавлением другой логики каждый раз, когда появляется форма).


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


— Барбара Лисков


В двух словах:


A ChildClass должен расширять a только ParentClass в том случае, если мы можем заменить ParentClass объект объектом ChildClass без изменения программы, в противном случае мы должны использовать композицию или делегирование.


Вариант использования


Представьте, что есть родительский класс Bird. У нас может быть много дочерних классов, таких как Воробей, Страус, Орел, Сокол и т.д.


Правильно ли иметь класс Sparrow и Ostrich, расширяющий Bird класс? Следуя принципу замещения Лискова, это не так.


Страус также является птицей, не имеет смысла, чтобы его объект мог летать, потому что он не может.


Мы могли бы разбить наследование на меньший уровень, чтобы следовать этому принципу.


Таким образом, класс Bird не будет неправильно заменен классом Sparrow в любом сценарии.


5. Принцип разделения интер-фейса


Клиент не должен реализовывать интерфейс, который не использует.


Это похоже на принцип ответственности, но на уровне интерфейса.


Вариант использования


Представьте, что есть интерфейс Worker с двумя методами: work() и sleep(). Таким образом, каждый конкретный рабочий класс сможет работать и спать.


Есть ли смысл в том, чтобы робот-воркер реализовывал метод sleep(), даже если мы знаем, что это невозможно?


Мы можем решить эту проблему, разбив интерфейс на мелкие и конкретные интерфейсы.


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


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


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


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


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


Вариант использования


Нашим высоким уровнем будет CustomerService, нашим низким уровнем будут MySqlImpl и PostgreSqlImpl, а нашей абстракцией будет CustomerRepository.


Обслуживание клиентов (высокий уровень)


CustomerRepository (абстракция)


MySqlImpl (низкий уровень)


PostgreSqlImpl (низкий уровень)


Таким образом, любая реализация CustomerRepository, которую вы используете с CustomerService, будет независимой от уникальной базы данных, например, в случае, если вам в итоге потребуется перейти на SqlServer, вы будете зависеть от наличия другой реализации, а ваш высокоуровневый CustomerService не будет знать, что происходит.


Весь код, относящийся к этой статье, можно найти в Project GitHub Repo . Не стесняйтесь вносить свой вклад в GitHub любым способом, вклад более чем приветствуется.