Как стать автором
Обновить

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

Абстракции НЕ должны зависеть от деталей. Детали должны зависеть от абстракций.

И еще. Создали мы PaymentAccount расширив его новым методом, но если мы его заменим на родительский Account у нас же всё сломается потому что в родительском нет нового метода.

Возможно упускаю что то в контексте комментария, но добавление нового метода в PaymentAccount не должно ничего сломать: методы которые ожидают Account не будут пытаться вызвать новый метод, там где ожидается PaymentAccount не удастся подставить Account без приведения к PaymentAccount

Тогда стоило бы переформулировать.

Классы-наследники могут быть заменены на родительские классы, при этом работа программы не должна измениться.

Потому как в комментарии говорится ровно об обратном: замена родителя на наследника.

Спасибо за комментарий. Исправил. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. Со вторым замечанием тоже согласен.

Принцип разделения интерфейсов прямо совсем очень плохо.

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

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

Там почти везде так.
"Принцип открытости-закрытости" говорит о расширении классов, но в в какой-то момент подменяется реализацией интерфейсов, но на самом-то деле другое и решается иные задачи.
"принцип подстановки Барбары Лисков" требует реализации кучи всего лишнего в предках, рушит саму идею расширения классов и в практически невозможен в сколько-либо сложные программах.
Да, даже такой красивый "принцип единственной ответственности" достижим только в достаточно простых с архитектурой точки зрения программах. Всё красиво на бэке, когда нужно тупо дата-классы туда-сюда гонять и мы описываем взаимосвязи через метаинформацию и всякие декораторы с атрибутами. А как только пытаемся это всё получить на "настоящих" программах, даже при на мобильной разработке", то вся красивая концепция рушится в прах, так как есть высокоуровневые сущности, которые управляют и владеют большим количеством классов с разным функционалом. И вообще, в конце-концов любая объектная программа представляет собой дерево, которое сходится к буквально нескольким классам, которые и представляют собой программу и потому никакая "единственная ответственность" даже теоретически не достижима.

Вульгарное (не оскорбление) у вас восприятие всех перечисленных принципов. Конечно же SRP достижим даже в условиях, когда фреймворк диктует агрегацию функционала в нескольких классах. В больших проектах для соблюдения SOLID зачастую происходит абстрагирование от фреймворка на любых платформах. Когда же вы абстрагировались от фреймворка, то можете соблюдать любые принципы, в том числе и SOLID.

OCP - вообще не про расширение классов, а про расширение функционала без изменения уже ранее написанного. Могу дать самые очевидные отсылки: декорация, проксирование, адаптирование, Chain of Responsibility, композиция и прочее, прочее...

LS - опять же про полиморфизм и контракты, которые ничего не рушат. Та же самая композиция зачастую только выигрывает от данного принципа. Если вам зачем-то нужно реализовать кучу всего лишнего в предках, то у вас нарушения архитектуры и SRP. Добавьте сюда еще и ISP поймете, что, например, те же репозитории (шаблон проектирования) могут реализовывать методы нескольких интерфейсов не нарушая SRP, но клиентский код получает этот функционал посредством небольших интерфейсов, в которых есть только нужный в конкретном месте применения функционал. Противоречия нет вообще.

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

И вообще, в конце‑концов любая объектная программа представляет собой дерево, которое сходится к буквально нескольким классам, которые и представляют собой программу и потому никакая «единственная ответственность» даже теоретически не достижима.

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

большая программа - никак не дерево, а набор слоев абстракций

И да и нет.

Если смотреть на дерево классов, то да. Программа не дерево. А если смотреть на дерево объектов, то дерево.

Пример. Что Borland C++ Builder, что Qt имеют примерно следующий код:

int main(int argc, char *argv[]) {
  QApplication app(argc, argv);
  QWidget window;
  window.show();
  return app.exec();
}

При этом app владеет window. window в свою очередь владеет объектами-контролами (кнопки, списки, ...). Вполне себе дерево объектов.

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

Разве не наоборот? Очевидно, что я не могу где угодно наследника заменить на родителя. Иначе я могу в любой метод передать объект

Абстракции должны зависеть от деталей. Детали должны зависеть от абстракций.

Найдите ошибку

Найдите ошибку

наследует от

Продайте ручку

а это наследует от

Сходи туда не знаю куда, принеси то не знаю что

И вот у царского суперкласса, полностью абстрактного, нет деталей: ни объекта "ручка", ни параметров ошибки.

конечно наоборот
if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program

Простой пример в одну строку для объяснения буковки "D". Класс и интерфейс известен всем, кто знаком с Java Core, поэтому не надо голову забивать придуманной предметной областью и сосредоточить внимание обучаемого на принципе.

List<Object> objects = new ArrayList<>();

Это Наследование ООП. Чтобы "продемонстрировать" D, нужно именно сочинать и выдумывать пример с предметной областью.

О, наконец то свежая тема!

Опять принцип Лисков перевернули с ног на голову и ради этого в примере сделали дополнительный класс для наследования.

Класс PaymentAccount либо должен быть контрактом (абстрактным классом или вообще интерфейсом), либо сразу содержать реализацию, но тогда он нарушит все те объяснения, что даны в статье - заменить его на Account не выйдет из-за проблем с методами. Потому добавили еще один класс который наследуется от PaymentAccount и тем самым защищает от логической ошибки в тексте. Потому, как верно было замечено в комментариях, последовательность там все таки иная - мы можем заменить Account на любую из реализаций и логика не должна сломаться.

Если говорить цитатами, то:
Let f(x) be a property provable about objects x of type T. Then f(y) should be true for objects y of type S where S is a subtype of T.

Что в переводе говорит нам, что функция f остается корректной, если вызов f(x) заменить на f(y) где y подтип x

Спасибо, то что нужно.

Спасибо за комментарии. Исправил принцип подстановки Барбары Лисков: с "Классы-наследники могут быть заменены на родительские классы, при этом работа программы не должна измениться." на "Должна быть возможность вместо базового (родительского) типа (класса) подставить любой его подтип (класс-наследник), при этом работа программы не должна измениться." Также исправил по тексту статьи.

Пингвин - доработанный слон с методом "плыть"

Принцип единственной ответственности на показанном примере не принимаю. Был один сервис, через который можно было получать информацию. Сделали четыре одноабзацных класса, имена которых надо ещё держать в уме. Был бы там исходный класс на 10к строк - можно было разбить, и то думать надо кто будет там хранить общий код, например параметры подключения к БД.

Open closed Principle вредный! Его следует избегать. Во всяком случае не следовать слепо во всех случаях. И ваш пример с отправкой сообщений это иллюстрирует: после модификации в соответствии с принципом код стал сложнее, и при этом ни на грамм не став надежнее или чем-то лучше. Пустая потеря времени.

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

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

При появлении какого-то аспекта, грани, требования к сервису (в данном случае), необходимо менять ВСЕ классы, на которые мы раздербанили наш сервис (ради упрощения!), одинаковым образом, согласовано и единообразно.

Правильно же было бы делать так. Если мы задумали сервис, который имел бы набор своих данных и набор методов, то и инкапсулировать надо ВСЕ методы сервиса в этот класс сервиса. Но не более того.

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

И прошу не переходить на личности с криками "ты ничего не понял в солиде!".

Еще раз. СОЛИД противоречит принципам ООП (сами ООП только расширяют принципы процедурного программирования), поэтому она должна порицаться и быть исключенной из практики.

Добавлю.

"Принцип" Барбары Лисков (кто это вообще?) ворует второй основной принцип ООП - наследование. https://ru.wikipedia.org/wiki/Наследование_(программирование). Причем автор статьи даже привел этот принцип, не понимая его сути, похоже, судя по ошибкам. Если вы создаете какую-то систему, развивая и обогащая ООП (А СОЛИД работает на базе ООП, и соответственно, обязан развивать его), то ваши "принципы" не должны повторять (и тем более, повторять, искажая) принципы базовой идеологии.

Это, если мы делаем ОДИН сервис.

В соответствии с принципами надо делать много сервисов - по одному на каждую сущность - машина, коллекция машин, счет, сообщение и т.п. И вот уже все, связанное с конкретной сущностью - инкапсулировать.

Я не понял первый пример. У нас был метод, который в зависимости от типа машины выводит о ней информацию. Мы решили, что это плохо, потому что этот метод однажды придётся поменять, поэтому написали класс, в котором есть этот же самый метод. Но вседь его всё равно однажды придётся поменять, зато у нас появилась обёртка в виде класса с единственным методом. А внутри веток if в этом методе тоже лежат разные классы с одним методом, из которых один знает, как отдавать информацию для sedan, а другой — для pickup?

В чём тут состоит улучшение? Пока видно только удлинение цепочки, которую нужно пройти, чтобы дописать обработку ещё одного типа машин.

А-а, я проглядел, что теперь первый класс не содержит этих методов. То есть, претензия была не к тому, что метод однажды придётся изменить, а к тому, что класс по аренде машин зачем-то умеет искать машины? Зачем тогда нужна приписка "тогда придется изменять и дописывать данный метод"? В любом случае придётся что-то дописывать, на то оно и добавление функциональности. Если это куда-то и подходит, то под принцип O, а не под принцип S.

Во втором принципе, про SMS мы не хотим писать ещё один if, чтобы не менять код класса. Но мы ведь всё равно его где-то напишем, этого никак не избежать. Просто это будет где-то повыше, где этот класс используется. Типа if (isОтправитьПоSMS) { new SMSsender() }. Не получится добавить новый способ отправки только создавая новые классы и не меняя старый код.

Блин, ну первое предложение, а тут "превдокода".

Спасибо за комментарий. Исправил.

С принципом подстановки - не очень удачный пример, кмк.

Мы вполне можем не выполнять какую-то операцию. Можем же мы выдать исключение, если будет указана неправильная сумма или неправильный счет получателя. Вот если мы явно запретим исключения в предке.

Принцип вообще не про разных потомков, а про предка и потомка. Он нарушается, если мы в операции потомка делаем что-то отличающееся от операции предка.

Классический пример - наследрование квадрата от прямоугольника, когда в квадрате установка длины меняет и ширину (и наоборот). Тесты предка не будут выполняться для потомка.

мда всё свелось к тому, чтобы надробить побольше абстрактных классов и поставить себе ачивку.

А потом выпустить статью "Как мы избавились от 80% своего кода, повысив скорость разработки и уменьшив количество ошибок" https://habr.com/ru/company/mvideo/blog/599401/ и поставить еще одну ачивку.

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

НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории