Очень простое объяснение принципов SOLID

    Disclaimer: Всем можно, ну а я чем хуже?!

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

    Попробуем разобраться в этих принципах на пальцах, без примеров кода и СМС.

    S — Принцип единственной ответственности (Single Responsibility Principle или SRP)

    Должна быть одна и только одна причина для изменения класса («A class should have only one reason to change.» Robert C. Martin.)
    Представим, что у вас на компе лежит пяток любимых песен, пара фильмов и фотки котиков. Вы валите все это в «Мои документы» и, в целом, радуетесь жизни.

    Потом вы качаете еще фильмов, новый альбом любимой группы и десяток новых котиков. В «Моих документах» становится как-то не комфортно и вы раскладываете все по папочкам.

    Музыка
    Фильмы
    Котики

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

    Музыка
      The Doors
        Waiting for the Sun
          Hello, I Love You.mp3
          ...
        ...
      RHCP
      Агутин
    Видео
      Сериалы
      Фильмы
      Студия Private
    Фото
      Котики
      Рыбалка
      Преферанс на даче
      куртизанки
    

    И что мы тут имеем?

    • Музыка — отвечает ТОЛЬКО за музыку.
    • The Doors — отвечает ТОЛЬКО за музыку, сыгранную группой The Doors
    • Waiting for the Sun — отвечает ТОЛЬКО за альбом Waiting for the Sun
    • Hello, I Love You.mp3 — отвечает ТОЛЬКО за песню Hello, I Love You

    Какая может быть причина изменения «The Doors»? Только одна — (качественное или количественное) изменение песен группы The Doors. А вот удаление надоевшего сериала из "/Видео/Сериалы/" такой причиной никак не может являться, так же как и переименование «Музыка» в «Music».

    Какие можно сделать выводы?

    1. SRP — это про декомпозицию (раскладываем по подпапочкам) и связность («Hello, I Love You.mp3» связана только с «Waiting for the Sun» и ей плевать на изменения в "../Сериалы". С другой стороны, все песни «The Doors» находятся внутри нее и их не должно быть в папке «Котики»).
    2. Уровень абстракции причины для изменения должен быть не выше уровня абстракции изменяемой сущности. Добавление в «Музыка» подпапки «Алла Пугачева» никак не может быть причиной изменения «Waiting for the Sun».
    3. Не нужно доводить до абсурда. Если у вас три песни, один видос и пять фоток котиков, то они прекрасно будут смотреться в одной куче — рассовывание их по папкам только все запутает. Как и сборник «The best of The Doors» не стоит разбивать на подпапки по годам, в каждой из которых будет по одной песне.

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

    Программные сущности (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения («Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.» Bertrand Meyer)
    Вернемся к тому шагу нашей истории, когда вы подключили скоростной безлимит. Допустим, что в первую очередь вы накачали всевозможных фильмом про сантехников кинокомпании Private и, так как они все были про сантехников, создали папку "/Видео/Про сантехников/".

    Через некоторое время вы скачали еще фильмов этой студии, но уже про доставку пиццы. Мало того, ваша новая девушка написала вам СМС «Я скачала фильм „Афоня“ и сохранила его в папку /Видео/Про сантехников/, ок?». И вроде бы все верно — про сантехника, но есть нюанс.
    И тут вам становится понятно, что изменение функциональности невозможно без модификации уже существующей структуры папок(классов), а это явное нарушение принципа OCP.

    Как в этом случае надо было сделать? Очень просто — изначально спроектировать систему таким образом, чтобы новый функционал не требовал изменения старого кода. Т.е. не хардкодить папку "../Про сантехников/", надеясь, что в будущем там только про них и будет, а повысить уровень абстракции до "../Студия Private/" и спокойно скармливать ей и сантехников, и разносчиков пиццы, и прочая-прочая…

    А для Афони создать новый класс, например "../Мосфильм/", расширив класс "/Видео/"

    Выводы:

    1. Думайте наперед о том, что будете делать, если появятся «другие» фильмы про сантехников. Может стоит сразу по уму сделать, чтоб не переделывать потом?
    2. Этот принцип, в основном, про абстрактные классы ("../Студия Private/").

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

    Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы.
    Ну тут совсем просто.

    Для объяснения давайте обратимся к милоте и няшности, а конкретно к папке "/Фото/Котики/".
    Котиков мы любим и фотографий собрано у нас очень много.

    Фото
      Котики
        Рыжие
        Полосатые
        Мокрые
        Черные
        Манулы


    Бывает даже прямо запускаем слайдшоу по всей корневой папке и любуемся. И вот, в один прекрасный момент, когда вы уже практически достигли нирваны, на экран выводится:
    Невозможно отобразить файл "/Фото/Котики/Книги/Кот в сапогах.fb2"
    Как потом выяснилось, ваша подруга решила поднять градус милоты и отнаследовала «Котики» новой подпапкой «Книги», грубо нарушив LSP, так как подкласс «Книги» ну никак нельзя использовать вместо базового класса «Фото».

    Выводы:

    1. Название пугающее, определение сложное, но сам принцип прост и интуитивно понятен.
    2. Если ваш метод ожидает на вход «Фото», то ему должно быть все равно, какого наследника от «Фото» вы ему подсунете: «Манулы», «Рыжие» или «Мокрые», но если ему на вход придет «Книги», то он, вполне ожидаемо, поперхнется чаем.

    I — Принцип разделения интерфейса (Interface Segregation Principle или ISP)

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

    Вам надоел стандартный проигрыватель музыки и вы решили скачать новый, супермодный и хайповый. Он даже умеет отображать обложку музыкального альбома и выводить субтитры исполняемой песни. Но вот беда, если в папке альбома отсутствуют файлы «cover.jpg» и «subtitles.txt», то проигрыватель падает с ошибкой. И вот вы, проклиная все на свете, начинаете создавать во всех подпапках с альбомами эти файлы заглушки.

    Т.е., проводя некорректные аналогии, мы обязали класс «Музыка» и всех его наследников реализовывать интерфейс «AudioCoverSubtitles». При этом, полностью этот интерфейс реализует только альбом «Waiting for the Sun», альбом «The best of The Doors» реализует только часть «Audio+Cover», а все остальные только «Audio».

    Это подводит нас к мысли, что имеет смысл разделить толстый интерфейс «AudioCoverSubtitles» на три небольших «Audio», «Cover» и «Subtitles» и применять их только там, где они действительно нужны.

    Выводы:

    1. ISP — это, внезапно, про разделение интерфейсов.
    2. Если ваш интерфейс вынуждает вас создавать методы заглушки, то это плохой интерфейс и по нему стоит пройтись ножницами.

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


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

    Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
    Модуль «The Doors» не должен зависеть от того, какого рода аудио файлы в нем сложены, .mp3, .flac или .wav.

    Модуль «The Doors», его подмодуль «Waiting for the Sun» (и все остальные), зависят от верхнеуровневой абстракции «Музыка», которая определяет их реализацию (то, что внутри у них музыка).

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

    Выводы:

    1. DIP — это про то, частное должно зависеть от общего, а не наоборот.
    2. DIP — это «Больше абстракций богу абстракций!»
    3. DIP — это еще и про причины и следствия, про правильный ответ на вопрос «Ветви качаются от того, что дует ветер или ветер дует от того, что ветви качаются»

    Спасибо за внимание и хороших выходных!

    Комментарии 60

      +2
      Disclaimer: Всем можно, ну а я чем хуже!?


      А чем лучше?
      Вангую следующее названия для «цикла статей о солид» — очень очень очень (PG-13) простое объяснение solid.
        +3

        О, свежая тема! :)

          +1
          «об этой картине можно говорить бесконечно!» (с) Корона Российской Империи
          :)
        • НЛО прилетело и опубликовало эту надпись здесь
            0

            Добавить в тот же уровень privare & Мосфильм. Множественное наследование? Ничего что наиболее массовые ООП языки его не поддерживают?

              0
              Наследование представляет собой отношение IS-A. Объясните пожалуйста. Как мне замапить в своей голове структуру видео\мосфильм\про_сантехников\Афоня на структуру классов. Что тут is-a чего?
              Лично я не могу говорить ничего об OCP ибо «простые примеры» вызывают неслабый такой диссонанс с привычными паттернами ооп.
                0
                Афоня is a видео,
                Афоня is a из_мосфильма,
                Афоня is a про_сантехников
                  0
                  Афоня is a про_сантехников

                  Как минимум тут что то лишнее, не находите?
                  И да, такая структура не соответствует «видео\мосфильм\про_сантехников\Афоня».
                    0
                    > Как минимум тут что то лишнее, не находите?

                    в каком смысле?

                    > И да, такая структура не соответствует «видео\мосфильм\про_сантехников\Афоня».

                    class Афоня: видео, из_мосфильма, про_сантехников

                    Если вы хотите поговорить про адекватность аналогии с файлами — тут я не помогу.
                      0
                      в каком смысле?

                      Всмысле is a тут лишнее, «Афоня» про сантехников, про сантехников — это категория видео, «афоня» != категория видео
                      class Афоня: видео, из_мосфильма, про_сантехников

                      Ну так получается что видео, из_мосфильма, про_сантехников никак между собой не связанны. Хотя это не так, структура то подразумевает вложенность
                      «видео\мосфильм\про_сантехников\...», а не «видео+мосфильм+про_сантехников\Афоня».
                      Если вы хотите поговорить про адекватность аналогии с файлами

                      Да я как раз об этом.
                        0
                        > про сантехников — это категория видео

                        В коде нет «категории». Про сантехников — это абстрактный класс, обладающий свойствами-методами специфичными для фильмов про сантехников. Например, ограничение прав на доступ для аккаутов несовершеннолетних.

                        Да и как-бы не существенно сделаете вы «видео\мосфильм\про_сантехников\» или «видео\про_сантехников\мосфильм\»

                        Можете сделать вложенную структуру:
                        class мосфильм: видео;
                        class про_сантехников: мосфильм;
                        class Афоня: про_сантехников;

                        Но сам класс Афоня от этого не поменяется, как и его «is-a»
                          0
                          В коде нет «категории». Про сантехников — это абстрактный класс, обладающий свойствами-методами специфичными для фильмов про сантехников. Например, ограничение прав на доступ для аккаутов несовершеннолетних.

                          Про возрастные ограничения не верный пример, от категории видео — не зависит (вернее не только от этого), в контексте мосфильма будет 4+ в контексте private 18+.
                          Можете сделать вложенную структуру:

                          Ну вот так как то и думает автор, я вас понял. Для меня же очевидно, что категория видео и производитель являются атрибутами «видео» и место тут имеет связь has-a а не is-a.
                            0

                            Это зависит от целей выделения «про сантехников». Вы говорите возрастные ограничения не зависят, а я, говорю, что не надо детям в листинге выдавать опцию private.


                            is-a тут не обосновано, но и ничему не противоречит — имеет право быть. Хотя пример так себе.

              • НЛО прилетело и опубликовало эту надпись здесь
                  0

                  Множественное наследование очень часто можно заменить на композицию

                0
                По-моему, аналогия с файлами на диске — не самая удачная. Особенно странно получается со множественным наследованием, так как в случае файловой системы у оригинального файла (не рассматриваем ярлыки и симлинки) путь к нему всегда один.
                  +1
                  Понятно, что если все написанное начать один к одному переводить в код, то волосы дыбом встанут в неожиданных местах. Суть поста (помимо того, что он пятничный) в «Попробуем разобраться в этих принципах на пальцах». Изначально вообще хотел назвать «SOLID для самых маленьких», но со студией Privat такое название не очень бьется.
                  А так то толковых, но занудных и трудноусвояемых для неподготовленного человека статей про SOLID более чем в достатке.
                    0
                    Ну как бы в большинстве случаев множественное наследование — нарушение SRP по определению.
                    +6
                    «Очень простое объяснение»

                    почему-то вы еще больше запутали всякими котиками-сантехниками
                      +1


                      RHCP
                      Агутин

                      Совести у Вас нет совсем!!! Фу таким быть! ))))))))
                      З.Ы.: За статью — плюс)
                        +1
                        Думайте наперед о том, что будете делать
                        Думать о том, что собираюсь делать — не проблема (с некоторыми ограничениями), но думать о том, что собираюсь думать — извините, никак.

                        Это я к тому, что будущее постоянно преподносит нам сюрпризы. Абсолютно непредсказуемые. На то оно и будущее. У нас может быть полная определённость относительно того, что книги можно только читать и писать. Напрягши фантазию, мы можем заранее проинтуичить, что их можно ещё покупать, продавать, использовать для хранения денег и для растопки печки. А потом, ближе к сдаче работ, проходит изменение бизнес-процесса и книжки, оказывается, теперь можно ещё и слушать.
                          0
                          Очень хороший комментарий, спасибо!
                          Одна из основных проблем SOLID (как и любых других рекомендательных принципов в программировании) в том, что в большинстве случаев они не дают абсолютно четких критериев определения, соблюдаются они или нет. При принятии конечного решения, разработчик сам выбирает достаточные для него «причину изменения», «возможность добавления функционала», «уровень абстракции вот этого конкретного метода» и т.д.
                            +2
                            Если бы эти правила давали чёткие критерии, то это была бы строгая формализация способов строгой формализации… чего? Да вообще всего, потому что программирование мы применяем во всех мыслимых областях. Боюсь, о таком даже мечтать вредно.

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

                            1. Когда давным-давно нас учили ООП, основным аргументом за эту штуку было то, что поскольку реальный мир состоит из объектов, то если программы тоже состоят из объектов, они более естественным образом отражают реальный мир. Но в этом рассуждении сразу две ошибки. Во-первых, ни из каких объектов реальный мир не состоит. Наше восприятие для удобства осмысления раскладывает его на объекты, притом разных ситуациях по-разному. Во-вторых, программа — не зеркало, и ей совсем не обязательно что-то там отражать. Программа — это инструмент решения задачи. Молоток не обязан отражать процесс забивания гвоздя, он должен забивать гвоздь. Автомобиль не должен моделировать движение по дороге, он должен ехать.
                            Можно заметить, что ООП оказалось чрезвычайно эффективным для создания вспомогательных абстрактных сущностей типа «строка», «файл», «массив», «бинарное дерево», «узел бинарного дерева», «функция» и т.п., а при создании прикладных сущностей типа «клиент», «товар», «пользователь» и др. вечно кривизна и геморрой. Объяснение простое: сущности нашего грешного «большого» мира слишком сложны, нечётки, многообразны, изменчивы и контекстно-зависимы, чтобы без проблем быть уложенными в прокрустово ложе любой жёсткой формализации. А абстрактным вспомогательным штучкам эта жёсткость только на пользу, потому что простота и предсказуемость — это как раз то, что нам в первую очередь от них нужно.

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

                            Соответственно, для себя в дополнение к SOLID я стараюсь придерживаться двух дополнительных правил выживания в сложном мире ООП:
                            1. Не пытаться моделировать программой реальный мир. Выкинуть вообще из головы эту глупую идею. Пусть все объекты в программе будут абстрактными вспомогательными полезняшками, а завязка на реальный мир пусть идёт через реализацию пользовательской метафоры. На верхнем уровне метафоро-ориентированный дизайн, а на нижнем ООП, но применённое по возможности исключительно к абстрактным инструментальным сущностям.
                            2. Наворот третьего уровня иерархии — харам. Аллах покарает. Если вышел на то, что третий уровень логичен и полезен, то ищи уже допущенную ошибку в своих рассуждениях.

                            Не знаю, какие буквы назначить этим двум принципам.
                              0
                              Если бы эти правила давали чёткие критерии, то это была бы строгая формализация способов строгой формализации… чего?
                              В предыдущем комментарии я несколько некорректно подобрал слово. Это, конечно же, не «проблема», а «особенность» этих принципов. В том смысле, что я невольно придал негативный оттенок тому, что не считаю плохим.
                              Однако можно пофантазировать, что это были бы четкие критерии, позволяющие определить, соответствует ли код данному принципу. Например это позволило бы писать автоматические анализаторы на соответствие SOLID.
                              любая иерархия с количеством уровней больше 2 неизбежно содержит внутри себя логическую ошибку. А когда начинают наворачиваться уровень на уровень, снова и снова, вся конструкция становится одним большим памятником человеческой глупости.
                              Вы рассматриваете иерархию как дерево — эдакий развесистый граф на плоскости, тогда как более правильно (имхо) рассматривать ее как фрактал (плохая аналогия, но что уж придумалось), по которому можно провалиться вниз, либо подняться наверх. И, при работе с конкретным уровнем абстракции, вы работаете только с ним и с интерфейсами выше- и ниже-лежащего уровней, если они есть.
                                0
                                позволило бы писать автоматические анализаторы на соответствие SOLID
                                Боюсь, некоторые из SOLIDных принципов не прочекать без понимания решаемой задачи, а это понимание автоматическому анализатору просто неоткуда взять. В явном виде оно в коде может быть не выражено никак.

                                при работе с конкретным уровнем абстракции, вы работаете только с ним и с интерфейсами выше- и ниже-лежащего уровней, если они есть
                                Знаете откуда вообще ноги растут у идеи нарезки уровней абстракции? Оттуда же, откуда и иерархии — из тех двух взаимосвязанных базовых логических операций, коими наш мозг постоянно себя развлекает: дедукции и индукции. От общего к частному или от частного к общему. Вниз или вверх по уровням абстракции.

                                Рассмотрим дедукцию. Имеем некий сложный объект и пытаемся его декомпозировать на составные части. Для того, чтобы это сделать, нам нужно придумать принцип, который будем использовать при делёжке, а потом его применить. Например, человеческую популяцию можно поделить по половому признаку. Или по гражданству. Или по возрастным категориям. Имущество организации можно поделить на движимое и недвижимое. Или по способу учёта — основные средства или малоценка. Человеческий организм можно декомпозировать по функциям, т.е. на органы. Или по типам тканей. Какой бы мы принцип дедукции ни выбрали, его применение нам сразу даёт существенный профит. Появляется возможность говорить про взаимоотношения полов, про влияние госполитики на национальное самосознание, про межпоколенческие конфликты, про особенности хозяйственного использования разных предметов, и т.д. Имеем два уровня абстракции и кучу позитива. Природная жадность не даёт нам остановиться на достигнутом и подталкивает к тому, чтобы развить успех. То есть нарастить снизу ещё один уровень абстракции (напоминаю, мы занимаемся дедукцией). Применить тот же принцип делёжки мы не можем, потому что на предыдущем шаге мы его уже полностью исчерпали. Нужно брать другой. Если на предыдущем шаге мы популяцию делили по полам, то теперь нужно поделить каждое подмножество по, например, возрастам. А теперь, внимание, вопрос: есть ли какой-то реальный смысл в том, что половой уровень абстракции оказался выше возрастного, а не наоборот? Очевидно, нет. Мы с таким же успехом могли бы первую декомпозицию провести по возрастам, а вторую по полам. В итоге имеем: верхний уровень абстракции заслуженно занимает своё положение, а последовательность нижележащих — чистой воды волюнтаризм.

                                С индукцией та же самая фигня. Тоже выбирается обобщающий принцип, и затем продуктивно применяется. Для следующего обобщения нужен уже другой принцип. И опять возникает вопрос: а с какого это бодуна у нас именно такая последовательность, а не наоборот?

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

                                Для того, чтобы до глубины души прочувствовать абсурдность многослойных абстракций, ознакомьтесь с шедевром — общероссийским классификатором основных фондов (известен в народе как ОКОФ). Моя любимая фишка — элемент, к которому можно отнести Большой Адронный Коллайдер. Называется «Ускорители заряженных частиц кольцевые». Лежит в группе, в названии которой присутствует словосочетание «оборудование для балансировки шин» :)))
                                0
                                > Объяснение простое: сущности нашего грешного «большого» мира слишком сложны, нечётки, многообразны, изменчивы и контекстно-зависимы, чтобы без проблем быть уложенными в прокрустово ложе любой жёсткой формализации.

                                Вот потому один из принципов DDD — выделять чётко ограниченные контексты и моделировать сущности и вообще только в рамках них. Как раз в соответствии с SRP — сущность не должна моделировать книгу и в контексте физического объекта, и в контексте печатного издания, и в контексте товара одновременно. Вот в этой «папочке» Book у нас разновидность физического объекта, имеющая например массу и три измерения, а в другой Book — печатное издание, имеющее тираж, коды, копирайты и т. п., а в третьей товар, имеющий цену закупки, продажи, остатки на складе и пр. Причём в каких-то из папочек даже сущностью может не быть Book, если нам не интересен в данный момент поштучный учёт книг, что бы под словом «книга» мы не имели в данном контексте.
                                  0
                                  Контекстная зависимость — самое главное западло. Контексты меняются, и с этим ничего поделать нельзя. Се ля ви. Чётко ограничить контексты, обнести их колючей проволокой и поставить вышки с автоматчиками — не вариант. Система или гибкая, или мёртвая. Нужно, чтобы каждая мелкая корректировка постановки задачи не вызывала масштабной перетряски всего кода. При этом предложение «заранее проинтучить возможные уточнения» не принимается. Заранее проинтуичить можно далеко не всё и не всегда. Если технология разработки ставит нас перед выбором или простое, но негибкое решение, или дико переусложнённая гибкая жуть, то, ИМХО, не надо использовать эту технологию.

                                  (Ага, легко сказать «не использовать», но если все вокруг только это и используют, то нам тупо некуда деваться. Плачем, колемся и продолжаем жрать этот кактус.)
                                    0
                                    Отражать изменения контекстов в коде не вариант? Вышки с автоматчиками передвинуть? Ну или новые поставить, если понял, что это не небольшое изменение существующего контекста, а абсолютно новый пускай и маленький пока?
                                      0
                                      По-всякому бывает. Опытным путём установлено, что доделки/переделки происходят намного проще и безболезненнее когда количество уровней в иерархии классов не больше двух. На учебных и демонстрационных (а также на тщательно отобранных из жизни) примерах, конечно, смотрится круто, как маленькое подкручивание фичи в базовом классе сразу даёт решение задачи, но в реальности бывает так, что сначала полдня роешься в горе исходников выясняешь, куда оно закопано, потом полдня придумываешь, как сделать так, чтобы докрутка чего не похерила в неожиданном месте, а в итоге приходишь к выводу, что «новая вводная» отчаянно просит вообще переделать нахрен всю схему наследования.
                                        0
                                        Бывает, да, всякое. Но нередко как раз множество уровней наследования говорит о нарушении SOLID в целом и SRP в частности. И о нечётком или вообще неправильном выделении контекстов и декомпозиции в целом.
                                          0
                                          Правильная с одной точки зрения декомпозиция оказывается в корне неверной с другой. Предположим, рассмотренное в статье дерево мы делаем как часть автоматизации радиостанции. На голубом глазу делаем простое обобщение: музыку можно пускать по радио, книжки можно читать по радио, а фото и видио — ну никак. Потом наш клиент немножко диверсифицируется и открывает киноконцертный зал. Музыку в нём можно слушать, видео можно смотреть, фотки выставлять, а вот книжки — мимо кассы. Совсем другое обобщение. Два немножко разных противоречащих друг другу видения предметной области, и оба правильные.

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

                                          Контекстами, кстати, в данном случае я называю ситуации, в которых нам нужно провести декомпозицию. Мой любимый пример того, как эти контексты переключаются прямо внутри одной фразы: «Если принесёшь стакан воды, я его выпью». В первой половине фразы стаканом у нас является вода+тара, а во втором только вода.
                                            +1
                                            Так другая (не обновленная, а совсем другая) версия правды и является очень хорошим индикатором необходимости создания нового контекста. Вот в вашем любимом примере напрашиваются две иерархии отражающие, например, переносимые в таре вещества и вещества, употребляемые животными перорально. Собственно вещества можно выделить в третью независимую иерархию, которую будут использовать путём композиции или наследования две первых, а можно создать каждому контексту свою, отражающие чётко его потребности, а связь осуществлять через какие-то примитивные адптеры на сонове DTO/VO, а не прямыми ссылками на сущности из «чужой» иерархии.
                            0
                            А можно с примерами кода? Может, ссылочку на статью?!
                            Не все до конца понял ;(
                              +1
                              Как потом выяснилось, ваша подруга решила поднять градус милоты и отнаследовала «Котики» новой подпапкой «Книги», грубо нарушив LSP, так как подкласс «Книги» ну никак нельзя использовать вместо базового класса «Фото».
                              После это она больше тебе не подруга, она тебе враг)))
                                +1
                                Простой пример из практики: попробуйте на ООП спроектировать поведение геометрических фигур. На вычислении пересечений всплывают определенные неуклюжести подхода. А когда надо еще оптимизировать производительность, то концепции рушатся как карточный домик.
                                  +1
                                  А у геометрических фигур есть поведение? Какое поведение у квадрата? :)
                                    0
                                    Зависит от задачи, долгий разговор на самом деле. Есть задачи графики, есть задачи разных расчетов.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                    +1
                                    Очень коротко.

                                    SRP — не мешаем в одном месте работу разных по возможностям и уровням доступа пользователей. У которых разные желания и разные причины для изменения.

                                    OCP — то что часто меняется или добавляется выносим наружу, внутрь это дело прокидываем как стратегию/шаблон. Например, повар и рецепты которые он выполняет лучше хранить и развивать отдельно, навигатор и алгоритм расчета пути из точки А в точку Б.

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

                                    ISP — не пытаемся засунуть все в один интерфейс, на то и позволена множественная реализация.

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

                                      0
                                      > LSP — производный класс при привидении его к базому должен вести себя как базовый класс.

                                      Неверно. Суть наследования обычно в изменении поведения. Но для соблюдения LSP важно, чтобы изменения поведения клиенты базового класса, которым подсунули производный, не заметили, чтобы это разное поведение не влияло на то, как клиент должен работать с классом, не влияло на его ожидание, чтобы поведение было инкапсулировано внутри классов и никак его изменение на клиентах базового класса не отражалось.
                                      +1

                                      Абстракция иерархической файловой системы для хранения файлов то подходит с большим трудом, не то что для объяснения архитектурных метапринципов.

                                        0

                                        Ваши аналогии подобны роялю на лыжах.


                                        Беда, в общем-то в самих принципах. Ну что вот это такое "A class should have only one reason to change". Любой класс меняют по одной единственной причине: он делает не то что от него хотят чтобы он делал. Всё, магическим образом все классы в мире стали solid?


                                        С другой стороны, любой класс будет меняться потому что:
                                        а) изменилось ТЗ на приложение и надо поменять логику класса
                                        б) в классе баг и он не соответствует ТЗ
                                        в) надо сделать класс более удобным для тестирования
                                        г) поменялось апи используемое классом


                                        Ну и где тут only one reason? В завистмости от того на каком уровне абстракции формулирую причины, их будет разное количество.

                                          0
                                          > а) изменилось ТЗ на приложение и надо поменять логику класса

                                          Какого? Вы смаппите пункт ТЗ на класс и получите one reason to change. Без этого как вы выбираете какой класс менять, монеткой?

                                          > б) в классе баг и он не соответствует ТЗ
                                          > в) надо сделать класс более удобным для тестирования

                                          Это вы в предыдущий раз работу недоделали. «Я комичу какую-то фигню, поэтому SRP не работает» — это слабый аргумент.

                                          > г) поменялось апи используемое классом

                                          А вот и пункт ТЗ с reason to change нашелся.

                                          > он делает не то что от него хотят чтобы он делал.

                                          Это «хотят» — что-то коллективное-бессознательное? Сегодня от него хотят, что бы класс складывал 2 числа, а завтра захотят, что бы отрисовывал кнопку на экране? Если вы не школьную лабу делаете и класс не называется MyFirstClass — у вас конкретный класс меняется по более конкретной причине (причинам), чем «хотят что-то другое». Вот и выберите одну.

                                          > В завистмости от того на каком уровне абстракции формулирую причины, их будет разное количество.

                                          *поперхнувшись*
                                          Нет, от того, что вы одну причину сформулируете разными словами — причин больше не станет.
                                            0

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

                                              0
                                              Ну вот и не формулируйте таких глупостей.

                                              К чему эта софистика?
                                                0

                                                К тому что принцип формулировку не дает и в этом его проблема.

                                                  0
                                                  Если всем дает, а вам не даёт — это не его проблема, а ваша.

                                                  За доведение до абсурда медалек не дают.
                                          +2
                                          А потом появляется Pink Floyd — The Wall, который и музыка и одновременно с этим видео, и простая стройная система ломается?
                                            0
                                            А это будет нарушением принципа инверсии зависимостей. Модуль высокого уровня «статья про SOLID» определяет детали реализации (аналогии с ФС), а не наоборот. Следовательно при разработке этой статьи в принципе не могло появиться «Pink Floyd — The Wall, который и музыка и одновременно с этим видео»
                                          • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Видимо в статье про устройство файловых систем, не? Тут их нет нет потому, что в контексте статьи они не нужны ;)
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Я уже высказывался о том, что формулировка «отвественность должна быть единственной» — мутно определена. Также мутно, как и «водитель должен убедиться в безопасности маневра».

                                              А вот формулировка «слишком много ответственности» уже более понятна. И хотя бы есть критерии, по которым количество ответственности можно мерять, хотя бы косвенно. Количество строчек кода — если их много, значит ответственности много — надо декомпозировать. Слишком много импортов — тот же вывод. Слишком много тестов нужно написать, чтобы протестировать — опять тот же вывод.
                                              Пороги между «еще норм» и «слишком много», конечно же, индивидуальны. Кому то 800 строчек в компоненте dropdown это норм, две страницы импортов это норм, что такое тесты знают только понаслышке.
                                              А кому то 20 строчек в теле функции это уже сигнал.
                                                0

                                                SRP — делим программы на минимальные единицы композиции, функции и типы. Сложное поведение достигается путём композиции этих функций.


                                                OCP — рассматриваем объект как чёрный ящик, не важна реализация, важен интерфейс. Расширяемость достигается путём композиции простых объектов.


                                                LSP — тип реализует Interface1 И Interface2 => тип реализует Interface1 ИЛИ Interface2


                                                ISP — SRP для интерфейсов.


                                                DIP — зависимость на интерфейсах вместо конкретных типов.


                                                Все эти вещи куда лучше работают с ФП идиомами, в частности с тайпклассами, трейтами и композицией вместо наследования.

                                                  0
                                                  не важна реализация, важен интерфейс

                                                  Является ли big-O сложность частью интерфейса?

                                                    0
                                                    Нет. Собственно часто это используется, когда реализации с разной сложностью и разными константами меняются даже в рантайме.
                                                  0
                                                  Спасибо за статью. Спасибо за новый взгляд на SOLID.
                                                  Читая про сантехников, сразу подумалось «это же LSP». Оказалось не про то.
                                                  LSP — это когда интерфейс, вроде, одинаков — фильмы. Вроде и общее есть — сантехники, а вот поведение другое. Ждут от фильмов рассказ о немецких сантехниках, а получают Афоню.
                                                    0
                                                    В черновике именно так и было кстати. ;)
                                                    0
                                                    С инверсией зависимости как-то совсем плохо получилось.
                                                      0
                                                      А пасхалку то так никто и не нашел… Печально.

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

                                                      Самое читаемое