Спасибо большое за лекцию! Это тема чего -то напоминает Information Expert из Grasp принципов))
@anryzhov4 жыл бұрын
Тимур есть еще твои лекции по принципам SOLID?
@dimitro.cardellini4 жыл бұрын
Не хватает еще одного кейса. Кстати, очень важного для ООП. type T = A|B|C; типы A, B, и C являются подтипами T (т.к. множество значений в рамках каждого из них, явяляется подмножеством значений в рамках T, что следует из объявления N), но не являются его наследниками. например, мы можем сделать функцию, которая будет принимать: массив, объект или строку в качестве параметра, и потом в зависимости от того, что пришло, будет производить действия. Этот кейс на самом деле также покрывается L-принципом. И думаю, имеет смысл показать, как это правильно разрулить в коде. Реальный пример: методы объекта application из Express.JS (там, по-моему, массив, строка и, кажется RegExp)
@test_bot55414 жыл бұрын
"...и потом в зависимости от того, что пришло, будет производить действия... " - ну, эта фраза противоречит Лиске, т. к. функции должно быть плевать на фактический тип того, что пришло, главное, чтоб аргумент соответствовал контракту супертипа. Т. е. ф-ция пограммируется на уровне этого базового контракта, без разводки на подтипы, которые его реализуют. В ООП это восходящее преобразование. Наверное вопрос в том, попадает ли под Лиски утиная типизация. Если есть, скажем, независимая ф-ция join, куда можно передовать массив или псевдомассив (объект, поля котрого это индексы: "0", "1" ... и есть св-во length), а на выходе получить строку с разделителем. Массив и псевдомассив имеют общего предка Object.prototype, который к делу не относится, но зато реализуют 1 и тот же контракт (по индексам и длине), так что внутренней реализации join не важно что это на самом деле - массив или объект, главное, что б оно выглядело как "утка". Фактически этот нигде не специфицированный контракт и есть супертип аргумента.
@dimitro.cardellini4 жыл бұрын
@@test_bot5541 ну речь идёт именно о тех случаях, когда аргумент может принимать значения типов, не имеющих общего предка, реализующего нужный интерфейс. Если взять пример с express, то по-хорошему, нам нужен предикат, который скажет, удовлетворяет ли путь некоторому условию. Но, в качестве аргументов передаются совсем разные типы. Лиски, как раз и говорит: не пишите такой код, когда надо разруливать тип аргумента внутри алгоритма. И в коде правильнее было бы разрулить созданием мапперов /кастеров/ разных типов /не из одной иерархии/ в один "основной тип" (в C++ часто используют перегрузку конструктора). В примере, надо было бы сделать мапперы строк и регулярное, а также их массивов в предикаты и вызывать уже методы монтирующие обработчики на конкретные пути с результатами этих мапперов. Но, такой кастинг "шумит" в коде ... Поэтому, имеет смысл сделать одну функцию, которая реализует алгоритм и принимает аргумент "базового типа", вторую, которая делает преобразование типов, и третью -- котооая будет использовать две первых. Т.е. уйти от использования подтипов в ключевом алгоритме, как таковом -- чтобы соответствовать принципу Лисков на уровне алгоритма, и следуя принципу единственной ответственности правильно распределить логику между функциями (или классами). Ну и да СОЛИД -- не только про ООП на классах ...
@ericraudy4 жыл бұрын
Надо же, я прямо сейчас нечто подобное изобретал по работе, не зная что это так называется)))
@TimurShemsedinov4 жыл бұрын
По поводу принципа или его конкретного применения?
@dimitro.cardellini4 жыл бұрын
18:16 Реализация Cancelable весьма условная. Во-первых, отмена не отработает на реджекте. Во-вторых, отмена отработает только после резолва основного исполнителя, что мягко говоря не ожидается от "отмены". Вот исправленная реализация с примером использования. gist.github.com/DScheglov/8ad1a59850a23f32751487f9e1078135 Но, проблема заключается в том, что так вообще нельзя делать потому, что такие промисы чейниться не будут. Пример ниже показывает, что так работать не будет: gist.github.com/DScheglov/8ad1a59850a23f32751487f9e1078135#gistcomment-3092598
@user-yu7gm4df3f3 жыл бұрын
там в сет таймауте нужно передать через лямбду ()=>{resolve('something')}
@ArMANIAK6664 жыл бұрын
Тимур, а почему на примере юзерАккаунта Вы говорите, что принцип подстановки ломается? Ведь код его употребляет и работает. Другое дело, что нет видимого эффекта, но это уже другая история
@dimitro.cardellini4 жыл бұрын
там меняется контракт для метода withdraw. Определим свойство q(x) так: если для экземпляра x вызвать метод withdraw, передав в него отрицательное число, то баланс x увеличиться на модуль переданного отрицательного числа. Очевидно, что для q(new Account()) = true, а вот q(new UserAccount()) = false. Поэтому можно говорить, о нарушении L-принципа.
@user-bo4qo1vz1j4 жыл бұрын
@@dimitro.cardellini выходит если я передаю в функцию наследника и получаю результат, отличный от результата вызова с родителем, то я автоматически нарушил принцип подстановки?
@dimitro.cardellini4 жыл бұрын
@@user-bo4qo1vz1j не совсем ) Какой бы тогда был смысл в переопределении (override).. Весь СОЛИД -- это весьма абстрактная штука, а Лисков -- особенно )) Смысл в том, что нарушение касается контракта базового типа. Если алгоритм, который использует некий базовый тип ожидает одно поведение, а получит другое, то результат алгоритма станет не предсказуемым ... Иными словами, если родительский тип пропускает операцию с отрицательным балансом, а наследник будет выбрасывать исключение, то ... Придется менять алгоритм, добавляя в него перехват исключения и его корректную обработку. И наоборот, все может пойти не так, если алгоритм ждём исключения, а метод наследника вернёт объект с признаком некорректности, к-й понятен только ему ... В общем, очень многое зависит от контракта
@johnins16462 жыл бұрын
@@dimitro.cardellini Я что-то тоже понять не могу лсп это про соблюдение контрактов или про то, чтобы не ломалась логика?
@dimitro.cardellini2 жыл бұрын
@@johnins1646 ну, власне, дотримання контракту -- це необхідна умова для того, щоб логіка не ломалась. Необхідна, але не достатня. Але є різні рівні формалізації контракту. Тобто формальний контракт може покривати не всю логіку, а лише окремі частини. Тому дивіться на LSP з математичної точки зору: Якщо є будь-яка суттєва з точки зору логіки умова, що виконується для типу (базового класу, як окремий випадок), але не виконується для підтипу (клас-спадкоємець), то для підтипу LSP порушено, що має бути виправлено.
@romanilienko23574 жыл бұрын
Правильно «Барбары Стрейзенд»
@dimitro.cardellini4 жыл бұрын
16:30 -- там оговорка в описании того, что происходит в методе. В UserAccount добавляется "защита от дурака", которая не позволяет увеличивать баланс с помощью функции withdrow, передавая отрицательные элементы. Здесь действительно ломается L-принцип, потому что нарушается контракт для этого метода. Решение: перенос этой проверки в базовый тип.
@whatthepeople3 жыл бұрын
А если у UserAccount, например, действительно не должно быть такой возможности, т.е. уходить в минус нельзя (допустим, что мы расширим этот метод до проверки amount < value, чтобы обычный "пользователь" не смог зайти в минус), а для какого-нибудь AdminAccount мы такую логику оставляем, т.е. администратор может уйти в минус. Я правильно понимаю, что для этого мы должны создать ещё два класса, например, PremiumAccount и UsualAccount, которые насследуются от Account и накладывают или снимают ограничения, а уже от них будут AdminAccount и UserAccount?
@dimitro.cardellini3 жыл бұрын
@@whatthepeople 1. в примере кода нет проверки на то, можно уходить в минус или нет (и что узвучил Тимур), там есть проверка можно ли списывать отрицательные суммы (т.е. вместо списания пополнять счет) или нет. 2. LSP гласит, что все (и каждое) свойство, присущее базовому классу, должно быть присуще и всем его потомкам. Т.е. у нас есть свойство: "Можно выполнять списание, если сумма операции отрицательная". Это свойство выполняется для базового класса, но не выполняется для потомка. Это означает, что LSP в потомке нарушен. Предложенное Вами решение, с промежуточными классами, разделяет иерархию счетов на две под-иерархии, в каждой из которых LSP соблюдаться будет, но на уровне всей иерархии -- нет. Это означает, что в коде также придется разделить иерархии счетов -- и счета из одной иерархии не могут использоваться в качестве счетов другой иерархии. Если это оправдано бизнес-требованиями -- то, такое решение вполне уместно. 3. Вместе с тем, предложенное Вами решение с классами "PremiumAccount и UsualAccount" скорее всего просто усложнит поддержку кода. Я бы посоветовал, до того, как анализировать LSP, посмотреть на требования SRP к решению Вашей задачи (и здесь не так важно, какую именно проверку надо добавить: неотрицательность баланса или неотрицательность суммы операции). Предоположим, что за баланс счетов у нас отвечает бухгалтерия, за логику "не может уходить в минус" -- менеджер банковского продукта. И получается, что проверку неотрицательности баланса после операции надо реализовать в каком-то другом классе, например: UserAccountContract, который и должен отвечать за продуктовые правила ведения счета. Если же предположить, что у нас за все отвечает только одна бухгалтерия, то в контракт базового класса необходимо заложить возможность выбрасывать исключение или возвращать ошибку (см. монада either), в случае если по каким-то причинам нельзя выполнить списание. И даже, если реализация в базовом классе никогда такие исключения бросать (возвращать ошибки) не будет, то потомки этого класса уже будут иметь такую возможность, следуя при этом LSP. Собственно, самым правильным решением будет -- правильно определить контракт базового класса, и правильно разделить ответственность за принятие решения о возможности выполнять операции.
@QuAzI_NODE2 жыл бұрын
@@dimitro.cardellini большое человеческое спасибо! Наконец-то хоть кто-то нормально объяснил.
@dimitro.cardellini2 жыл бұрын
@@QuAzI_NODE ) Не при Тимуре будет сказано, но по ООП лучше смотреть Дмитрия Афанасьева: kzfaq.info Он, к сожалению, пхп-шник, но рассказывает понятно и для js-ников. Вот реальное лучший видео-контент на русском языке по SOLID и Design Pattern-ам. Джавистов лучше не смотреть -- там очень мало контента, который можно рассматривать вне контекста явы ...
@maksymshyshkov27874 жыл бұрын
Можно вопрос? А зачем "Нормальный" программист при создании наследника не прочитав Интрефейс прямо такие "ломает" его поведение? Принцип Барбары Лисков это ПРИНЦИП ПРОВЕРКИ КРИВОРУКОСТИ? ))
@johnins16462 жыл бұрын
Ничего не понимаю. ЛПС это про типы иди про логику? Во всех примерах кроме 4 приводится примеры в разрезе типов(контрактов), а в 4 приводится пример в разрезе бизнес логики. Где логика???
@TimurSevimli Жыл бұрын
Так все понятно.. Вкратце от типов подразумевается все что удовлетворяет контракту и не меняет поведение. Структура должна быть таким что бы очевидным образом получали ожидаемый эффект без логических проверок, вот и вся логика))