Принципы SOLID - без этого, даже не идите на собеседование!

solid java

Если вы не знаете, что такое SOLID, то можете даже не идти на собеседование, да, да, я серьезно ;). Поэтому давайте разбираться:

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

  • S - Single-responsibility principle - принцип единой ответственности
  • O - Open-closed principle - принцип открытости/закрытости.
  • L - Liskov substitution principle - принцип подстановки Барбары Лисков
  • I - Interface segregation principle - принцип разделения интерфейса
  • D - Dependency Inversion Principle - принцип инверсии зависимостей

Запомнить названия по первости будет сложно, да к этому и не нужно стремиться. Главное понимать содержимое и те идеи, которые предлагаются в каждом из них. Я покажу принципы SOLID на примере языка Java, однако смысл применим к любому языку программирования.

S - Single-responsibility principle

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

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

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

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

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

Советую обратить внимание на следующие приемы, которые помогают соблюдать данный принцип:

O - Open-closed principle

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

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

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

Одним словом, изменять код базового класса строго настрого запрещено!

L - Liskov substitution principle

Принцип подстановки Барбары Лисков, самый не понятный принцип из-за названия :). Но все достаточно просто и немного похоже на предыдущий принцип. Принцип гласит, что поведение методов в дочернем классе должно следовать принципам базового класса, а не изменять их. То есть, дочерний класс переопределяя методы или переменные, не должен менять заложенную логику базового класса.

Например:

class Rectangle {
    
    private int width;
    private int height;
    
    public int getWidth() {
        return width;
    }
  
    public int getHeight() {
        return height;
    }

    public void setWidth(int width) {
        this.width = width;
    }
  
    public void setHeight(int height) {
        this.height = height;
    }
}

class Square implements Rectangle {
    
    public void setSize(int size) {
        super.setWidth(size);
        super.setHeight(size);
    }
}

Выглядит немного странно, правда? Класс Square(Квадрат) наследуется от Rectangle(прямоугольник), а так как у квадрата все стороны равны, в классе Square мы просто задаем ширину и высоту, одну и ту же. В итоге, мы испортили изначальную идею класса Rectangle, в который заложена логика, что стороны могут отличаться. Получается, не меняя базовый класс, мы умудрились нарушить данный принцип.

В этом примере Square должен быть отдельным классом и ни в коем случае не наследоваться от Rectangle. То есть, поведение методов не должно изменяться. Если написано, что метод возвращает ширину, значит он и должен возвращать ширину, а не что-то другое.

I - Interface segregation principle

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

Например:

interface ItemClick {
    void onClick()
    void onLongClick()
}

Итак, есть интерфейс, который требует реализовать два метода: короткое нажатие и длинное нажатие. Но что, если нам необходимо только короткое нажатие? В этом случае у нас будет, что-то такое:

class MyClass implements ItemClick {
    void onClick() {
        // тут реализуем необходимую логику
    }
    void onLongClick() {
        // а вот тут нам нечего не надо делать, но все равно приходится
        // реализовать этот метод потому, что этого требует интерфейс
    }
}

Что-бы этого избежать необходимо сделать два разных интерфейса, которые можно применять по отдельности.

interface ItemClick {
    void onClick()
}
interface ItemLongClick {
    void onLongClick()
}

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

D - Dependency Inversion Principle

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

Например, у вас есть следующие модули в приложении: presentation(модуль для показа чего либо на экране) и data(модуль для хранения и получения данных). Соответственно presentation будет зависеть от модуля data, так как ему необходимо получать данные для отображения их на экране, но вот модулю data совсем не нужно ничего знать о presentation, ему абсолютно все равно, как эти данные будут отображаться, модуль data просто предоставляет данные.

Обновлено 31 мая 2020

Теги:

"Сайт использует cookie-файлы для того, чтобы вам было удобнее им пользоваться. Для продолжения работы с сайтом, вам необходимо принять использование cookie-файлов."