SOLID: Принцип подстановки Барбары Лисков/ LSP (The Liskov Substitution Principle)

Поделиться
HTML-код
  • Опубликовано: 20 дек 2024

Комментарии • 442

  • @SergeyNemchinskiy
    @SergeyNemchinskiy  2 месяца назад

    💪Новый поток advanced тренинга Enterprise patterns стартует 2.12 - go.foxminded.ua/4eXh83k

  • @-ebaka
    @-ebaka 4 года назад +234

    Роберт что-то непонятное сказал, то ли дело Барбара

    • @6598335
      @6598335 4 года назад

      XDXDXD

    • @СергО-л6ф
      @СергО-л6ф 3 года назад +3

      Математически чёткое описание всегда более точное, чем словесное описание. Например,
      Правила Ассоциативноти и Дистрибутивности и звучат лучше...

  • @vadimsokhatsky2748
    @vadimsokhatsky2748 Год назад +7

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

  • @alokym86
    @alokym86 Год назад

    Я знаю чому для Вас формулювання Барбари Лісков складне і заплутане! Все тому, що для Вас і квадрат - це не прямокутник!)
    Дякую за відео. Приклад з квадратом підірвав мій мозок.

  • @BukasovMaxim
    @BukasovMaxim 4 года назад +43

    Я бы еще добавил, что при оверрайде неабстрактных методов, новый метод должен уметь принимать параметры в таком же диапазоне или шире (но ни в коем случае не уже), а при возвращении значений - в таком же диапазоне или уже (но ни в коем случае не шире).
    Например в каком-то классе есть метод, который умеет принимать число от 1 до 100 и возвращать от 1 до 10. Если его заоверрайдить так, что новый будет уметь принимать 0..200, а возвращать 3..5 - существующий клиентский код будет доволен. А вот если переписать так, что новый метод умеет принимать только 1..50 или вдруг возвращает 0..20 - то существующий клиентский код может быть неприятно удивлён.
    Также можно было бы еще сказать, что список выбрасываемых исключений можно сокращать, а исключения - конкретизировать, а вот наоборот - добавлять новые или расширять существующие - нельзя. Но с контролируемыми исключениями компилятор Джавы в этом плане сам подскажет, что можно, а что нет, а неконтролируемые... на то они и неконтролируемые :)

    • @vladzaiko5012
      @vladzaiko5012 3 года назад

      что то я не уверен что вы правильно написалали, если базовый класс возвращал от 1 до 10, а наследник будет возвращать от 3 до 5 а это ведь нарушение принципов о которых говорит Сергей - наследник не может возвращать меньше чем базовый класс. Базовый класс будет ожидать что при определенных параметрах вернется 10, а наследник вернет максимум 5 и все сломается.

    • @BukasovMaxim
      @BukasovMaxim 3 года назад +3

      @@vladzaiko5012 Представьте, что есть клиентский код, который умеет разговаривать на английском, французском и немецком. Есть базовый компонент, который иногда выдаёт клиенту что-то на английском, а иногда - на немецком. Клиент понимает эти сообщения от компонента.
      Если заменим этот компонент на новый, который будет выдавать сообщения только на английском (т.е. сузим диапазон возвращаемых значений), то клиентский код всё еще будет понимать эти сообщения. А вот если новый компонент вдруг заговорит на японском (т.е. расширим диапазон возвращаемых значений), то клиентский код сломается - потому что клиентский код не знает, что делать с сообщениями на японском.

    • @vladzaiko5012
      @vladzaiko5012 3 года назад

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

    • @BukasovMaxim
      @BukasovMaxim 3 года назад +4

      @@vladzaiko5012 Речь не о новых методах. Речь о старых методах, которые в новых компонентах вдруг начали выдавать на выход то, что старые компоненты не выдавали.
      Пример: батарейка (как компонент). Допустим у нас есть электрическая схема (клиент), которой нужно питание +5В +/-10% (т.е. от 4.5 до 5.5 - будет работать). Если у нас есть батарейка, которая выдаёт +5В +/- 5% (т.е. от 4.75 до 5.25), то все ОК, схема будет работать и не сгорит (потому что при замене компонентов выходные диапазоны можно сужать без проблем). Но если мы вставим батарейку +5В +/-20%, то она может дать или слишком мало (и схема не заработает) или слишком много (и схема сгорит). Поэтому, при подмене компонентов, расширять возвращаемый диапазон - нельзя (можно только сужать).
      Входной диапазон - наоборот: сужать нельзя (чтобы клиент не послал в компонент что-то такое, что он не поймёт), а расширять - можно (чтобы компонент был не глупее, чем его предок).

    • @catmother8368
      @catmother8368 3 года назад +1

      @@BukasovMaxim спасибо за отличное объяснение!
      А что, если у нас есть класс User и класс Admin, который является наследником от User? Допустим, в User есть метод, вохвращающий все права пользователей. User будет возвращать стандартные разрешения типа редактировать аккаунт и проч. Но у Admin нужно будет расширять этот список. Верно ли я понимаю, что в данном случае произойдёт нарушение принципа LSP?
      Как этого избежать? Не лучше ли выделить отдельный класс с правами, чтобы не нарушать SRP?

  • @TheHosuwisp
    @TheHosuwisp 4 года назад +136

    Математический вариант Барбары все разложил по полочкам, а то так много воды и ничего не понятно.

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад +10

      ахаха. Ну, рад за вас :)

    • @ffconsole3467
      @ffconsole3467 4 года назад +46

      Попався, математiк

    • @fakerliar6599
      @fakerliar6599 4 года назад +12

      Тоже объяснение Барбары показалось более простым и понятным, хотя не математик. Может это потому что я женщина?:D

    • @murationametisation4347
      @murationametisation4347 3 года назад +2

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

    • @lindx2533
      @lindx2533 3 года назад +8

      странно что ты за этим вариантом полез на ютьюб к немчинскому

  • @АлексейПоляков-ш1э
    @АлексейПоляков-ш1э 4 года назад +7

    TDD - ДА! Про return null - ДА!
    Спасибо за видео!

  • @romansharpe1131
    @romansharpe1131 4 года назад +5

    Есть 2 класса Pistol и Rifle. Оба должны стрелять и перезаряжаться, значит выносим эту логику в класс Weapon и наследуемся от него. Появлятся новое оружие Knife, которое тоже атакует (стреляет) и также наследуется от Weapon. Теперь нож и стреляет, и перезаряжается. Реализуем LSP: между классами Weapon -> Pistol/Rifle делаем прослойку Gun и выносим туда логику перезарядки, а в Weapon оставляем только атаку/стрельбу. В итоге получаем: Weapon -> Gun -> Pistol/Rifle и Weapon -> Knife

    • @JackFastGame
      @JackFastGame 4 года назад

      Что делать, если к такому пришли только после написания кода? Например, нужно было сделать пистолет и ружьё, написали классы: Weapon -> Pistol, Weapon -> Rifle, а потом заказчик добавил в ТЗ нож. Что теперь делать?

    • @romansharpe1131
      @romansharpe1131 4 года назад

      ​@@JackFastGame по нормальному неплохо было бы продумать все это заранее. Здесь же я привел свой пример, как выглядит LSP на практике.

    • @0imax
      @0imax 4 года назад

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

  • @БендерЗадунайский-щ9ы
    @БендерЗадунайский-щ9ы 4 года назад +127

    надо!
    про тест фёст надо
    про нуль из методов
    и ДОСКУ МАРКЕРНУЮ! НАДО!

    • @Asmaers
      @Asmaers 4 года назад

      и про красную футболку!

    • @VitaliyZlobin
      @VitaliyZlobin 4 года назад +2

      доску особенно, уши режет

    • @kspshnik
      @kspshnik 3 года назад

      дададад! и про TDD и про null надонадонадо!

    • @КазимБерловский
      @КазимБерловский 3 года назад

      Доска как раз под бумагой. Приглядись)

  • @stakhovskiy
    @stakhovskiy 4 года назад +40

    Август месяц на дворе, а его все еще зовут Сергей Немчинский. Хосспаде!
    Спасибо за видео 😄Лайк.

    • @z1zzz
      @z1zzz 4 года назад

      А в чём прикол?

    • @igorost795
      @igorost795 3 года назад

      Попрошу! Сергей "тутромбик" Немчинский!

  • @Snake19S
    @Snake19S 4 года назад +2

    Сергей, вы такой живчик в этом видео. Отлично получилось!

  • @whoiam6395
    @whoiam6395 3 года назад +1

    Спасибо огромное!!! В 3х словах рассказал то что я не смог понять ни в одном видео до этого.

    • @whoiam6395
      @whoiam6395 3 года назад

      А я их пересмотрел штук 10

    • @Virus-td9nc
      @Virus-td9nc 24 дня назад

      а я снова нихуя не понял

  • @masterbedroom594
    @masterbedroom594 3 года назад +1

    Сергей, выражаю вам искреннюю благодарность

  • @LeoMrakobes
    @LeoMrakobes 4 года назад +34

    @Sergey Nemchinskiy уже раньше обещал про "почему нельзя возвращать null"

  • @Madeniath
    @Madeniath 4 года назад +56

    Про TDD интересно было бы посмотреть.

  • @YuretsUA
    @YuretsUA 3 года назад

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

  • @xdef42
    @xdef42 4 года назад +35

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

    • @nanvlad
      @nanvlad 4 года назад +4

      Ты сраный рукожоп по версии Немчинского)) Я тоже агрегирую и композирую с интерфейсами + ещё дженерики

    • @alexeydeyev4970
      @alexeydeyev4970 4 года назад +4

      Имплементирование можно с натяжкой отнести к наследованию.

    • @grommaks
      @grommaks 4 года назад +1

      аналогично

    • @makaroningable
      @makaroningable 4 года назад

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

    • @woodzimierz9621
      @woodzimierz9621 4 года назад

      @@makaroningable есть товарищи, которые отрицают любую форму наследования

  • @UnrealSPh
    @UnrealSPh 4 года назад +7

    Возможно, описание самой Барбары станет более понятным, если понимать когда она его предложила и как выглядели ассоциации типов в ЯП в то время, так как *спойлер* наследование не единственный способ ассоциации классов, а скорее частный случай.
    Насчёт использования наследования вы скорее всего заблуждаетесь, но причина заблуждения это легаси информация, которую из года в год перевыпускают в требованиях к коду.
    Если вы боретесь с копипастингом кода в проекте, то для этого больше подходит композиция кода.
    А если и методология разработки у вас не waterfall, то n-ступенчатая иерархия наследования превратит процесс изменения кода в ад (не забываем, что классы содержат ещё и стейты).
    Наследование сильный инструмент, но его главное назначение в продуктах, которые должны иметь жёсткую структуру иерархии, а так же максимально долгое время жизни самой иерархии. Напрмер фреймворки. Там наследование чувствует себя хорошо.

  • @НиколайЮрченко-о2й
    @НиколайЮрченко-о2й 4 года назад +7

    Классное видео, спасибо за детальное и простое объяснение, хотелось бы еще послушать про паттерн pipeline.

  • @ntvisigoth
    @ntvisigoth 4 года назад +2

    @Sergey Nemchinskiy: Мужик, здоровья тебе! Отличное пояснение! ;)

  • @NikolayMishin
    @NikolayMishin 3 года назад +2

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

    • @yuriytheone
      @yuriytheone Год назад +1

      Ты сыпишься, потому что Solid ложны. Каждый принцип ложен! Давай про Srp. Когда ты создаёшь дочерний класс, ты его создаёшь именно таким, какой нужен именно в твоём коде. И именно с таким интерфейсом, который нужен твоему софту. Далее Ocp - ты не только можешь менять код метода, но и должен если это нужно для оптимизации. Да, представь себе Ocp тоже ложен. Далее Lsp - зачем тебе порождать наследников и закидывать в какой либо метод работающий с классом родителем. Ну, вот ты и не можешь объяснить... У немчи6ского яйц нет признать, Solid - фуфло.

  • @vladyarets3596
    @vladyarets3596 2 года назад

    1:16 ну нафиг)) Как это прочитать получилось?!)
    1:50 а не. вот тут нафиг, дяде Бобу спасибо огромнейшее!

  • @bumer7721
    @bumer7721 4 года назад +14

    Об ACID интересно послушать.
    А за SOLID благодарю)

    • @maxlich9139
      @maxlich9139 4 года назад

      было, Владимир Кузнецов рассказывал.

    • @bumer7721
      @bumer7721 4 года назад

      @@maxlich9139 Дякую! Шукатиму.

  • @РостикНазаренко-п8с

    Дуже вам дякую за пояснення. Все просто і зрозуміло )

  • @protchenko.oleksandr
    @protchenko.oleksandr 2 года назад

    Або це просто вже 100те відео про Лісков і я нарешті зрозумів, або це найкраще з тих відео що я бачив і я зрозумів =))

  • @samuro2ua
    @samuro2ua 4 года назад +3

    Сергей, тема разработки через тестирование очень интересна! Как и SOLID. Спасибо.

  • @AnnaIsHere
    @AnnaIsHere 4 года назад +45

    Хотелось бы больший упор на "как надо" вместо "как не надо"

    • @Gusto20000
      @Gusto20000 3 года назад +6

      Просто берете как не надо и делаете не так и будет как надо

    • @fractalzombie
      @fractalzombie 3 года назад

      С опытом придёт.

    • @АнтонДудкевич
      @АнтонДудкевич 3 года назад

      @@Gusto20000 и если это приходится объяснять, то, наверное, лучше никак не надо ;)

    • @screamer8932
      @screamer8932 2 года назад

      @@Gusto20000 Часто бывает 101 способ как не надо и только парочку как надо.
      Кск би так убегая от плохого 98 способа не попасть в 86))

    • @НиколайКоваленко-г2к
      @НиколайКоваленко-г2к 7 месяцев назад

      что касаемо lsp то что бы его не нарушать нужно просто придерживаться ооп. Надо постараться что бы его нарушить.

  • @alexanderlex-s933
    @alexanderlex-s933 4 года назад

    Изучаю Java с нуля. Даром прошли 5 лет в 1С, ООП - это взрыв мозга ))))
    11:20 - я не могу использовать ООП в 1С, там только процедурный код - и да, я чувствую себя рукожопом, но за хорошую зарплату.

  • @kosatchev
    @kosatchev 4 года назад +5

    Принцип на примере семьи Хилтон:
    class Hilton {
    Money manageHotel(Hotel h) {
    Money m = makeCustomersHappy(c);
    return m;
    }
    }
    class Paris extends Hilton {
    @Override
    Money manageHotel(Hotel h){
    Money m = sellHotel(h);
    return m;
    }
    }

    • @torrvic1156
      @torrvic1156 2 года назад +1

      Отличная шутка про ведение бизнеса 😂

  • @kotanhp3115
    @kotanhp3115 2 года назад

    Хорошее видео, жду такое же про Оксимирона

  • @sergekim
    @sergekim 4 года назад +2

    Спасибо большое, Сергей!)

  • @andrey7829
    @andrey7829 10 месяцев назад

    Очень понятно объясняете - супер, спасибо

  • @ChatMayevskogo
    @ChatMayevskogo 3 года назад

    Я себе тоже такую футболочку взял, тока черную, чтоб прям в тему последних времен было)
    Это когда джаву в js тоже будут компилить, наверное)

  • @kyryloshamraiev6289
    @kyryloshamraiev6289 2 года назад +2

    Очень наглядное объяснение. Проще говоря, нужно наследовать большее от меньшего, а не наоборот. Все станет на свои места, если считать, что квадрат - это частный случай прямоугольника, а не его наследник. Именно такой подход применят учёные. Потому обьяснение Барбары и не понятно программистам с ООП профдеформацией. :)

    • @RomanL321
      @RomanL321 Год назад

      ООП профдеформация это интересно))) всё под неё пытаются подтянуть, иногда усложняя код до нельзя...

  • @Дмитрий-х2й5р
    @Дмитрий-х2й5р 4 года назад +6

    Спасибо.
    Только с квадратом как быть? Разве при наследовании у него не разумно переопределить метод подсчета площади?

    • @danilakapitanov7044
      @danilakapitanov7044 3 года назад +2

      Проблема не столько с определением площади, сколько с установкой сторон в клиентском коде. Правильным решением является создание абстрактного класса Figure с методом вычисления площади, от которого наследуется отдельно Rect и отдельно Square и реализуют метод вычисления площади. При этом для клиентского кода логика работы по установке сторон Rect и Square становится разная. Т.е. прямоугольник и квадрат по сути это разные сущности, как ,например, прямоугольник и круг.

    • @alexanderbelov6892
      @alexanderbelov6892 3 года назад +2

      @@danilakapitanov7044 Раз уж речь про клиентский код работы с фигурами, то характеристики фигур должны прописываться при создании, и отсутствовать методы их изменения, которые будут разными для разных фигур. К примеру для фигур могут быть методы get_square() для площади, и scale() для изменения размера. Но никак не изменения характеристик описывающих фигуру по отдельности. Для изменения фигуры с другими сторонами нужно создать новую фигуру, а предыдущую удалить.

  • @eligolin9947
    @eligolin9947 2 года назад +4

    По моей оценке вся суть проблемы была не раскрыта.
    Наследование как "анти паттерн", основана на сложности соблюдение LSP принципа.
    Пройдёмся по примерам в этом ролике:
    1. Здесь Сергей рассказывает как плохо нарушать LSP принцип и к чему это может привести. Но дело не в том что его нельзя явно нарушать (это и так понятно), а в том что его очень сложно соблюдать.
    2. Сергей здесь спасибо вам за альтернативное определение LSP (оно добавляет глубины понимания!) хотя по моей оценке примеры всё равно слабы.
    3. По моей оценке этот пример вообще не об этом. Здесь проблема не в соблюдении или несоблюдении LSP, а в отсутствии инкапсуляции. Это был хороший пример того что лучше писать иммутабильные классы а setters/getters могут раскрыть зачастую внутреннюю структуру класса.
    С точки зрения наследования - это как раз один из немногих примеров где наследование является корректным. Наследования квадрата от прямоугольника является математически правильным и хорошо обоснованным с точки зрения качеств квадрата по отношению к качествам прямоугольника.Если бы состояние этих классов задавалось только при помощи конструктора, то никакой проблемы не было бы.

  • @ВадимКорженко-э8б
    @ВадимКорженко-э8б 4 года назад +1

    TDD интересно. С удовольствием послушаю.

  • @yuriyfedoryshyn5206
    @yuriyfedoryshyn5206 4 года назад +4

    looking forward to a TDD video))

  • @samirsalimkhanov3554
    @samirsalimkhanov3554 4 года назад +13

    Надо убрать бумагу между маркером и доской, потом писать. Я так думаю))) спаисбо за видос

    • @YuretsUA
      @YuretsUA 3 года назад +2

      Это нарушает 1-й принцип SLP, каждому объекту своя зона ответственности, доска для того, чтобы бумагу удерживать, и бумага не рвалась под фломастером. А бумага уже для отображения текстовой информации, так что тут все грамотно...

  • @sick_bear
    @sick_bear 2 года назад

    Наконец-то я понял этот принцип)

  • @alexeydeyev4970
    @alexeydeyev4970 4 года назад +1

    Очень интересно про Test First!!! В топ!!

  • @nataliia9346
    @nataliia9346 4 года назад +1

    Хотелось бы с таким классным объяснением и примерами послушать о Паттернах Программирования :)

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад

      эм.... Ну вот же оно: foxminded.com.ua/grasp-gof-design-patterns-advanced-on-line-course/

  • @eligolin9947
    @eligolin9947 2 года назад +1

    Я постараюсь привести пример проблематики соблюдения LSP принципа.
    Допустим у вас есть класс Human. У этого класса есть такие поля как; пол, возраст, рост, вес и тд..
    Теперь представим что у нас есть класс Student. В этом классе добавляется название колледжа, номер курса, и специальность.
    Согласитесь что наследование студента от человека выглядит довольно безобидным и естественным. Ещё представим что в текущей версии кода есть метода которая вычисляет налог "социального страхования" для любого человека, и основана она только на возрасте человека.
    Понятно что в текущей версии кода, LSP принцип соблюдается для этой методы.
    А теперь представим что в будущем закон изменился и теперь студенты освобождены от оплаты налогов. С этого момента LSP принцип нарушен.
    проблема здесь на самом деле глубокая и заключается она в том что если B наследует от A, то требуется доказать математически что B является A.
    Дело в том что наследование Student от Human основывается на нашем естественном восприятии но в то же время плохо определено в математическом смысле.
    Вся сложность LSP является в том что наследование должно быть корректно не только в текущей версии кода, но и во всех (неизвестно на сегодня) будущих его версиях.
    Когда домейн классов находится в математической плоскости нам легче создать корректное наследование, но к сожалению это лишь малая часть всех проблем которыми мы занимаемся.
    Отсюда вытекает общий антипаттерн наследования.

  • @РинатРаот
    @РинатРаот 4 года назад

    Ах**нно)
    наконец то понял в чём фишка этого принципа

  • @Zlobusz
    @Zlobusz 4 года назад +1

    Программирую на php в Symfony. На самом деле крайне редко приходится использовать наследование. Чаще фреймворк предоставляют интерфейсы, чем абстрактные классы. А наследоваться от какой-то конкретной реализации - тоже не совсем приятно. Поэтому случаев наследования можно по пальцам одной руки пересчить. Но принцип постановки Лисковой зашёл. Спасибо! Теперь бы про инверсию зависимости послушать)

    • @alexeyb5830
      @alexeyb5830 2 года назад

      потому, что ооп это не про наследование. В чистой архитектуре про это написано. Ооп это про полиморфизм. Старину Боба видимо не всего Немчинский читал, либо читал и не понял.

  • @vanilla1006
    @vanilla1006 4 года назад +2

    TDD, буду рад выслушать

  • @woodzimierz9621
    @woodzimierz9621 4 года назад +2

    Про TDD очень интересно.

  • @wandos777
    @wandos777 2 года назад

    Супер, Сергей) Спасибо большое за видео, однозначно лайк!)

  • @СергейКарпиченко-с6б
    @СергейКарпиченко-с6б 4 года назад +1

    Спасибо большое за видео!

  • @dmitriykozyrev8835
    @dmitriykozyrev8835 4 года назад +1

    Сразу лайк, спасибо тебе большое!

  • @ReAgent003
    @ReAgent003 3 года назад

    11:02 так всё-таки какая ситуация лучше: перезаписать сторону или выбросить исключение? Как в данной ситуации действовать?

    • @andriiv7033
      @andriiv7033 3 года назад

      Никак. Только нарушать принцип OCP - управляющий код должен знать про два класса. Так написано у Мартина.

  • @victortamanov
    @victortamanov 4 года назад +2

    Про TDD интересно будет посмотреть.

  • @VoroninPavel
    @VoroninPavel 4 года назад

    Хе-хе, люблю этот пример с квадратом и прямоугольником. Только он еще веселее. Если ввести иммутабельность с втиле старого доброго ФП и после вызова любого "мутирующего" метода возвращать экземпляро нового объекта, то квадрат-таки можно сделать проямоугольником, не нарушая принципы ООП ;-)

  • @alexeiceglei9841
    @alexeiceglei9841 4 года назад +1

    Юнит тесты интересны очень, особенно какие то хитрости, типо того же test first

  • @halgerdka9287
    @halgerdka9287 4 года назад

    про пример с квадратом. что мешает переопределить только метод getSquare() return a*a ? и вообще не трогать b

  • @user-hv8rh8nk9d
    @user-hv8rh8nk9d 4 года назад

    Отлично. Про тесты видео нужно

  • @Ardolynk
    @Ardolynk Год назад +1

    Разве mock не должен лежать в модуле test, недосягаемом для основного кода?

  • @dmitrinikolaev6986
    @dmitrinikolaev6986 4 года назад +2

    Про TDD было бы очень интересно послушать!

  • @chocolazerboom7389
    @chocolazerboom7389 4 года назад +46

    Чёт прямо больно, что такие листы бумаги тратятся на то, что можно нарисовать на доске и стереть

    • @ESTechnonet
      @ESTechnonet Год назад +3

      А еще больно - звук маркера скрипучий.

  • @wcode404
    @wcode404 4 года назад +1

    Квадрат выглядит как частный случай прямоугольника, так же как квадрат частный случай ромба или прямоугольник частный случай параллелограмма. На самом деле это всё фигуры и на одна из них не является дочерней по отношению к другой. Я писал об этом здесь:
    github.com/peter-gribanov/clean-code-php

  • @СанжарСовет-ъ5ц
    @СанжарСовет-ъ5ц 3 года назад

    ну круто я понял как делать не надо ,а как делать надо сам разберусь спасибо лайк щищ

  • @demidovmaxim1008
    @demidovmaxim1008 4 года назад

    Большое спасибо за выпуск!!!

  • @homo-ergaster
    @homo-ergaster 4 года назад

    Вот что интересно, базовые классы Java нарушают LSP совершенно без стеснения. Попробуйте, например, сделать так:
    Map map = Map.of("a", 10, "b", 20);
    map.put("c", 30);
    и охренейте от того, что вам прилетит Exception в Runtime. При том что вся эта лабуда прекрасно скомпилируется. Когда-то налип с этим делом по незнанию, пришлось в довольно большом пакете искать где я делал Map.of и переделывать на new HashMap.

  • @sfoxer
    @sfoxer 3 года назад

    Лучший препод =) Что бы мы без вас делали)

  • @haqon2k
    @haqon2k 4 года назад +1

    В целом, неплохо рассказано, конечно. Я бы добавил в формат реальный кейс с кодом, примеров нарушений принципа в системных библиотеках (в той же Java нарушения лисков достаточно). Ещё не затронута тема ковариантности и контравариантности.
    Иии что значит нельзя возвращать null из методов? Это абсолютно нормально и даже нужно. Естественно, с документацией. Или когда ваш язык поддерживает нормальные nullable типы - null как отдельный тип данных.

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад

      нельзя возвращать null, это очевидно

    • @haqon2k
      @haqon2k 4 года назад

      ​@@SergeyNemchinskiy Если вы имеете ввиду принцип Лисков, то и тут не соглашусь, всё зависит от контракта, предоставляемым базовым типом, если там подразумевается null, то можно.
      А если вы имеете ввиду вообще, в целом, то и тут неясна ваша идея. В некоторых случаях, действительно, есть лучше способ показать отсутствие данных. Например, в .NET - try pattern, когда возвращаемое значение - bool, показывающее результат операции, а данные возвращаются через ссылку одним из аргументов. Также в .net ввели nullable reference types, с помощью которого явно видно, где тип может быть null, а где нет.

    • @0imax
      @0imax 4 года назад

      Я бы предпочёл получить из метода пустой список вместо null, прописанного где-нибудь в дебрях в документации.

    • @haqon2k
      @haqon2k 4 года назад

      0imax Касаемо чего то перечисляемого, да, тут лучшим способом будет вернуть что то пустое, но существующее. И то стоит понимать, что могут быть моменты, когда нужно уметь различать отсутсвие самого списка и отсутствие в списке вообще кого то. Но обычные объекты, возвращаемые аля GetUserById, GetActualPlayerConnection и прочее - здесь лучшим способом заявить об отсутствии чего то только null, никаких throw

    • @0imax
      @0imax 4 года назад

      @@haqon2k а по мне так лучше кинуть нормальный exception и обработать его, чем возвращать null:
      Во-первых, правильно названный exception сразу даёт информацию о причине "поломки". А во-вторых, таким образом можно обработать ситуацию, когда причин отсутствия объекта может быть несколько, как с тем же connection.

  • @newprim1
    @newprim1 3 года назад +1

    Это самое лучшее объяснение принципа Лисков, что я встречал! Подписка однозначно

  • @kirilleremeev9707
    @kirilleremeev9707 4 года назад +2

    Ну так, а какое все таки решение в вопросе прямоугольник-квадрат? Все его приводят в пример, а решения ни кто не приводит! Получается два выбора: либо отказаться от наследования и следовать LSP... Но тогда будет нарушен другой же принцип - не дублируй код. Т.е. если у вас в базовом классе (например в прямоугольнике) двести методов и один из них не LSP, то что тогда?

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад

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

    • @vanweyden
      @vanweyden 4 года назад

      Вообще квадрат не нужен)

    • @kirilleremeev9707
      @kirilleremeev9707 4 года назад +1

      @@SergeyNemchinskiy Квадрат это частный случай от прямоугольника (по крайней мере я лучше выкину принципы solid чем так поступлю). И функции setWidth и setHight нужно будет переписывать для прямоугольника, что опять же нарушает - дочерний класс переопределяет функциональность базового. Теперь новый side эффект - я ожидаю что стороны равны для квадарта (базового класса), а для прямоугольника - не равны. Проблема с функцией площади теперь выливается аналогичную проблему с функцией с периметром...

  • @jossefal1957
    @jossefal1957 4 года назад

    Хорошее объяснение, как обычно лайк

  • @antonkuranov
    @antonkuranov 4 года назад

    Принцип подстановки безусловно правильный, но реально работает лишь в отдельных ограниченных случаях. Начиная с определенного момента ваши абстракции начинают течь, и решить эту проблему становится гораздо сложнее, нежели если бы LSP вообще не использовался бы с самого начала: приходится расширять интерфейс и патчить все зависимости. Пример протекающей абстракции -- это любой интерфейс драйвера, который возвращает "driver capabilities".

  • @TheDaslord
    @TheDaslord 4 года назад

    Сергей, спасибо за видео!
    Пожалуйста, снимите видео про возврат null, очень интересно, сам постоянно так делаю.

  • @ПашаХЗ-м8й
    @ПашаХЗ-м8й 4 года назад +1

    Давно ждал!

  • @АнтонДудкевич
    @АнтонДудкевич 3 года назад +2

    Я вот чет не понял, что плохого в примере с квадратом ) Ну, перезаписал он сторону с 10 на 5, ну, посчитал он площадь 25. Так она такая и есть в его случае )
    Одинаковый интерфейс родителя и наследника никак не означает одинакового результата. Если он одинаковый всегда, - нахрена наследовали-то? )

  • @makskors5002
    @makskors5002 3 года назад

    TDD очень интересно!

  • @Telemahk
    @Telemahk 3 года назад

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

  • @undefined-n5v
    @undefined-n5v 4 года назад +2

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

    • @screamer8932
      @screamer8932 2 года назад

      Насколько я понял из поиска по гуглу. Тут весь пример завязан на Mock об'екте который по определению является пустышкой с методами заглушками. И если оставлять родительськую реализацию то нарушается смысл.
      Это как я понял. Если кто поправит буду рад

  • @ArtemGaleev-w4t
    @ArtemGaleev-w4t 4 года назад +2

    Раз уж вы сказали про наследование и попросили предложить что после СОЛИД рассказать, то предлагаю достаточно холиварную тему: наследование vs композиция. Заранее прошу прощения если вы эту тему уже поднимали

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад +1

      расскажу. Регулярно поднимаю эту тему на тренингах, надо и в отдельном видео рассказать

  • @artemboiarshinov
    @artemboiarshinov 4 года назад

    Немного про моки, раз уж Сергей про них заговорил.
    Чтобы замокать только строго определенные методы стороннего компонента, вызывающегося в тестируемом коде, можно использовать библиотеки типа Mockito или PowerMock, вместо того чтобы самостоятельно писать mock-реализацию этого компонента. Это стандартное решение, все так делают.
    Есть еще идея Егора Бугаенко: для каждого компонента в его интерфейсе писать fake-реализацию этого интерфейса, которая будет использоваться в тестах всех зависимых компонентов. Это похоже на то, о чем говорит Сергей Немчинский, но ответственность за написания мока берет на себя не разработчик клиентского кода, а разработчик компонента. Соответственно разработчик компонента обязан реализовать все методы этой fake-реализации. Немного утопическая идея, но она решает проблему хрупкости, присущей тестам, написанным с помощью Mockito.

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад

      то, что Бугаенко не знает слова Stub и вместо этого придумывает свои термины - не удивлен

    • @artemboiarshinov
      @artemboiarshinov 4 года назад

      @@SergeyNemchinskiy Его основная идея заключается в том, что эти заглушки должны быть написаны в java-файле предоставляемого интерфейса. Мол это позволит пользователям вашей библиотеки не писать заглушки самостоятельно, что уменьшит дублирование кода на планете))
      А так мне и самому Егор Бугаенко не нравится, слишком уж он хайпожористый

  • @igor_knv
    @igor_knv 4 года назад +1

    Понравился пример про квадрат и прямоугольник. Кто-кого расширяет.

  • @topalidinka
    @topalidinka 3 года назад +4

    Я вас обожаю. Вы один из самых интересных и умных людей, которых я знаю) спасибо за знания и ваш юмор ☺️

  • @eb6006
    @eb6006 4 года назад +2

    Про TDD интересно, запишите пож-ста

  • @dmitriimolchanov4971
    @dmitriimolchanov4971 3 года назад +3

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

  • @torrvic1156
    @torrvic1156 2 года назад +3

    Я просто офигеваю от того, какой Сергей потрясающий интеллектуал. Мне страшно смотреть его видео. Страшно от того, что голова может лопнуть от обилия деталей, но потом трясущейся рукой тянусь и нажимаю кнопку начала просмотра, а потом инфа кое-как укладывается под отличную и ровную мелодию под методичный голос Сергея…
    P.S. короче, как тупой я понял принцип LSP главным образом в том, что не должен наследуемый класс делать больше, чем его основной класс.

  • @BelarusianInUk
    @BelarusianInUk Год назад

    Мое дБ должна быть сделана с помощью мок апи, например mockito, jmock, easymock, etc Не надо самим эти сроки писать, ТК их количество будет рочти постоянно. Эти сроки тоже могут баги иметь. И тоже надо тестить)))

  • @gomer3894
    @gomer3894 4 года назад +1

    спасибо за видео!

  • @Hellsome_Rider
    @Hellsome_Rider 4 года назад +1

    Это вообще из другой оперы, но я бы послушал про DDD domain driven design, что такое bounded context и как его правильно определять

  • @AAAnatoly
    @AAAnatoly 4 года назад +29

    Юнит-тесты очень интересны. По сути, я самоучка, и до юнит-тестов не дошёл, а начинать страшно

    • @YoungT18
      @YoungT18 4 года назад

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

    • @kisurov
      @kisurov 4 года назад +4

      Ничего там нет страшного

    • @pgriggs1299
      @pgriggs1299 4 года назад +3

      @@YoungT18 Mockito и JUnit обязательно нужно знать джуну, также будет в разы круче, если код, например, в пет-проектах будет протестирован, потому что не протестированный код = нерабочий код

    • @r2d2925
      @r2d2925 4 года назад +1

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

    • @John_602nd
      @John_602nd 4 года назад +1

      Ничего страшного совершенно, с ними другая проблема, что их ленятся писать. В Java все юнит тесты: @Before, @Test, assert(Equals/That/...)
      Если прочитать как это работает, то... Это в целом всё. А прочитать можно в первой же статье в гугле по запросу "Java UnitTest"

  • @zhanibekkaimuldenov8272
    @zhanibekkaimuldenov8272 4 года назад

    Давайте про TTD и я не понял пример с квадратом, в итоге нельзя квадрату наследоваться от прямоугольника получается или что делать ?)

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад +1

      в итоге надо наследовать наоборот

    • @Selt3Z
      @Selt3Z 3 года назад +2

      @@SergeyNemchinskiy стоило сказать об этом в самом видео

  • @mykola-rohoza
    @mykola-rohoza 4 года назад

    На счет возврата налл из метода - очень интересно ваше мнение
    Чем вам неугодил Актив Рекорд и какие вы видете альтернативы (все так работать с объектом который можно собрать из кусков и сохранить достатолчно удобно)

    • @FrolOFF100
      @FrolOFF100 4 года назад

      Нарушает srp со всеми описанными в предыдущих видео последствиями

    • @mykola-rohoza
      @mykola-rohoza 4 года назад

      @@FrolOFF100 Да понятно, что нарушение, но оно всеобщее (для пыховых фрэймов во всяком случае) потому и интересно какую альтернативу Сергей предложит

  • @ЕвгенийЛапин-о4т
    @ЕвгенийЛапин-о4т 4 года назад

    Ну вот отнаследовался я от Rect, сделал Square. На свойство b забил, переопределил метод getSquare, где работаю только с a. Ещё переопределил геттер/сеттер b, чтобы он работал с a вместо b. Работать будет, и даже тесты не рухнут скорее всего. Но плохо то, что есть лишнее свойство b, плохо то, что неочевидно работают геттеры/сеттеры b. Принцип именно в том, что не должно быть лишних свойств и неочевидного поведения?

    • @0imax
      @0imax 4 года назад

      Принцип в том, что если подсунуть Square (в вашем случае) вместо Rect, то поведение не должно измениться. А оно изменится, причём с нарушением здравого смысла. Это и есть нарушение принципа LSP.

  • @JackFastGame
    @JackFastGame 4 года назад +1

    А что является клиентским кодом в Unity?

  • @DeMoniaqGRANI
    @DeMoniaqGRANI 4 года назад +1

    Можно делать ООП и без наследования, через реализацию интерфейсов

    • @DeMoniaqGRANI
      @DeMoniaqGRANI 4 года назад

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

    • @0imax
      @0imax 4 года назад +1

      @@DeMoniaqGRANI А можно и без натяжки, поскольку это по сути наследование от full abstract класса))

  • @FrolovDaniil
    @FrolovDaniil 4 года назад +9

    Можно ещё про DRY, KISS, YAGNI

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад +3

      уже в планах

    • @FrolovDaniil
      @FrolovDaniil 4 года назад +2

      @@SergeyNemchinskiy Смотрю Вас из интереса уже наверное больше года, когда ещё начинал знакомиться с Java, сейчас вообще Python разработчик. Так вот качество видосов стало на порядок выше, видно, что развиваетесь. Так держать! Пожалуйста, подумайте по поводу маркерной доски, а то эти шуршания маркера по бумаге некоторых приводят в ужас (меня например).

  • @ivanoviv1844
    @ivanoviv1844 10 месяцев назад

    Ну как тут не подписаться ну никак и лайк поствлю и все что угодно

  • @denisbielishev
    @denisbielishev 4 года назад

    Будет интересно услышать про GRASP и GoF

  • @Lammax2012
    @Lammax2012 4 года назад

    TDD - очень интересно!!!

  • @svyatshu713
    @svyatshu713 4 года назад

    так выход какой (чтобы принцип не нарушать) при наследовании - нужно переопределять все методы парента?

    • @0imax
      @0imax 4 года назад

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

  • @Brick87Game87
    @Brick87Game87 4 года назад +1

    супер, спасибо!

  • @grommaks
    @grommaks 4 года назад +7

    Раскрыта только одна сторона медали...
    Еще нужно рассмотреть вариант, что у нас есть абстрактный класс / интерфейс и фиг пойми сколько наследников
    Как правильно доработать существующий метод (уже опубликованный), чтобы ни один из наследников не нарушил L из SOLID
    Это очень холиварное заявление по поводу наследования...Ваши предки накидали г..на в больших системах, что невозможно работать :)
    Magento - PHP решение для сложных магазинов с соблюдением всех возможных подходов из Java об OOP
    И Magento 2 решили отказаться от наследования, получив при этом очень расширяемую систему и ни разу не потеряв в OOP шности...
    Т.е. контроллеры (и прочие инфрастуктуры) не наследуют базовый класс, а реализуют интерфейс.
    Magento 1 с возможностью строить цепочки по 10-20 наследников это больно сильно
    Допустим PHP это не ЯП, а магазины это слишком легко для Java разработчика...
    Но в Красной книге по DDD есть отдельная глава, где говорится, что архитектура может быть на базе наследования и моделей с состояниями или на базе сервисов без состояния и анемичными моделями...и то и то OOP подход основанный на различном фундаменте...со своими плюсами и минусами
    За видео спасибо :)

    • @sergijg
      @sergijg 4 года назад +1

      Вот интересно, а общая логика/алгоритмы где тогда лежат, в ютилити методах?

    • @grommaks
      @grommaks 4 года назад +1

      В vendor/magneto/framework
      В интерфейсах, например
      DirectoryListInterface
      Filesystem/DriverInterface
      и так далее
      Сама Magento после установки содержит 45 000 файлов из которых интерфейсов будет более 1000
      Подключаете в конструктор тот интерфейс который нужен для логики и используете...функций нет, статических методов нет...protected свойств и методов тоже практически нет

    • @woodzimierz9621
      @woodzimierz9621 4 года назад

      @@grommaks ну просто планета Шелезяка. Вообще ни чего нет. )))

    • @grommaks
      @grommaks 4 года назад

      @@woodzimierz9621 аххха)))

  • @alexanderp4532
    @alexanderp4532 4 года назад

    11:20 оуоуоу, палехчи, если мы не используем наследование, мы используем композицию. Народная программистская мудрость: "Зачем нужно наследование, если есть композиция". Любой более-менее опытный программист, пишущий на одном из ООП языков скажет, что если есть возможность выбрать между наследованием и композицией, то однозначный выбор - композиция, а преимущества наследования можно получить при помощи интерфейсов

    • @SergeyNemchinskiy
      @SergeyNemchinskiy  4 года назад +2

      ох, распространенное убеждение. Сниму видео

    • @alexanderp4532
      @alexanderp4532 4 года назад

      @@SergeyNemchinskiy к слову в Go отказались полностью от наследования и предпочли именно вариант с интерфейсами и композицией. Отсутствие наследования не особо больно бьет, жить можно вполне (да Go не ООП язык, но структуры и функции, принадлежащие структурам вполне позволяют реализовывать ООПшные практики)

  • @tislambek
    @tislambek 4 года назад

    Можете расказать в следующем видео о проекты спринга