Принципы SOLID (продолжение ООП)

В предыдущей статье мы рассмотрели базовые принципы ООП (инкапсуляция, наследование и полиморфизм). Также многие стараются придерживаться принципов SOLID, которые являются чем-то более расширенным, чем основные принципы ООП. Итак, давайте рассмотрем каждый из 5 принципов.

S — Single responsibility — Принцип единственной ответственности. (ссылка на вики)

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

class Request {
public Request makeRequest(String host, String path) {
//some implementation
}
}

class Response {
public Response getResponse() {
//some implementation
}
}

class NetManager {
private Request request;
private Response response;
public NetManager(Request request, Response response) {
this.request = request;
this.response = response;
}
public void getData() {
//use request to get response
}
}

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

O — Open closed principle — Принцип открытости/закрытости (ссылка на вики)

Этот принцип говорит о том, что наши классы должны быть доступны (открыты) для расширения, но (недоступны) закрыты для изменения. На мой взгляд, этот принцип пересекается с инкапсуляцией, потому как исходя из этого принципа нужно закрывать от конечного потребителя реализацию и не давать возможности изменять код. Как этого добиться? Использовать private поля и публичные методы, с учетом принципа открытости к расширению методы могут быть protected, но чтобы не давать возможности к переопределению, их можно помечать final. А сам класс оставить с модификатором public, чтобы можно было наследоваться от него. И еще один лайфхак — все главные классы можно переместить в модуль под названием Ядро и подрубать в проект в виде .aar, чтобы уж точно никто не мог модифицировать код.

L — Liskov substitution principle — Принцип подстановки Барбары Лисков (ссылка на вики)

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

I — Interface segregation principle — Принцип разделения интерфейса (ссылка на вики)

Этот принцип весьма прост — он говорит о том, чтобы дробить интерфейсы на мелкие. Для чего это делать — предположим есть 1 класс и ему нужно 3 метода. Если все эти 3 метода записать в 1 интерфейсе, то, возможно, мы придем к следующей ситуации. Через какое-то время нам нужен будет класс, в котором понадобится 1 из этих 3 методов. Что делать тогда? Имплементировать весь интерфейс и оставить 2 метода пустыми? Данный принцип рекомендует разбить наш большой интерфейс на 3 мелких и имплементировать в первом классе все 3, а во втором только 1 из них. В итоге, когда поменяется какой-либо метод, то в том классе, в котором они не нужны, изменений и вовсе не будет. Или рассмотрим следующий пример — есть 2 класса, обоим нужен один и тот же метод, а еще первому классу нужен другой метод, который не нужен во втором классе, тогда мы напишем 2 интерфейса в каждом из которых по 2 метода, но в обоих интерфейсах будет один и тот же метод. И когда необходимость возникнет менять один метод в первом интерфейсе, то нужно будет не забыть, что точно такой же метод есть и в другом интерфейсе. Опять мы пришли к двойной работе и чтобы ее избежать стоит разделять логику в интерфейсы. т.е. лучше много мелких интерфейсов, чем 1 громоздский.

D — Dependency inversion principle — Принцип инверсии зависимостей (ссылка на вики)

Формулировку возьмем из википедии, так как она весьма ясна и понятна

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

 

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

Очень рекомендую к прочтению книгу Роберта Мартина — Чистый код.

Запись опубликована в рубрике Программирование Java. Добавьте в закладки постоянную ссылку.

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *