Мне довольно часто встречаются разработчики, которые не слышали о принципах SOLID (мы подробно рассказывали о них здесь. — Пер.) или объектно-ориентированного программирования (ООП), или слышали, но не используют их на практике. В этой статье описываются преимущества принципов ООП, которые помогают разработчику в его ежедневном труде. Некоторые из них хорошо известны, другие — не очень, так что статья будет полезна и новичкам, и уже опытным программистам.

Напоминаем: для всех читателей «»Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».

Skillbox рекомендует: Образовательный онлайн-курс «Java-разработчик».

DRY (Don’t Repeat Yourself)


Довольно простой принцип, суть которого ясна из названия: «Не повторяйся». Для программиста это означает необходимость ухода от дублирующего кода, а также возможность использовать в работе абстракцию.

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

Это нужно для того, чтобы упростить код и сделать его поддержку проще, что является основной задачей ООП. Злоупотреблять объединением тоже не стоит, поскольку один и тот же код не пройдет проверку как с OrderId, так и с SSN.

Инкапсуляция изменений


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

Если вы пишете на Java, то по умолчанию присваивайте private методам и переменным.

Принцип открытости/закрытости


Этот принцип можно легко запомнить, прочитав следующее утверждение: «Программные сущности (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения». На практике это означает, что они могут позволять менять свое поведение без изменения исходного кода.

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

Вот пример кода, который нарушает этот принцип.



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

Кстати, открытость-закрытость — один из принципов SOLID.

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


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



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

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




Выше приведен пример кода, где AppManager зависит от EventLogWriter, который, в свою очередь, тесно связан с AppManager. Если нужен иной способ показать уведомление, будь это пуш, SMS или email, нужно изменить класс AppManager.

Проблема может быть решена при помощи DIP. Так, вместо AppManager мы запрашиваем EventLogWriter, который будет введен при помощи фреймворка.

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

Композиция вместо наследования


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

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

Даже “Effective Java” Джошуа Блох (Joshua Bloch) советует отдавать предпочтение композиции, а не наследованию.

Принцип подстановки Барбары Лисков (LSP)


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

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

Вот участок кода, который противоречит LSP.



Метод area(Rectangle r) просчитывает площадь Rectangle. Программа упадет после выполнения Square, поскольку Square здесь не является Rectangle. Согласно принципу LSP, функции, которые используют ссылки на базовые классы, должны иметь возможность использовать и объекты производных классов без дополнительных инструкций.

Этот принцип, который является специфичным определением подтипа, был предложен Барбарой Лисков в 1987 году на конференции в основном докладе под названием «Абстракция данных и иерархия» — отсюда и его название.

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


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

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

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

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



Программирование для интерфейса, а не реализации


Здесь все понятно из названия. Применение этого принципа ведет к созданию гибкого кода, который сможет работать с любой новой реализацией интерфейса.

Следует использовать тип интерфейса для переменных, возвращаемых типов или же типа аргумента метода. Пример — использование SuperClass, а не SubClass.

То есть:

List numbers= getNumbers();

А не:

ArrayList numbers = getNumbers();

Вот практическая реализация того, о чем говорится выше.



Принцип делегирования


Распространенный пример — методы equals() и hashCode() в Java. Когда требуется сравнить два объекта, то это действие делегируется соответствующему классу вместо клиентского.

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



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

Skillbox рекомендует:

Комментарии (24)


  1. questor
    31.05.2019 16:10
    +4

    Это называется «галопом по Европам»: без вдумчивого погружения в каждый вопрос, проскакали по верхам. Практически с тем же успехом можно было просто написать список из 10ти пунктов, чтобы те, кто не знает пошли гуглить конкретику.


    1. Acuna
      01.06.2019 16:05

      Практически с тем же успехом можно было просто написать список из 10ти пунктов, чтобы те, кто не знает пошли гуглить конкретику.

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

      Но композицию, LSP и им подобные да, надо все-таки гуглить… Но как говорили преподаватели у меня в универе — «Университет — это место, где учат находить знания».


  1. agalakhov
    31.05.2019 16:10
    +1

    Подавляющее большинство этих принципов относятся не конкретно к ООП, а к программированию вообще. В любой парадигме и на любом языке. Слово "класс" при необходимости меняется на слово "функция", "структура", "модуль", "файл исходных текстов" и т.д. в зависимости от особенностей конкретного языка. При отсутствии в языке явных слов private и public вместо них могут работать области видимости, слово "static" в plain C, непрозрачные структуры там же, схемы именования и др.


    1. sshikov
      31.05.2019 19:03
      +2

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


  1. priority
    31.05.2019 17:05
    +1

    Жду статью о бинарном поиске и сортировках!


    1. Palpatin
      31.05.2019 18:58

      Про сортировки будет очень интересно почитать!


      1. kzhyg
        31.05.2019 19:02
        +9

        Наконец-то на хабре появятся статьи о сортировке!


      1. shishmakov
        02.06.2019 22:48

        Ждём 10 способов реализовать сортировку «Пузырьком»!


        1. MaxVetrov
          03.06.2019 11:33

          Берем «пузырь», на нем пишем «сортировка» и реализуем потребителю 10-ю различными способами:) Любая задача имеет решение.)


  1. Frintezza93
    31.05.2019 17:05
    +2

    Liskov substitution описан, как мне кажется, неверно.
    Даже если предположить существование некоторого метода

    area(Rectangle r)
    , работать он должен одинаково как для квадрата так и для прямоугольника. Иерархия квадрат -> прямоугольник нарушает LSP при определении контракта на изменение. (изменение высоты у прямоугольника не влечёт изменение ширины, в отличие от квадрата).


    1. fogone
      31.05.2019 18:22

      Да, почему-то очень любят использовать связь прямоугольник-квардрат как иллюстрацию наследования, хотя это очень плохой пример и (подумав) такой код никогда в реальной жизни не напишешь. Мало того, что очень сложно выбрать, кто от кого наследуется, так еще и сразу начинают лезть несоответствия вроде того, что описал Frintezza93. Из за этого обычно не рекомендуют наследоваться от реализаций, но только от специально подготовленных астрактных классов или еще лучше реализовывать интерфейсы типа Figure с метдами вроде area() и дргуих.


      1. Carburn
        31.05.2019 21:20
        +1

        Квадрат это частный случай прямоугольника. Не каждый прямоугольник является квадратом. Квадрат наследуется от прямоугольника.


        1. VolCh
          31.05.2019 21:35
          +1

          Квадрат — это также частный случай ромба. Некоторые ромбы — это прямоугольники, а некоторые прямоугольники — это ромбы. Уверены, что тут вообще наследование применимо?


          1. altexxx
            31.05.2019 22:15

            Квадрат — это сын ромба и прямоугольника, и наследует геометрические свойства обоих родителей, являясь при этом внуком четырехугольника и вместе со своими родителями наследуя и его геометрические свойства.


            1. Deosis
              03.06.2019 09:24

              Скорее квадрат — внук параллелограмма и правнук трапеции.


        1. fogone
          01.06.2019 12:50

          Тогда возможно он должен быть не потомком, а экземпляром прямоугольника?


    1. altexxx
      31.05.2019 22:27

      Пример и правда не лучший — стоило бы убрать сеттеры и передавать параметры в конструктор (Rectangle(int width, int height) и Square(int size), тогда бы было меньше вопросов. Классы не обязаны быть изменяемыми.


  1. fzn7
    31.05.2019 20:16

    Ты им — должен знать, а они в ответ — нам не надо


  1. Vahman
    31.05.2019 20:27
    +1

    Ещё пара сотен таких статей, и люди запомнят принципы


    1. aol-nnov
      01.06.2019 07:28

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


  1. Carburn
    31.05.2019 21:15

    Основные принципы это абстракция, инкапсуляция, наследование и полиморфизм.


    1. VolCh
      31.05.2019 21:37

      Надо не путать принципы обьектно-ориентированных программирования и проектирования. Или не разделять их.


  1. neurocore
    01.06.2019 14:20

    когда-нибудь я соберусь с мыслями и пойму это


  1. shishmakov
    02.06.2019 22:45

    Обязательно напишите статью как написать программу «Привет Мир!» на 10 разных ЯП. Это очень нужно на Habrahabr!