Я знаю чому для Вас формулювання Барбари Лісков складне і заплутане! Все тому, що для Вас і квадрат - це не прямокутник!) Дякую за відео. Приклад з квадратом підірвав мій мозок.
Я бы еще добавил, что при оверрайде неабстрактных методов, новый метод должен уметь принимать параметры в таком же диапазоне или шире (но ни в коем случае не уже), а при возвращении значений - в таком же диапазоне или уже (но ни в коем случае не шире). Например в каком-то классе есть метод, который умеет принимать число от 1 до 100 и возвращать от 1 до 10. Если его заоверрайдить так, что новый будет уметь принимать 0..200, а возвращать 3..5 - существующий клиентский код будет доволен. А вот если переписать так, что новый метод умеет принимать только 1..50 или вдруг возвращает 0..20 - то существующий клиентский код может быть неприятно удивлён. Также можно было бы еще сказать, что список выбрасываемых исключений можно сокращать, а исключения - конкретизировать, а вот наоборот - добавлять новые или расширять существующие - нельзя. Но с контролируемыми исключениями компилятор Джавы в этом плане сам подскажет, что можно, а что нет, а неконтролируемые... на то они и неконтролируемые :)
что то я не уверен что вы правильно написалали, если базовый класс возвращал от 1 до 10, а наследник будет возвращать от 3 до 5 а это ведь нарушение принципов о которых говорит Сергей - наследник не может возвращать меньше чем базовый класс. Базовый класс будет ожидать что при определенных параметрах вернется 10, а наследник вернет максимум 5 и все сломается.
@@vladzaiko5012 Представьте, что есть клиентский код, который умеет разговаривать на английском, французском и немецком. Есть базовый компонент, который иногда выдаёт клиенту что-то на английском, а иногда - на немецком. Клиент понимает эти сообщения от компонента. Если заменим этот компонент на новый, который будет выдавать сообщения только на английском (т.е. сузим диапазон возвращаемых значений), то клиентский код всё еще будет понимать эти сообщения. А вот если новый компонент вдруг заговорит на японском (т.е. расширим диапазон возвращаемых значений), то клиентский код сломается - потому что клиентский код не знает, что делать с сообщениями на японском.
@@BukasovMaxim так в том то и дело что клиентский код, который работает с базовым классом он не знает о функционале на японском языке, который уже появился в наследниках, он не может его вызвать и сломаться.
@@vladzaiko5012 Речь не о новых методах. Речь о старых методах, которые в новых компонентах вдруг начали выдавать на выход то, что старые компоненты не выдавали. Пример: батарейка (как компонент). Допустим у нас есть электрическая схема (клиент), которой нужно питание +5В +/-10% (т.е. от 4.5 до 5.5 - будет работать). Если у нас есть батарейка, которая выдаёт +5В +/- 5% (т.е. от 4.75 до 5.25), то все ОК, схема будет работать и не сгорит (потому что при замене компонентов выходные диапазоны можно сужать без проблем). Но если мы вставим батарейку +5В +/-20%, то она может дать или слишком мало (и схема не заработает) или слишком много (и схема сгорит). Поэтому, при подмене компонентов, расширять возвращаемый диапазон - нельзя (можно только сужать). Входной диапазон - наоборот: сужать нельзя (чтобы клиент не послал в компонент что-то такое, что он не поймёт), а расширять - можно (чтобы компонент был не глупее, чем его предок).
@@BukasovMaxim спасибо за отличное объяснение! А что, если у нас есть класс User и класс Admin, который является наследником от User? Допустим, в User есть метод, вохвращающий все права пользователей. User будет возвращать стандартные разрешения типа редактировать аккаунт и проч. Но у Admin нужно будет расширять этот список. Верно ли я понимаю, что в данном случае произойдёт нарушение принципа LSP? Как этого избежать? Не лучше ли выделить отдельный класс с правами, чтобы не нарушать SRP?
Чувак, и мне показалось вариант Барбары намного ясный и простой. Но задело то, что автор видео так обобщает, "всем непонятно". Бро, как раз таки и понятно. Это может для гуманитариев проблема понять из-за боязни математики.
Есть 2 класса Pistol и Rifle. Оба должны стрелять и перезаряжаться, значит выносим эту логику в класс Weapon и наследуемся от него. Появлятся новое оружие Knife, которое тоже атакует (стреляет) и также наследуется от Weapon. Теперь нож и стреляет, и перезаряжается. Реализуем LSP: между классами Weapon -> Pistol/Rifle делаем прослойку Gun и выносим туда логику перезарядки, а в Weapon оставляем только атаку/стрельбу. В итоге получаем: Weapon -> Gun -> Pistol/Rifle и Weapon -> Knife
Что делать, если к такому пришли только после написания кода? Например, нужно было сделать пистолет и ружьё, написали классы: Weapon -> Pistol, Weapon -> Rifle, а потом заказчик добавил в ТЗ нож. Что теперь делать?
@@JackFastGame Если код был написан правильно, то другие классы обращались к оружию через интерфейс Weapon и знать не знали, что там внутри. Тогда, если расширить структуру наследования от Weapon, как в комментарии выше, для внешнего кода работа этого класса не изменится.
Я наследую только абстрактные классы где имплементирую основное поведение, частично переопределяемое в классах наследниках. Но вот так чтоб в чистом виде унаследоваться от другого конкретного класса - такого тоже нет.
Возможно, описание самой Барбары станет более понятным, если понимать когда она его предложила и как выглядели ассоциации типов в ЯП в то время, так как *спойлер* наследование не единственный способ ассоциации классов, а скорее частный случай. Насчёт использования наследования вы скорее всего заблуждаетесь, но причина заблуждения это легаси информация, которую из года в год перевыпускают в требованиях к коду. Если вы боретесь с копипастингом кода в проекте, то для этого больше подходит композиция кода. А если и методология разработки у вас не waterfall, то n-ступенчатая иерархия наследования превратит процесс изменения кода в ад (не забываем, что классы содержат ещё и стейты). Наследование сильный инструмент, но его главное назначение в продуктах, которые должны иметь жёсткую структуру иерархии, а так же максимально долгое время жизни самой иерархии. Напрмер фреймворки. Там наследование чувствует себя хорошо.
Ты сыпишься, потому что Solid ложны. Каждый принцип ложен! Давай про Srp. Когда ты создаёшь дочерний класс, ты его создаёшь именно таким, какой нужен именно в твоём коде. И именно с таким интерфейсом, который нужен твоему софту. Далее Ocp - ты не только можешь менять код метода, но и должен если это нужно для оптимизации. Да, представь себе Ocp тоже ложен. Далее Lsp - зачем тебе порождать наследников и закидывать в какой либо метод работающий с классом родителем. Ну, вот ты и не можешь объяснить... У немчи6ского яйц нет признать, Solid - фуфло.
Изучаю Java с нуля. Даром прошли 5 лет в 1С, ООП - это взрыв мозга )))) 11:20 - я не могу использовать ООП в 1С, там только процедурный код - и да, я чувствую себя рукожопом, но за хорошую зарплату.
Принцип на примере семьи Хилтон: 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; } }
Очень наглядное объяснение. Проще говоря, нужно наследовать большее от меньшего, а не наоборот. Все станет на свои места, если считать, что квадрат - это частный случай прямоугольника, а не его наследник. Именно такой подход применят учёные. Потому обьяснение Барбары и не понятно программистам с ООП профдеформацией. :)
Проблема не столько с определением площади, сколько с установкой сторон в клиентском коде. Правильным решением является создание абстрактного класса Figure с методом вычисления площади, от которого наследуется отдельно Rect и отдельно Square и реализуют метод вычисления площади. При этом для клиентского кода логика работы по установке сторон Rect и Square становится разная. Т.е. прямоугольник и квадрат по сути это разные сущности, как ,например, прямоугольник и круг.
@@danilakapitanov7044 Раз уж речь про клиентский код работы с фигурами, то характеристики фигур должны прописываться при создании, и отсутствовать методы их изменения, которые будут разными для разных фигур. К примеру для фигур могут быть методы get_square() для площади, и scale() для изменения размера. Но никак не изменения характеристик описывающих фигуру по отдельности. Для изменения фигуры с другими сторонами нужно создать новую фигуру, а предыдущую удалить.
По моей оценке вся суть проблемы была не раскрыта. Наследование как "анти паттерн", основана на сложности соблюдение LSP принципа. Пройдёмся по примерам в этом ролике: 1. Здесь Сергей рассказывает как плохо нарушать LSP принцип и к чему это может привести. Но дело не в том что его нельзя явно нарушать (это и так понятно), а в том что его очень сложно соблюдать. 2. Сергей здесь спасибо вам за альтернативное определение LSP (оно добавляет глубины понимания!) хотя по моей оценке примеры всё равно слабы. 3. По моей оценке этот пример вообще не об этом. Здесь проблема не в соблюдении или несоблюдении LSP, а в отсутствии инкапсуляции. Это был хороший пример того что лучше писать иммутабильные классы а setters/getters могут раскрыть зачастую внутреннюю структуру класса. С точки зрения наследования - это как раз один из немногих примеров где наследование является корректным. Наследования квадрата от прямоугольника является математически правильным и хорошо обоснованным с точки зрения качеств квадрата по отношению к качествам прямоугольника.Если бы состояние этих классов задавалось только при помощи конструктора, то никакой проблемы не было бы.
Это нарушает 1-й принцип SLP, каждому объекту своя зона ответственности, доска для того, чтобы бумагу удерживать, и бумага не рвалась под фломастером. А бумага уже для отображения текстовой информации, так что тут все грамотно...
Я постараюсь привести пример проблематики соблюдения LSP принципа. Допустим у вас есть класс Human. У этого класса есть такие поля как; пол, возраст, рост, вес и тд.. Теперь представим что у нас есть класс Student. В этом классе добавляется название колледжа, номер курса, и специальность. Согласитесь что наследование студента от человека выглядит довольно безобидным и естественным. Ещё представим что в текущей версии кода есть метода которая вычисляет налог "социального страхования" для любого человека, и основана она только на возрасте человека. Понятно что в текущей версии кода, LSP принцип соблюдается для этой методы. А теперь представим что в будущем закон изменился и теперь студенты освобождены от оплаты налогов. С этого момента LSP принцип нарушен. проблема здесь на самом деле глубокая и заключается она в том что если B наследует от A, то требуется доказать математически что B является A. Дело в том что наследование Student от Human основывается на нашем естественном восприятии но в то же время плохо определено в математическом смысле. Вся сложность LSP является в том что наследование должно быть корректно не только в текущей версии кода, но и во всех (неизвестно на сегодня) будущих его версиях. Когда домейн классов находится в математической плоскости нам легче создать корректное наследование, но к сожалению это лишь малая часть всех проблем которыми мы занимаемся. Отсюда вытекает общий антипаттерн наследования.
Программирую на php в Symfony. На самом деле крайне редко приходится использовать наследование. Чаще фреймворк предоставляют интерфейсы, чем абстрактные классы. А наследоваться от какой-то конкретной реализации - тоже не совсем приятно. Поэтому случаев наследования можно по пальцам одной руки пересчить. Но принцип постановки Лисковой зашёл. Спасибо! Теперь бы про инверсию зависимости послушать)
потому, что ооп это не про наследование. В чистой архитектуре про это написано. Ооп это про полиморфизм. Старину Боба видимо не всего Немчинский читал, либо читал и не понял.
Хе-хе, люблю этот пример с квадратом и прямоугольником. Только он еще веселее. Если ввести иммутабельность с втиле старого доброго ФП и после вызова любого "мутирующего" метода возвращать экземпляро нового объекта, то квадрат-таки можно сделать проямоугольником, не нарушая принципы ООП ;-)
Квадрат выглядит как частный случай прямоугольника, так же как квадрат частный случай ромба или прямоугольник частный случай параллелограмма. На самом деле это всё фигуры и на одна из них не является дочерней по отношению к другой. Я писал об этом здесь: github.com/peter-gribanov/clean-code-php
Вот что интересно, базовые классы Java нарушают LSP совершенно без стеснения. Попробуйте, например, сделать так: Map map = Map.of("a", 10, "b", 20); map.put("c", 30); и охренейте от того, что вам прилетит Exception в Runtime. При том что вся эта лабуда прекрасно скомпилируется. Когда-то налип с этим делом по незнанию, пришлось в довольно большом пакете искать где я делал Map.of и переделывать на new HashMap.
В целом, неплохо рассказано, конечно. Я бы добавил в формат реальный кейс с кодом, примеров нарушений принципа в системных библиотеках (в той же Java нарушения лисков достаточно). Ещё не затронута тема ковариантности и контравариантности. Иии что значит нельзя возвращать null из методов? Это абсолютно нормально и даже нужно. Естественно, с документацией. Или когда ваш язык поддерживает нормальные nullable типы - null как отдельный тип данных.
@@SergeyNemchinskiy Если вы имеете ввиду принцип Лисков, то и тут не соглашусь, всё зависит от контракта, предоставляемым базовым типом, если там подразумевается null, то можно. А если вы имеете ввиду вообще, в целом, то и тут неясна ваша идея. В некоторых случаях, действительно, есть лучше способ показать отсутствие данных. Например, в .NET - try pattern, когда возвращаемое значение - bool, показывающее результат операции, а данные возвращаются через ссылку одним из аргументов. Также в .net ввели nullable reference types, с помощью которого явно видно, где тип может быть null, а где нет.
0imax Касаемо чего то перечисляемого, да, тут лучшим способом будет вернуть что то пустое, но существующее. И то стоит понимать, что могут быть моменты, когда нужно уметь различать отсутсвие самого списка и отсутствие в списке вообще кого то. Но обычные объекты, возвращаемые аля GetUserById, GetActualPlayerConnection и прочее - здесь лучшим способом заявить об отсутствии чего то только null, никаких throw
@@haqon2k а по мне так лучше кинуть нормальный exception и обработать его, чем возвращать null: Во-первых, правильно названный exception сразу даёт информацию о причине "поломки". А во-вторых, таким образом можно обработать ситуацию, когда причин отсутствия объекта может быть несколько, как с тем же connection.
Ну так, а какое все таки решение в вопросе прямоугольник-квадрат? Все его приводят в пример, а решения ни кто не приводит! Получается два выбора: либо отказаться от наследования и следовать LSP... Но тогда будет нарушен другой же принцип - не дублируй код. Т.е. если у вас в базовом классе (например в прямоугольнике) двести методов и один из них не LSP, то что тогда?
@@SergeyNemchinskiy Квадрат это частный случай от прямоугольника (по крайней мере я лучше выкину принципы solid чем так поступлю). И функции setWidth и setHight нужно будет переписывать для прямоугольника, что опять же нарушает - дочерний класс переопределяет функциональность базового. Теперь новый side эффект - я ожидаю что стороны равны для квадарта (базового класса), а для прямоугольника - не равны. Проблема с функцией площади теперь выливается аналогичную проблему с функцией с периметром...
Принцип подстановки безусловно правильный, но реально работает лишь в отдельных ограниченных случаях. Начиная с определенного момента ваши абстракции начинают течь, и решить эту проблему становится гораздо сложнее, нежели если бы LSP вообще не использовался бы с самого начала: приходится расширять интерфейс и патчить все зависимости. Пример протекающей абстракции -- это любой интерфейс драйвера, который возвращает "driver capabilities".
Я вот чет не понял, что плохого в примере с квадратом ) Ну, перезаписал он сторону с 10 на 5, ну, посчитал он площадь 25. Так она такая и есть в его случае ) Одинаковый интерфейс родителя и наследника никак не означает одинакового результата. Если он одинаковый всегда, - нахрена наследовали-то? )
Сложно представить сценарий, когда при наследовании захочется переопределить какой-то метод базового класса чтобы он кидал NotIImplementedException, у кого был грешок, расскажите, что вас к этому привело?
Насколько я понял из поиска по гуглу. Тут весь пример завязан на Mock об'екте который по определению является пустышкой с методами заглушками. И если оставлять родительськую реализацию то нарушается смысл. Это как я понял. Если кто поправит буду рад
Раз уж вы сказали про наследование и попросили предложить что после СОЛИД рассказать, то предлагаю достаточно холиварную тему: наследование vs композиция. Заранее прошу прощения если вы эту тему уже поднимали
Немного про моки, раз уж Сергей про них заговорил. Чтобы замокать только строго определенные методы стороннего компонента, вызывающегося в тестируемом коде, можно использовать библиотеки типа Mockito или PowerMock, вместо того чтобы самостоятельно писать mock-реализацию этого компонента. Это стандартное решение, все так делают. Есть еще идея Егора Бугаенко: для каждого компонента в его интерфейсе писать fake-реализацию этого интерфейса, которая будет использоваться в тестах всех зависимых компонентов. Это похоже на то, о чем говорит Сергей Немчинский, но ответственность за написания мока берет на себя не разработчик клиентского кода, а разработчик компонента. Соответственно разработчик компонента обязан реализовать все методы этой fake-реализации. Немного утопическая идея, но она решает проблему хрупкости, присущей тестам, написанным с помощью Mockito.
@@SergeyNemchinskiy Его основная идея заключается в том, что эти заглушки должны быть написаны в java-файле предоставляемого интерфейса. Мол это позволит пользователям вашей библиотеки не писать заглушки самостоятельно, что уменьшит дублирование кода на планете)) А так мне и самому Егор Бугаенко не нравится, слишком уж он хайпожористый
На примере mock, можно ведь для неиспользуемых методов оставить имплементацию парент класса, если это, конечно, не абстрактый класс или мы не имплеменим интерфейс. Хотя тут уже тогда нарушение как раз второго принципа-необходимо использовать интерфейс, или я что-то упустил?)
Я просто офигеваю от того, какой Сергей потрясающий интеллектуал. Мне страшно смотреть его видео. Страшно от того, что голова может лопнуть от обилия деталей, но потом трясущейся рукой тянусь и нажимаю кнопку начала просмотра, а потом инфа кое-как укладывается под отличную и ровную мелодию под методичный голос Сергея… P.S. короче, как тупой я понял принцип LSP главным образом в том, что не должен наследуемый класс делать больше, чем его основной класс.
Мое дБ должна быть сделана с помощью мок апи, например mockito, jmock, easymock, etc Не надо самим эти сроки писать, ТК их количество будет рочти постоянно. Эти сроки тоже могут баги иметь. И тоже надо тестить)))
Такая же ситуация, но успокаиваю себя тем что джуну вроде как не обязательно наличие в стеке этой технологии и в будущем я смогу уже на реальном проекте это изучить
@@YoungT18 Mockito и JUnit обязательно нужно знать джуну, также будет в разы круче, если код, например, в пет-проектах будет протестирован, потому что не протестированный код = нерабочий код
Что сложного, вызвал метод, передал тестов данные, и сравнял с заранее верным ответом. Это примитив, но смысл таков. Модульные тесты сложнее, но тоже эмалируешь роботу кода и сравниваешь с подготовленным ответом.
Ничего страшного совершенно, с ними другая проблема, что их ленятся писать. В Java все юнит тесты: @Before, @Test, assert(Equals/That/...) Если прочитать как это работает, то... Это в целом всё. А прочитать можно в первой же статье в гугле по запросу "Java UnitTest"
На счет возврата налл из метода - очень интересно ваше мнение Чем вам неугодил Актив Рекорд и какие вы видете альтернативы (все так работать с объектом который можно собрать из кусков и сохранить достатолчно удобно)
Ну вот отнаследовался я от Rect, сделал Square. На свойство b забил, переопределил метод getSquare, где работаю только с a. Ещё переопределил геттер/сеттер b, чтобы он работал с a вместо b. Работать будет, и даже тесты не рухнут скорее всего. Но плохо то, что есть лишнее свойство b, плохо то, что неочевидно работают геттеры/сеттеры b. Принцип именно в том, что не должно быть лишних свойств и неочевидного поведения?
Принцип в том, что если подсунуть Square (в вашем случае) вместо Rect, то поведение не должно измениться. А оно изменится, причём с нарушением здравого смысла. Это и есть нарушение принципа LSP.
Влад Солнцев так через интерфейсы, минуя наследование, полиморфизм и делается. Главные парадигмы инкапсуляция и полиморфизм. Конечно можно с натяжкой сказать, что реализация интерфейсов это тоже наследование по сути...
@@SergeyNemchinskiy Смотрю Вас из интереса уже наверное больше года, когда ещё начинал знакомиться с Java, сейчас вообще Python разработчик. Так вот качество видосов стало на порядок выше, видно, что развиваетесь. Так держать! Пожалуйста, подумайте по поводу маркерной доски, а то эти шуршания маркера по бумаге некоторых приводят в ужас (меня например).
Раскрыта только одна сторона медали... Еще нужно рассмотреть вариант, что у нас есть абстрактный класс / интерфейс и фиг пойми сколько наследников Как правильно доработать существующий метод (уже опубликованный), чтобы ни один из наследников не нарушил L из SOLID Это очень холиварное заявление по поводу наследования...Ваши предки накидали г..на в больших системах, что невозможно работать :) Magento - PHP решение для сложных магазинов с соблюдением всех возможных подходов из Java об OOP И Magento 2 решили отказаться от наследования, получив при этом очень расширяемую систему и ни разу не потеряв в OOP шности... Т.е. контроллеры (и прочие инфрастуктуры) не наследуют базовый класс, а реализуют интерфейс. Magento 1 с возможностью строить цепочки по 10-20 наследников это больно сильно Допустим PHP это не ЯП, а магазины это слишком легко для Java разработчика... Но в Красной книге по DDD есть отдельная глава, где говорится, что архитектура может быть на базе наследования и моделей с состояниями или на базе сервисов без состояния и анемичными моделями...и то и то OOP подход основанный на различном фундаменте...со своими плюсами и минусами За видео спасибо :)
В vendor/magneto/framework В интерфейсах, например DirectoryListInterface Filesystem/DriverInterface и так далее Сама Magento после установки содержит 45 000 файлов из которых интерфейсов будет более 1000 Подключаете в конструктор тот интерфейс который нужен для логики и используете...функций нет, статических методов нет...protected свойств и методов тоже практически нет
11:20 оуоуоу, палехчи, если мы не используем наследование, мы используем композицию. Народная программистская мудрость: "Зачем нужно наследование, если есть композиция". Любой более-менее опытный программист, пишущий на одном из ООП языков скажет, что если есть возможность выбрать между наследованием и композицией, то однозначный выбор - композиция, а преимущества наследования можно получить при помощи интерфейсов
@@SergeyNemchinskiy к слову в Go отказались полностью от наследования и предпочли именно вариант с интерфейсами и композицией. Отсутствие наследования не особо больно бьет, жить можно вполне (да Go не ООП язык, но структуры и функции, принадлежащие структурам вполне позволяют реализовывать ООПшные практики)
💪Новый поток advanced тренинга Enterprise patterns стартует 2.12 - go.foxminded.ua/4eXh83k
Роберт что-то непонятное сказал, то ли дело Барбара
XDXDXD
Математически чёткое описание всегда более точное, чем словесное описание. Например,
Правила Ассоциативноти и Дистрибутивности и звучат лучше...
Сергей, спасибо! С вашими видео обучение программированию становится сплошным кайфом!
Я знаю чому для Вас формулювання Барбари Лісков складне і заплутане! Все тому, що для Вас і квадрат - це не прямокутник!)
Дякую за відео. Приклад з квадратом підірвав мій мозок.
Я бы еще добавил, что при оверрайде неабстрактных методов, новый метод должен уметь принимать параметры в таком же диапазоне или шире (но ни в коем случае не уже), а при возвращении значений - в таком же диапазоне или уже (но ни в коем случае не шире).
Например в каком-то классе есть метод, который умеет принимать число от 1 до 100 и возвращать от 1 до 10. Если его заоверрайдить так, что новый будет уметь принимать 0..200, а возвращать 3..5 - существующий клиентский код будет доволен. А вот если переписать так, что новый метод умеет принимать только 1..50 или вдруг возвращает 0..20 - то существующий клиентский код может быть неприятно удивлён.
Также можно было бы еще сказать, что список выбрасываемых исключений можно сокращать, а исключения - конкретизировать, а вот наоборот - добавлять новые или расширять существующие - нельзя. Но с контролируемыми исключениями компилятор Джавы в этом плане сам подскажет, что можно, а что нет, а неконтролируемые... на то они и неконтролируемые :)
что то я не уверен что вы правильно написалали, если базовый класс возвращал от 1 до 10, а наследник будет возвращать от 3 до 5 а это ведь нарушение принципов о которых говорит Сергей - наследник не может возвращать меньше чем базовый класс. Базовый класс будет ожидать что при определенных параметрах вернется 10, а наследник вернет максимум 5 и все сломается.
@@vladzaiko5012 Представьте, что есть клиентский код, который умеет разговаривать на английском, французском и немецком. Есть базовый компонент, который иногда выдаёт клиенту что-то на английском, а иногда - на немецком. Клиент понимает эти сообщения от компонента.
Если заменим этот компонент на новый, который будет выдавать сообщения только на английском (т.е. сузим диапазон возвращаемых значений), то клиентский код всё еще будет понимать эти сообщения. А вот если новый компонент вдруг заговорит на японском (т.е. расширим диапазон возвращаемых значений), то клиентский код сломается - потому что клиентский код не знает, что делать с сообщениями на японском.
@@BukasovMaxim так в том то и дело что клиентский код, который работает с базовым классом он не знает о функционале на японском языке, который уже появился в наследниках, он не может его вызвать и сломаться.
@@vladzaiko5012 Речь не о новых методах. Речь о старых методах, которые в новых компонентах вдруг начали выдавать на выход то, что старые компоненты не выдавали.
Пример: батарейка (как компонент). Допустим у нас есть электрическая схема (клиент), которой нужно питание +5В +/-10% (т.е. от 4.5 до 5.5 - будет работать). Если у нас есть батарейка, которая выдаёт +5В +/- 5% (т.е. от 4.75 до 5.25), то все ОК, схема будет работать и не сгорит (потому что при замене компонентов выходные диапазоны можно сужать без проблем). Но если мы вставим батарейку +5В +/-20%, то она может дать или слишком мало (и схема не заработает) или слишком много (и схема сгорит). Поэтому, при подмене компонентов, расширять возвращаемый диапазон - нельзя (можно только сужать).
Входной диапазон - наоборот: сужать нельзя (чтобы клиент не послал в компонент что-то такое, что он не поймёт), а расширять - можно (чтобы компонент был не глупее, чем его предок).
@@BukasovMaxim спасибо за отличное объяснение!
А что, если у нас есть класс User и класс Admin, который является наследником от User? Допустим, в User есть метод, вохвращающий все права пользователей. User будет возвращать стандартные разрешения типа редактировать аккаунт и проч. Но у Admin нужно будет расширять этот список. Верно ли я понимаю, что в данном случае произойдёт нарушение принципа LSP?
Как этого избежать? Не лучше ли выделить отдельный класс с правами, чтобы не нарушать SRP?
Математический вариант Барбары все разложил по полочкам, а то так много воды и ничего не понятно.
ахаха. Ну, рад за вас :)
Попався, математiк
Тоже объяснение Барбары показалось более простым и понятным, хотя не математик. Может это потому что я женщина?:D
Чувак, и мне показалось вариант Барбары намного ясный и простой. Но задело то, что автор видео так обобщает, "всем непонятно". Бро, как раз таки и понятно. Это может для гуманитариев проблема понять из-за боязни математики.
странно что ты за этим вариантом полез на ютьюб к немчинскому
TDD - ДА! Про return null - ДА!
Спасибо за видео!
Есть 2 класса Pistol и Rifle. Оба должны стрелять и перезаряжаться, значит выносим эту логику в класс Weapon и наследуемся от него. Появлятся новое оружие Knife, которое тоже атакует (стреляет) и также наследуется от Weapon. Теперь нож и стреляет, и перезаряжается. Реализуем LSP: между классами Weapon -> Pistol/Rifle делаем прослойку Gun и выносим туда логику перезарядки, а в Weapon оставляем только атаку/стрельбу. В итоге получаем: Weapon -> Gun -> Pistol/Rifle и Weapon -> Knife
Что делать, если к такому пришли только после написания кода? Например, нужно было сделать пистолет и ружьё, написали классы: Weapon -> Pistol, Weapon -> Rifle, а потом заказчик добавил в ТЗ нож. Что теперь делать?
@@JackFastGame по нормальному неплохо было бы продумать все это заранее. Здесь же я привел свой пример, как выглядит LSP на практике.
@@JackFastGame Если код был написан правильно, то другие классы обращались к оружию через интерфейс Weapon и знать не знали, что там внутри. Тогда, если расширить структуру наследования от Weapon, как в комментарии выше, для внешнего кода работа этого класса не изменится.
надо!
про тест фёст надо
про нуль из методов
и ДОСКУ МАРКЕРНУЮ! НАДО!
и про красную футболку!
доску особенно, уши режет
дададад! и про TDD и про null надонадонадо!
Доска как раз под бумагой. Приглядись)
Август месяц на дворе, а его все еще зовут Сергей Немчинский. Хосспаде!
Спасибо за видео 😄Лайк.
А в чём прикол?
Попрошу! Сергей "тутромбик" Немчинский!
Сергей, вы такой живчик в этом видео. Отлично получилось!
Спасибо огромное!!! В 3х словах рассказал то что я не смог понять ни в одном видео до этого.
А я их пересмотрел штук 10
а я снова нихуя не понял
Сергей, выражаю вам искреннюю благодарность
@Sergey Nemchinskiy уже раньше обещал про "почему нельзя возвращать null"
Про TDD интересно было бы посмотреть.
про то, как оно в жизни
Очень доступное объяснение, особенно в разрезе примеров. Сразу вспомнил где я уже наговнокодил...
Я не использую наследование, я имплементирую интерфейсы, и занимаюсь агрегацией и композицией
Ты сраный рукожоп по версии Немчинского)) Я тоже агрегирую и композирую с интерфейсами + ещё дженерики
Имплементирование можно с натяжкой отнести к наследованию.
аналогично
Я наследую только абстрактные классы где имплементирую основное поведение, частично переопределяемое в классах наследниках. Но вот так чтоб в чистом виде унаследоваться от другого конкретного класса - такого тоже нет.
@@makaroningable есть товарищи, которые отрицают любую форму наследования
Возможно, описание самой Барбары станет более понятным, если понимать когда она его предложила и как выглядели ассоциации типов в ЯП в то время, так как *спойлер* наследование не единственный способ ассоциации классов, а скорее частный случай.
Насчёт использования наследования вы скорее всего заблуждаетесь, но причина заблуждения это легаси информация, которую из года в год перевыпускают в требованиях к коду.
Если вы боретесь с копипастингом кода в проекте, то для этого больше подходит композиция кода.
А если и методология разработки у вас не waterfall, то n-ступенчатая иерархия наследования превратит процесс изменения кода в ад (не забываем, что классы содержат ещё и стейты).
Наследование сильный инструмент, но его главное назначение в продуктах, которые должны иметь жёсткую структуру иерархии, а так же максимально долгое время жизни самой иерархии. Напрмер фреймворки. Там наследование чувствует себя хорошо.
Классное видео, спасибо за детальное и простое объяснение, хотелось бы еще послушать про паттерн pipeline.
@Sergey Nemchinskiy: Мужик, здоровья тебе! Отличное пояснение! ;)
Самый сложный принцип, я вечно сыплюсь на нем))) но объяснение с прямоугольником и квадратом просто огонь! Спасибо 🙏
Ты сыпишься, потому что Solid ложны. Каждый принцип ложен! Давай про Srp. Когда ты создаёшь дочерний класс, ты его создаёшь именно таким, какой нужен именно в твоём коде. И именно с таким интерфейсом, который нужен твоему софту. Далее Ocp - ты не только можешь менять код метода, но и должен если это нужно для оптимизации. Да, представь себе Ocp тоже ложен. Далее Lsp - зачем тебе порождать наследников и закидывать в какой либо метод работающий с классом родителем. Ну, вот ты и не можешь объяснить... У немчи6ского яйц нет признать, Solid - фуфло.
1:16 ну нафиг)) Как это прочитать получилось?!)
1:50 а не. вот тут нафиг, дяде Бобу спасибо огромнейшее!
Об ACID интересно послушать.
А за SOLID благодарю)
было, Владимир Кузнецов рассказывал.
@@maxlich9139 Дякую! Шукатиму.
Дуже вам дякую за пояснення. Все просто і зрозуміло )
Або це просто вже 100те відео про Лісков і я нарешті зрозумів, або це найкраще з тих відео що я бачив і я зрозумів =))
я просто умею объяснять :)
Сергей, тема разработки через тестирование очень интересна! Как и SOLID. Спасибо.
Хотелось бы больший упор на "как надо" вместо "как не надо"
Просто берете как не надо и делаете не так и будет как надо
С опытом придёт.
@@Gusto20000 и если это приходится объяснять, то, наверное, лучше никак не надо ;)
@@Gusto20000 Часто бывает 101 способ как не надо и только парочку как надо.
Кск би так убегая от плохого 98 способа не попасть в 86))
что касаемо lsp то что бы его не нарушать нужно просто придерживаться ооп. Надо постараться что бы его нарушить.
Изучаю Java с нуля. Даром прошли 5 лет в 1С, ООП - это взрыв мозга ))))
11:20 - я не могу использовать ООП в 1С, там только процедурный код - и да, я чувствую себя рукожопом, но за хорошую зарплату.
Принцип на примере семьи Хилтон:
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;
}
}
Отличная шутка про ведение бизнеса 😂
Хорошее видео, жду такое же про Оксимирона
Спасибо большое, Сергей!)
Очень понятно объясняете - супер, спасибо
Я себе тоже такую футболочку взял, тока черную, чтоб прям в тему последних времен было)
Это когда джаву в js тоже будут компилить, наверное)
Очень наглядное объяснение. Проще говоря, нужно наследовать большее от меньшего, а не наоборот. Все станет на свои места, если считать, что квадрат - это частный случай прямоугольника, а не его наследник. Именно такой подход применят учёные. Потому обьяснение Барбары и не понятно программистам с ООП профдеформацией. :)
ООП профдеформация это интересно))) всё под неё пытаются подтянуть, иногда усложняя код до нельзя...
Спасибо.
Только с квадратом как быть? Разве при наследовании у него не разумно переопределить метод подсчета площади?
Проблема не столько с определением площади, сколько с установкой сторон в клиентском коде. Правильным решением является создание абстрактного класса Figure с методом вычисления площади, от которого наследуется отдельно Rect и отдельно Square и реализуют метод вычисления площади. При этом для клиентского кода логика работы по установке сторон Rect и Square становится разная. Т.е. прямоугольник и квадрат по сути это разные сущности, как ,например, прямоугольник и круг.
@@danilakapitanov7044 Раз уж речь про клиентский код работы с фигурами, то характеристики фигур должны прописываться при создании, и отсутствовать методы их изменения, которые будут разными для разных фигур. К примеру для фигур могут быть методы get_square() для площади, и scale() для изменения размера. Но никак не изменения характеристик описывающих фигуру по отдельности. Для изменения фигуры с другими сторонами нужно создать новую фигуру, а предыдущую удалить.
По моей оценке вся суть проблемы была не раскрыта.
Наследование как "анти паттерн", основана на сложности соблюдение LSP принципа.
Пройдёмся по примерам в этом ролике:
1. Здесь Сергей рассказывает как плохо нарушать LSP принцип и к чему это может привести. Но дело не в том что его нельзя явно нарушать (это и так понятно), а в том что его очень сложно соблюдать.
2. Сергей здесь спасибо вам за альтернативное определение LSP (оно добавляет глубины понимания!) хотя по моей оценке примеры всё равно слабы.
3. По моей оценке этот пример вообще не об этом. Здесь проблема не в соблюдении или несоблюдении LSP, а в отсутствии инкапсуляции. Это был хороший пример того что лучше писать иммутабильные классы а setters/getters могут раскрыть зачастую внутреннюю структуру класса.
С точки зрения наследования - это как раз один из немногих примеров где наследование является корректным. Наследования квадрата от прямоугольника является математически правильным и хорошо обоснованным с точки зрения качеств квадрата по отношению к качествам прямоугольника.Если бы состояние этих классов задавалось только при помощи конструктора, то никакой проблемы не было бы.
TDD интересно. С удовольствием послушаю.
looking forward to a TDD video))
Надо убрать бумагу между маркером и доской, потом писать. Я так думаю))) спаисбо за видос
Это нарушает 1-й принцип SLP, каждому объекту своя зона ответственности, доска для того, чтобы бумагу удерживать, и бумага не рвалась под фломастером. А бумага уже для отображения текстовой информации, так что тут все грамотно...
Наконец-то я понял этот принцип)
Очень интересно про Test First!!! В топ!!
Хотелось бы с таким классным объяснением и примерами послушать о Паттернах Программирования :)
эм.... Ну вот же оно: foxminded.com.ua/grasp-gof-design-patterns-advanced-on-line-course/
Я постараюсь привести пример проблематики соблюдения LSP принципа.
Допустим у вас есть класс Human. У этого класса есть такие поля как; пол, возраст, рост, вес и тд..
Теперь представим что у нас есть класс Student. В этом классе добавляется название колледжа, номер курса, и специальность.
Согласитесь что наследование студента от человека выглядит довольно безобидным и естественным. Ещё представим что в текущей версии кода есть метода которая вычисляет налог "социального страхования" для любого человека, и основана она только на возрасте человека.
Понятно что в текущей версии кода, LSP принцип соблюдается для этой методы.
А теперь представим что в будущем закон изменился и теперь студенты освобождены от оплаты налогов. С этого момента LSP принцип нарушен.
проблема здесь на самом деле глубокая и заключается она в том что если B наследует от A, то требуется доказать математически что B является A.
Дело в том что наследование Student от Human основывается на нашем естественном восприятии но в то же время плохо определено в математическом смысле.
Вся сложность LSP является в том что наследование должно быть корректно не только в текущей версии кода, но и во всех (неизвестно на сегодня) будущих его версиях.
Когда домейн классов находится в математической плоскости нам легче создать корректное наследование, но к сожалению это лишь малая часть всех проблем которыми мы занимаемся.
Отсюда вытекает общий антипаттерн наследования.
Цілком погоджуюсь.
Ах**нно)
наконец то понял в чём фишка этого принципа
Программирую на php в Symfony. На самом деле крайне редко приходится использовать наследование. Чаще фреймворк предоставляют интерфейсы, чем абстрактные классы. А наследоваться от какой-то конкретной реализации - тоже не совсем приятно. Поэтому случаев наследования можно по пальцам одной руки пересчить. Но принцип постановки Лисковой зашёл. Спасибо! Теперь бы про инверсию зависимости послушать)
потому, что ооп это не про наследование. В чистой архитектуре про это написано. Ооп это про полиморфизм. Старину Боба видимо не всего Немчинский читал, либо читал и не понял.
TDD, буду рад выслушать
Про TDD очень интересно.
Супер, Сергей) Спасибо большое за видео, однозначно лайк!)
Спасибо большое за видео!
Сразу лайк, спасибо тебе большое!
11:02 так всё-таки какая ситуация лучше: перезаписать сторону или выбросить исключение? Как в данной ситуации действовать?
Никак. Только нарушать принцип OCP - управляющий код должен знать про два класса. Так написано у Мартина.
Про TDD интересно будет посмотреть.
Хе-хе, люблю этот пример с квадратом и прямоугольником. Только он еще веселее. Если ввести иммутабельность с втиле старого доброго ФП и после вызова любого "мутирующего" метода возвращать экземпляро нового объекта, то квадрат-таки можно сделать проямоугольником, не нарушая принципы ООП ;-)
Юнит тесты интересны очень, особенно какие то хитрости, типо того же test first
про пример с квадратом. что мешает переопределить только метод getSquare() return a*a ? и вообще не трогать b
Отлично. Про тесты видео нужно
Разве mock не должен лежать в модуле test, недосягаемом для основного кода?
Про TDD было бы очень интересно послушать!
Чёт прямо больно, что такие листы бумаги тратятся на то, что можно нарисовать на доске и стереть
А еще больно - звук маркера скрипучий.
Квадрат выглядит как частный случай прямоугольника, так же как квадрат частный случай ромба или прямоугольник частный случай параллелограмма. На самом деле это всё фигуры и на одна из них не является дочерней по отношению к другой. Я писал об этом здесь:
github.com/peter-gribanov/clean-code-php
ну круто я понял как делать не надо ,а как делать надо сам разберусь спасибо лайк щищ
Большое спасибо за выпуск!!!
Вот что интересно, базовые классы Java нарушают LSP совершенно без стеснения. Попробуйте, например, сделать так:
Map map = Map.of("a", 10, "b", 20);
map.put("c", 30);
и охренейте от того, что вам прилетит Exception в Runtime. При том что вся эта лабуда прекрасно скомпилируется. Когда-то налип с этим делом по незнанию, пришлось в довольно большом пакете искать где я делал Map.of и переделывать на new HashMap.
Лучший препод =) Что бы мы без вас делали)
В целом, неплохо рассказано, конечно. Я бы добавил в формат реальный кейс с кодом, примеров нарушений принципа в системных библиотеках (в той же Java нарушения лисков достаточно). Ещё не затронута тема ковариантности и контравариантности.
Иии что значит нельзя возвращать null из методов? Это абсолютно нормально и даже нужно. Естественно, с документацией. Или когда ваш язык поддерживает нормальные nullable типы - null как отдельный тип данных.
нельзя возвращать null, это очевидно
@@SergeyNemchinskiy Если вы имеете ввиду принцип Лисков, то и тут не соглашусь, всё зависит от контракта, предоставляемым базовым типом, если там подразумевается null, то можно.
А если вы имеете ввиду вообще, в целом, то и тут неясна ваша идея. В некоторых случаях, действительно, есть лучше способ показать отсутствие данных. Например, в .NET - try pattern, когда возвращаемое значение - bool, показывающее результат операции, а данные возвращаются через ссылку одним из аргументов. Также в .net ввели nullable reference types, с помощью которого явно видно, где тип может быть null, а где нет.
Я бы предпочёл получить из метода пустой список вместо null, прописанного где-нибудь в дебрях в документации.
0imax Касаемо чего то перечисляемого, да, тут лучшим способом будет вернуть что то пустое, но существующее. И то стоит понимать, что могут быть моменты, когда нужно уметь различать отсутсвие самого списка и отсутствие в списке вообще кого то. Но обычные объекты, возвращаемые аля GetUserById, GetActualPlayerConnection и прочее - здесь лучшим способом заявить об отсутствии чего то только null, никаких throw
@@haqon2k а по мне так лучше кинуть нормальный exception и обработать его, чем возвращать null:
Во-первых, правильно названный exception сразу даёт информацию о причине "поломки". А во-вторых, таким образом можно обработать ситуацию, когда причин отсутствия объекта может быть несколько, как с тем же connection.
Это самое лучшее объяснение принципа Лисков, что я встречал! Подписка однозначно
Ну так, а какое все таки решение в вопросе прямоугольник-квадрат? Все его приводят в пример, а решения ни кто не приводит! Получается два выбора: либо отказаться от наследования и следовать LSP... Но тогда будет нарушен другой же принцип - не дублируй код. Т.е. если у вас в базовом классе (например в прямоугольнике) двести методов и один из них не LSP, то что тогда?
наследовать прямоугольник от квадрата
Вообще квадрат не нужен)
@@SergeyNemchinskiy Квадрат это частный случай от прямоугольника (по крайней мере я лучше выкину принципы solid чем так поступлю). И функции setWidth и setHight нужно будет переписывать для прямоугольника, что опять же нарушает - дочерний класс переопределяет функциональность базового. Теперь новый side эффект - я ожидаю что стороны равны для квадарта (базового класса), а для прямоугольника - не равны. Проблема с функцией площади теперь выливается аналогичную проблему с функцией с периметром...
Хорошее объяснение, как обычно лайк
Принцип подстановки безусловно правильный, но реально работает лишь в отдельных ограниченных случаях. Начиная с определенного момента ваши абстракции начинают течь, и решить эту проблему становится гораздо сложнее, нежели если бы LSP вообще не использовался бы с самого начала: приходится расширять интерфейс и патчить все зависимости. Пример протекающей абстракции -- это любой интерфейс драйвера, который возвращает "driver capabilities".
Сергей, спасибо за видео!
Пожалуйста, снимите видео про возврат null, очень интересно, сам постоянно так делаю.
Давно ждал!
Я вот чет не понял, что плохого в примере с квадратом ) Ну, перезаписал он сторону с 10 на 5, ну, посчитал он площадь 25. Так она такая и есть в его случае )
Одинаковый интерфейс родителя и наследника никак не означает одинакового результата. Если он одинаковый всегда, - нахрена наследовали-то? )
TDD очень интересно!
По итогу - надо квадрат наследовать от прямоугольника или правильнее новый класс делать?
Сложно представить сценарий, когда при наследовании захочется переопределить какой-то метод базового класса чтобы он кидал NotIImplementedException, у кого был грешок, расскажите, что вас к этому привело?
Насколько я понял из поиска по гуглу. Тут весь пример завязан на Mock об'екте который по определению является пустышкой с методами заглушками. И если оставлять родительськую реализацию то нарушается смысл.
Это как я понял. Если кто поправит буду рад
Раз уж вы сказали про наследование и попросили предложить что после СОЛИД рассказать, то предлагаю достаточно холиварную тему: наследование vs композиция. Заранее прошу прощения если вы эту тему уже поднимали
расскажу. Регулярно поднимаю эту тему на тренингах, надо и в отдельном видео рассказать
Немного про моки, раз уж Сергей про них заговорил.
Чтобы замокать только строго определенные методы стороннего компонента, вызывающегося в тестируемом коде, можно использовать библиотеки типа Mockito или PowerMock, вместо того чтобы самостоятельно писать mock-реализацию этого компонента. Это стандартное решение, все так делают.
Есть еще идея Егора Бугаенко: для каждого компонента в его интерфейсе писать fake-реализацию этого интерфейса, которая будет использоваться в тестах всех зависимых компонентов. Это похоже на то, о чем говорит Сергей Немчинский, но ответственность за написания мока берет на себя не разработчик клиентского кода, а разработчик компонента. Соответственно разработчик компонента обязан реализовать все методы этой fake-реализации. Немного утопическая идея, но она решает проблему хрупкости, присущей тестам, написанным с помощью Mockito.
то, что Бугаенко не знает слова Stub и вместо этого придумывает свои термины - не удивлен
@@SergeyNemchinskiy Его основная идея заключается в том, что эти заглушки должны быть написаны в java-файле предоставляемого интерфейса. Мол это позволит пользователям вашей библиотеки не писать заглушки самостоятельно, что уменьшит дублирование кода на планете))
А так мне и самому Егор Бугаенко не нравится, слишком уж он хайпожористый
Понравился пример про квадрат и прямоугольник. Кто-кого расширяет.
Я вас обожаю. Вы один из самых интересных и умных людей, которых я знаю) спасибо за знания и ваш юмор ☺️
Про TDD интересно, запишите пож-ста
На примере mock, можно ведь для неиспользуемых методов оставить имплементацию парент класса, если это, конечно, не абстрактый класс или мы не имплеменим интерфейс. Хотя тут уже тогда нарушение как раз второго принципа-необходимо использовать интерфейс, или я что-то упустил?)
Я просто офигеваю от того, какой Сергей потрясающий интеллектуал. Мне страшно смотреть его видео. Страшно от того, что голова может лопнуть от обилия деталей, но потом трясущейся рукой тянусь и нажимаю кнопку начала просмотра, а потом инфа кое-как укладывается под отличную и ровную мелодию под методичный голос Сергея…
P.S. короче, как тупой я понял принцип LSP главным образом в том, что не должен наследуемый класс делать больше, чем его основной класс.
Мое дБ должна быть сделана с помощью мок апи, например mockito, jmock, easymock, etc Не надо самим эти сроки писать, ТК их количество будет рочти постоянно. Эти сроки тоже могут баги иметь. И тоже надо тестить)))
спасибо за видео!
Это вообще из другой оперы, но я бы послушал про DDD domain driven design, что такое bounded context и как его правильно определять
Юнит-тесты очень интересны. По сути, я самоучка, и до юнит-тестов не дошёл, а начинать страшно
Такая же ситуация, но успокаиваю себя тем что джуну вроде как не обязательно наличие в стеке этой технологии и в будущем я смогу уже на реальном проекте это изучить
Ничего там нет страшного
@@YoungT18 Mockito и JUnit обязательно нужно знать джуну, также будет в разы круче, если код, например, в пет-проектах будет протестирован, потому что не протестированный код = нерабочий код
Что сложного, вызвал метод, передал тестов данные, и сравнял с заранее верным ответом. Это примитив, но смысл таков. Модульные тесты сложнее, но тоже эмалируешь роботу кода и сравниваешь с подготовленным ответом.
Ничего страшного совершенно, с ними другая проблема, что их ленятся писать. В Java все юнит тесты: @Before, @Test, assert(Equals/That/...)
Если прочитать как это работает, то... Это в целом всё. А прочитать можно в первой же статье в гугле по запросу "Java UnitTest"
Давайте про TTD и я не понял пример с квадратом, в итоге нельзя квадрату наследоваться от прямоугольника получается или что делать ?)
в итоге надо наследовать наоборот
@@SergeyNemchinskiy стоило сказать об этом в самом видео
На счет возврата налл из метода - очень интересно ваше мнение
Чем вам неугодил Актив Рекорд и какие вы видете альтернативы (все так работать с объектом который можно собрать из кусков и сохранить достатолчно удобно)
Нарушает srp со всеми описанными в предыдущих видео последствиями
@@FrolOFF100 Да понятно, что нарушение, но оно всеобщее (для пыховых фрэймов во всяком случае) потому и интересно какую альтернативу Сергей предложит
Ну вот отнаследовался я от Rect, сделал Square. На свойство b забил, переопределил метод getSquare, где работаю только с a. Ещё переопределил геттер/сеттер b, чтобы он работал с a вместо b. Работать будет, и даже тесты не рухнут скорее всего. Но плохо то, что есть лишнее свойство b, плохо то, что неочевидно работают геттеры/сеттеры b. Принцип именно в том, что не должно быть лишних свойств и неочевидного поведения?
Принцип в том, что если подсунуть Square (в вашем случае) вместо Rect, то поведение не должно измениться. А оно изменится, причём с нарушением здравого смысла. Это и есть нарушение принципа LSP.
А что является клиентским кодом в Unity?
Можно делать ООП и без наследования, через реализацию интерфейсов
Влад Солнцев так через интерфейсы, минуя наследование, полиморфизм и делается. Главные парадигмы инкапсуляция и полиморфизм. Конечно можно с натяжкой сказать, что реализация интерфейсов это тоже наследование по сути...
@@DeMoniaqGRANI А можно и без натяжки, поскольку это по сути наследование от full abstract класса))
Можно ещё про DRY, KISS, YAGNI
уже в планах
@@SergeyNemchinskiy Смотрю Вас из интереса уже наверное больше года, когда ещё начинал знакомиться с Java, сейчас вообще Python разработчик. Так вот качество видосов стало на порядок выше, видно, что развиваетесь. Так держать! Пожалуйста, подумайте по поводу маркерной доски, а то эти шуршания маркера по бумаге некоторых приводят в ужас (меня например).
Ну как тут не подписаться ну никак и лайк поствлю и все что угодно
Будет интересно услышать про GRASP и GoF
TDD - очень интересно!!!
так выход какой (чтобы принцип не нарушать) при наследовании - нужно переопределять все методы парента?
Выход - делать наследование там, где это действительно наследование, в остальных случаях - композицию.
супер, спасибо!
Раскрыта только одна сторона медали...
Еще нужно рассмотреть вариант, что у нас есть абстрактный класс / интерфейс и фиг пойми сколько наследников
Как правильно доработать существующий метод (уже опубликованный), чтобы ни один из наследников не нарушил L из SOLID
Это очень холиварное заявление по поводу наследования...Ваши предки накидали г..на в больших системах, что невозможно работать :)
Magento - PHP решение для сложных магазинов с соблюдением всех возможных подходов из Java об OOP
И Magento 2 решили отказаться от наследования, получив при этом очень расширяемую систему и ни разу не потеряв в OOP шности...
Т.е. контроллеры (и прочие инфрастуктуры) не наследуют базовый класс, а реализуют интерфейс.
Magento 1 с возможностью строить цепочки по 10-20 наследников это больно сильно
Допустим PHP это не ЯП, а магазины это слишком легко для Java разработчика...
Но в Красной книге по DDD есть отдельная глава, где говорится, что архитектура может быть на базе наследования и моделей с состояниями или на базе сервисов без состояния и анемичными моделями...и то и то OOP подход основанный на различном фундаменте...со своими плюсами и минусами
За видео спасибо :)
Вот интересно, а общая логика/алгоритмы где тогда лежат, в ютилити методах?
В vendor/magneto/framework
В интерфейсах, например
DirectoryListInterface
Filesystem/DriverInterface
и так далее
Сама Magento после установки содержит 45 000 файлов из которых интерфейсов будет более 1000
Подключаете в конструктор тот интерфейс который нужен для логики и используете...функций нет, статических методов нет...protected свойств и методов тоже практически нет
@@grommaks ну просто планета Шелезяка. Вообще ни чего нет. )))
@@woodzimierz9621 аххха)))
11:20 оуоуоу, палехчи, если мы не используем наследование, мы используем композицию. Народная программистская мудрость: "Зачем нужно наследование, если есть композиция". Любой более-менее опытный программист, пишущий на одном из ООП языков скажет, что если есть возможность выбрать между наследованием и композицией, то однозначный выбор - композиция, а преимущества наследования можно получить при помощи интерфейсов
ох, распространенное убеждение. Сниму видео
@@SergeyNemchinskiy к слову в Go отказались полностью от наследования и предпочли именно вариант с интерфейсами и композицией. Отсутствие наследования не особо больно бьет, жить можно вполне (да Go не ООП язык, но структуры и функции, принадлежащие структурам вполне позволяют реализовывать ООПшные практики)
Можете расказать в следующем видео о проекты спринга