Comments 329
Я — функциональщик. Прекрасно себя чувствую. Это вы от объектов такой раздражительный.
Помнится недавно в обсуждении ФП, один из его адептов отказывался признавать LISP в качестве ФП (язык с которого ФП и началось), за то что в новых его версиях была добавлена поддержка ООП.
PS поскольку речь зашла о связи Windows и ООП, то интересно как работают без ООП интерфейсы у ФП программ написанных без ООП?
поскольку речь зашла о связи Windows и ООП, то интересно как работают без ООП интерфейсы у ФП программ написанных без ООП?Я про пользовательский интерфейс программы.
а вот Windows уже требовал именно ООП чтобы писать что-то неконсольное
Чего? Я ни разу не умею это ваше (С) ООП, пишу гуевые программки и игрушки под Винду на чистых сях/хаскеле/чем угодно — что я делаю не так? ВинАПИ сишное на все сто, куда и зачем мне объекты пихать?
В JS невозможно реализовать сколь-нибудь полезную чистую функцию. О каком ФП тут можно говорить?
Хех, так Лисп — это не функциональный язык. Лисп поддерживает ФП-парадигму, но не заставляет ей следовать. Вы можете писать на Лиспе в стиле раннего Си с глобальными переменными.
Извините, я думал вы программист с продакшена, а не сферический в вакууме. Можете не читать.
Поддерживать ООП-код, когда у тебя 100500 классов и чтобы добавить хоть какую-то доп-фичу нужно перелопачивать 100500 классов и интерфейсов, это ад.
Как уже неоднократно сказано в комментариях, это проблема не ООП, а плохой архитектуры — которую можно на чем угодно нафигачить.
ООП требует очень много лишней работы для очень простых вещей.
Например?
И когда архитектура разрастается, то поддерживать и расширять её становится настолько сложно, что она превращается в «плохую архитектуру».
Так, а альтернативные предложения?
Давайте вы сами (да и отписавшиеся здесь) не будете себя обманывать. Вы никогда не писали архитектуру.
Оу да, конечно, вам виднее, что я (и остальные отписавшиеся) писал.
Я же говорю, вам явно виднее, кто что писал. Зато как конкретные вопросы — так никаких ответов. Мимими.
Ну и да, признаюсь, я правда никогда не испытывал проблем с кэшом и прозводительностью из-за использования ООП. Разное было, и хреновый I/O (пятикратная просадка), и идиотская ошибка в распаковке base64 (шестидесятикратная просадка), и всякие другие вещи — а вот ООП как-то не мешало пока еще.
мм… А вы вообще в курсе, что ООП-программы компилируются в тот же ассемблер, что и всё остальное?
Десяток уровней абстракции, сотня классов в предметной области где высокая связность всего и вся. И получаем дикую потерю в скорости. Но!
1 — без ООП мы бы еще схемы на доске рисовали, а не проблемы щупали.
2 — Задача обычно решается так или иначе кешированием, или некоторым нарушением изящной структуры.
3 — даже если задача действительно плохо пригодна для эффективной работы в ООП-парадигме (например в конечном итоге у нас оказывается 80% пятиэтажных join «в узких местах», которые занимают 80%, то всё равно гораздо проще и эффективнее писать это всё поверх уже работающей пусть и неэффективной структуры, чем «в вакууме».
4 — реально задач таких почти и нет. В голову сейчас приходит только один пример, да и то не у меня был — там предметная область была — научные исследования в области генетики. «Божественный код» это классическая лапша, поскольку о структуре там никто не думал, так что слабо детерминируется, и анализ его аналогично вызывает вопросы. Тут нельзя отрефакторить предметную область :)
Резюмирую — ООП действительно имеет проблему с производительностью, но это цена за более быструю и понятную разработку. К тому-же как правило в прямых руках цена незначительна.
Ну включите уже наконец моск! Люди неспроста придумали микросервисы, сервисные шины и оркестровку!!! Все кто солидарен со статьей здесь говорят ООП подразумевая, что это определяет архитектуру системы… да как так?! Вы сколько лет в этом бизнесе народ?!
Микросервисы, сервисные шины и оркестровка — это все как раз быстренько убивает производительность в ее первичном смысле, потому что это все — распределенные системы. Другое дело, что после этого мы можем заниматься масштабированием и всем таким, но это уже принципиально другой уровень сложности.
И мы опять возвращаемся к тому же самому. Что ООП != архитектура. Вы повторяете ту же ошибку, что и lookid и JustMoose выше. Делаете однозначные заявление о технологии в принципе, не учитывая детали ее применения.
Класс который связан с тяжелыми вычислениями, если один-единственный на микросервис или же вплетен в иерархию классов монолитного приложения. Как оценить его производительность абстрагируясь от архитектуры, в которой он живет? От методов, в которых он используется?
И вообще, мы как то ушли от основной темы. В общем я к чему — в целом вообще неважно ООП у тебя или ФП. Накосячить можно и так, и так.
Да, поскольку часто выделение микросервов сокращаит оверхед.
Нет, выделение микросервисов не сокращает оверхед. Выделение микросервисов добавляет оверхед, который потом может компенсироваться тем или иным выигрышем (например, за счет асинхронности/параллелизма).
в целом вообще неважно ООП у тебя или ФП. Накосячить можно и так, и так.
Можно подумать, с этим кто-то спорил.
Голословное утверждение
Что именно голословное утверждение? Что распределенная система добавляет оверхед?
повышение или снижение производительности напрямую зависит от задачи
Не от задачи, а от архитектуры и контекста.
Вы тут совсем не в тему сказали.
Тут согласен. А дальше ересь пошла.
Пытаемся по немногу рефакторить сего монстра. Встаивание функционала по ТЗ (т.н. «улучшения») в код, построенный на ФП происходит раза в четыре быстрее, чем в унаследованный код. Львиную долю времени приходится отслеживать связи, чтобы понять причину падения при успешной сборке.
Да-да, архитектура, конечно, но она – наипрямейшее следствие злоупотребления ООП! И адепты ООП могут сколь угодно с пеной у рта рассказывать про «руки из жопы», но мы на этом теряем время, а потому активно выпиливаем из проекта этот зООПарк, ибо он вредит процессу.
Товарищ сделал плохую архитектуру и жалуется на парадигму. Ну-ну. При этом он еще и не понимает, что главное в ООП — это именно интерфейсы и есть, а наследование вообще необязательно.
Посмотрим, как через пару лет он напишет то же самое про ФП :)
Плохая архитектура это их конек.
П.С.
ООП использую в меру, без фанатизма.
«Actually I made up the term „object-oriented“, and I can tell you I did not have C++ in mind.» — Alan Kay
GoF 20 лет назад разжевали, что наследование использовать не надо, и как именно не надо.
Но лучше, наверное, подождать ответа areht-а.
Design Patterns: Elements of Reusable Object-Oriented Software. Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.
Приёмы объектно-ориентированного проектирования. Паттерны проектирования. Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидс
pdf тоже гуглиться несложно, на разных языках
Главное в Объектно Ориентированном Программировании это, как несложно догадаться, объекты. Объект это такая штука, которая имеет поведение и внутреннее состояние. Интерфейсы в общем — то это поведение и есть, но лучше раскрывать мысль полностью.
Что любопытно, конкретная реализация интерфейсов например в Java — плохая. Использовать для интерфейсов отдельное ключевое слово implements — плохо. Но это, конечно, не меняет того факта, что поведение со скрытым состоянием в ООП — главное.
Мне жаль, что давным давно я использовал термин «объект» для этой темы, потому что из-за этого многие люди фокусируются на меньшей из идей. Большая идея это «сообщения» Ключ к созданию хороших масштабируемых систем это проработка механизмов общения модулей, а не проработка их внутренних свойств и поведения.
Полный текст интереснее.
Проблема повторного использования — единственная проблема в программировании
Да ладно. В зависимости от того, чьими глазами вы смотрите, либо "единственная проблема" — это управление сложностью, либо проблем две, и это именование и инвалидация кэша.
нет никакой сложности в написании линейной программы
Во-первых, это само по себе не правда. Во-вторых, только очень маленькое количество программ линейно.
ей не нужны никакие именования
Это тоже не правда. Во-первых, она сама должна быть поименована, во-вторых, используемые ей операции должны быть поименованы. Это необходимый минимум.
инвалидация кэша не относится к задачам программирования.
Почему это?
Все программы линейны.
> Это тоже не правда. Во-первых, она сама должна быть поименована, во-вторых, используемые ей операции должны быть поименованы. Это необходимый минимум.
Не должна. Не должны.
>Почему это?
Потому что, тезисы должен доказывать тот, кто их выдвигает.
Все программы линейны.
В таком случае тезис "нет никакой сложности в написании линейной программы" ложен. Потому что если все программы линейны, то этот тезис эквивалентен "нет никакой сложности в написании любой программы", а это заведомо не так.
Не должна. Не должны.
То есть используемая вами программа не имеет никакого имени, и используемые в ней операции — тоже не имеют никаких имен?
Потому что, тезисы должен доказывать тот, кто их выдвигает.
Ну вот и доказывайте тезис о том, что "инвалидация кэша не относится к задачам программирования" — вы же его выдвинули?
Только в вашей вселенной. Это ваш тезис.
> То есть используемая вами программа не имеет никакого имени, и используемые в ней операции — тоже не имеют никаких имен?
Именно так. К моей задаче программиста не входит выдумывать никакие названия. Кроме как для целей повторного использования кода.
>Ну вот и доказывайте тезис о том, что «инвалидация кэша не относится к задачам программирования» — вы же его выдвинули?
Нет, вы выдвинули тезис, что это задача программирования. Если вы уже забыли об этом, у вас действительно все плохо.
Только в вашей вселенной. Это ваш тезис.
То есть вы утверждаете, что нет никакой сложности в написании любой программы?
Именно так.
Приведите пример. Я не знаю ни одной программы, не имеющей названия.
Нет, вы выдвинули тезис, что это задача программирования.
Я его не выдвинул, я процитировал весьма известное высказывание (жаль, что вы его не знаете). Впрочем, доказывается это утверждение просто: за последнюю неделю я, как программист, дважды столкнулся с необходимостью сделать инвалидацию кэша (строго в рамках своей работы); следовательно, эти задачи входят в задачи программирования.
Именно это я и утверждаю. Естественно, если вы, опять же, не забыли про декларированную мною «единственную сложность» и целью не является выдрать фразу из контекста.
> Приведите пример. Я не знаю ни одной программы, не имеющей названия.
Опять я должен доказывать ваш тезис. Бритва Оккама плачет. Вы решили, что программе нужно название, а я должен доказывать, что Бога нет. Печаль. Но ок, вот вам программа без названия
Достать руку из кармана
Положить ее на клавиатуру
Включить мозг и перестать троллить, и так слишком много в мире нерешенных интересных задач
>Я его не выдвинул, я процитировал весьма известное высказывание (жаль, что вы его не знаете).
Жаль, но логикой тут и не пахнет. То, что вам, как программисту ставят задачи не программирования, опять же жаль, но никак не доказывает ваш тезис.
Именно это я и утверждаю.
В таком случае вся индустрия внезапно занимается не тем. Угу.
Естественно, если вы, опять же, не забыли про декларированную мною «единственную сложность»
Рекурсивные определения не ко мне.
Но ок, вот вам программа без названия
Достать руку из кармана
Положить ее на клавиатуру
Включить мозг и перестать троллить, и так слишком много в мире нерешенных интересных задач
Содержащая десяток именованных объектов. Поэтому, внезапно, задача именования все еще актуальна.
То, что вам, как программисту ставят задачи не программирования, опять же жаль, но никак не доказывает ваш тезис.
Никто не ставил мне эту задачи, они возникли как частная проблема внутри конкретной бизнес-задачи (точнее, как частное решение задачи оптимизации производительности, возникшей, в свою очередь, как выполнение "нефункционального" требования к бизнес-сценарию).
Не знаю чем занимается ваша выдуманная индустрия, моя занимается проблемой повторного использования.
> Рекурсивные определения не ко мне.
Я заметил, что у вас сложности с ведением диалога.
> Содержащая десяток именованных объектов. Поэтому, внезапно, задача именования все еще актуальна.
Именование этих объектов не относится к программированию никаким боком, если вы не заметили для написания этой безымянной программы (кстати, поздравляю, вы ее увидели первый раз в жизни), мне не пришлось выдумывать ни одного нового наименования.
> Никто не ставил мне эту задачи, они возникли как частная проблема внутри конкретной бизнес-задачи
Ура, теперь и вы поняли, что задача инвалидации кэша является задачей бизнеса, а не программирования, и ключевое решение «какой вариант выбрать» принимается на уровне бизнес-планирования. А вот уже обычную задачу «инвалидация кэша при вот таком-то выбранном бизнес-решении» можно решить и программированием.
Не знаю чем занимается ваша выдуманная индустрия, моя занимается проблемой повторного использования.
Окей, у нас с вами разные индустрии. В принципе, на этом можно и закончить, сложности вашей индустрии меня не интересуют.
PS
задача инвалидации кэша является задачей бизнеса, а не программирования, и ключевое решение «какой вариант выбрать» принимается на уровне бизнес-планирования.
А вот и нет. Бизнесу на это положить, у бизнеса есть ровно одно требование: 30+ транзакций такого типа в секунду (при такой-то загрузке).
Впрочем, повторюсь, представления вашей индустрии о программировании меня не интересуют.
Это и является тем самым бизнес-решением, что разделяет «задачу инвалидации кэша» от «задачи инвалидации кэша при таких-то условиях» и делает ее элементарной. А вот выбрать сколько должно быть этих самых транзакций в секунду — вот это действительно сложно, но к программированию отношения не имеет.
Что-то мне кажется, что вы обоими своими комментариями промахнулись с адресатом. Но я с вами согласен, если что.
А то, что некоторые люди (ох, как оказалось их много), занимаясь не только программированием, решили, что теперь все есть оно, наверное, это ЧСВ, ну, или банально ошибка. Хотя, скорее, попытка выжить, ведь уже в огромном спектре задач не нужны переводчики в виде программистов, компьютер и так понимает смысл жестов, естественных языков, звуков и т.д.
А обычно, это человек принимающий бизнес-решения на уровне IT: менеджер проекта, IT-директор и т.д. В маленьких компаниях, очень часто, человек является и менеджером проекта и программистом, отсюда и непонимание. Но ведь если он поможет один раз собрать новые стулья, к примеру, или в компании принято каждое утро убираться на территории всем, вряд ли эти занятия станут программированием, правда?
Но тогда вопрос, если кеширование — не задача программиста, то естественно, из утверждений выходит, что ему и знать это не нужно. Так выходит что ли?
А тогда встречный вопрос, если этого не нужно знать и можно даже представления не иметь о принципах, то каким образом это будет поддерживать и кто за это будет отвечать?
И вот еще, главный вопрос, каким образом в таком формате Вы собираетесь строить кеши, которые не разезжаются с базой?
По поводу «линейности» каждой программы. Это Ваше личное мнение? Линейность предполагает выполнение одной команды за другой и этот порядок ничем и никогда не нарушается (1-2 курс университета).
А как быть с определением «нелинейное программирование»? Или, по Вашему, эти программы все равно линейны?
А то, что некоторые люди (ох, как оказалось их много), занимаясь не только программированием, решили, что теперь все есть оно, наверное, это ЧСВ, ну, или банально ошибка
Наверное за столько лет расширился спект задач, существенно прибавилось технологий, существенно выросли запросы, существенно ИТ разрослось, и не только сообщество. Любое развитие предполагает развитие и если, как программист, не понимаешь, как работает на серверах то, что использует программный продукт и с чем взаимодействует, как взаимодействует устройство с кодом, каким образом собираетесь его оптимизировать? Каким образом собираетесь укладываться в наложенные ограничения? Или Вы полагаете, что каждый заказчик желать масштабироваться вертикально? Да нет, чаще экономят.
Все программы линейны.
Извините за личный вопрос, Вы случайно не закончили краткие курсы, вместо учёбы в ВУЗе?
Уж очень в Вашем сообщении сквозит невежество и отсутствие какого-либо знакомства с теорией.
А нет никакой сложности в написании линейной программы
Про "проблему остановки" вы похоже не слышали.
Все по одной простой причине, мы всегда пишем программу для конкретного исполнителя (для которого уже задан набор команд), будь то процессор или виртуальная машина.
А когда пишут псевдокод, просто в голове подразумевают ряд существующих языков (написанных для конкретного процессора) и пишут нечто, для чего можно в любой момент написать компилятор.
Именно поэтому часто бывает так, что есть основная платформа, написанная на «сложных» языках, где уже определены обобщения на большинство случаев, а над ней есть какой-нибудь скриптовый язык с простейшими возможностями: так было с javascript, так есть с 1С, Siebel, так часто используется Lua и т.д. Потому что до выкристализации обобщения нужно доказать его ценность для последующих задач. В том числе и опытным путем.
Даже спорить не хочется с очередным JavaScript- & PHP-функциональным программистом, с одной стороны находящим проблемы даже в шаблонизации HTML и тут же говорящим об отсутсвии оных в программировании вообще.
В проектах, где количество разработчиков больше одного, задача разделения кода и отображения — одна из первоочередных
Почему нельзя написать идеальную программу
Можно, ведь она основана на логике — абстрактном математическом понятии. При этом мы изначально берем за аксиому верность каких то утверждений:
Если А верно, то и Б верно, но то, что А верно — мы решили сами.
Но программы работают на конкретном оборудовании — станки, ЭВМ, самолеты. А они собраны из неидеальных деталей, а значит неидеальным способом. А значит — они сломаются даже до физического износа. В общем, об этом и так сказано достаточно много.
Поэтому наша программа из одной строчки гарантированно будет работать с ошибками.
По долгу службы приходится иметь очень много дел с JSON, и здесь система типов TypeScript не помогает ничем, даже мешает, ведь компилятор сообщает об отсутствии ошибок, JSON.parse возвращает тип Any. Кроме того, TypeScript не поддерживает рефлексию, ввиду специфики работы, а значит, нет возможности проверить тип, основываясь на уже существующем коде. Также, до последнего времени, средств для мета-программирования не было вовсе.
Сплошь линейные задачи… но какие сложные!
Формированием формальных требований и выбором конечного устройства не занимается программирование, а следовательно и программист, хотя один человек способен заниматься несколькими профессиями (например, быть и программистом, и менеджером части бизнеса и принимать такие решения).
Кодер — жаргонное понятие, обозначающее программиста, не способного создавать собственные абстракции для повторного использования.
Неспособность некоторых «заказчиков» создавать формальные требования, не делает программирование чем-то другим, зато добавляет необходимость заниматься их разработкой ДО программирования и возможно теми же людьми, что и будут программировать.
Согласен, все это путать не надо, да и в википедии (по вашей же ссылке) примерно это и описано https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%81%D1%82#.D0.A1.D0.BB.D0.BE.D0.B2.D0.BE.D1.83.D0.BF.D0.BE.D1.82.D1.80.D0.B5.D0.B1.D0.BB.D0.B5.D0.BD.D0.B8.D0.B5. А на требование «дай мне кнопку сделать все» дают код в ответ не программисты, а несуществующие персонажи из сказки «Программист — бог».
либо проблем две, и это именование и инвалидация кэша.две: именование, инвалидация кеша и ошибки на единицу.
Для того что бы получить код для повторного использования, нужно писать его с учетом дальнейшего повторного его использования.
В Go, кстати, очень элегантная реализация ООП.
Речь о величине куска с контрактом. Чем он меньше, тем меньше изменений в использующем его коде.
И при чём тут ООП?
Ровно то же справедливо для любой парадигмы, опять же. Названия меняются, суть остается.
В ООП жестко задан тип связи между методами (поведение объекта), в ФП — и эта связь является описываемой функцией. Так вот в одном модуле этот класс объекта полностью поддерживает SRP, а в другом нужна только его половина.
Мало того, каждый модуль можно рассматривать во временном промежутке. Сегодня SRP для него таков, а завтра может быть что-то изменится (тут можно спорить о качестве композиции).
И вновь напомню, что люди не одинаковые. Для одних будет SRP заключаться в этом, для других в другом.
Так вот, ФП позволяет сделать систему гибче, за счет настраиваемости связей, жестко зашитых в ООП (да, да, именно публичные, приватные, защищенные свойства, принципы наследования и т.д.). Но за эту гибкость приходится платить сложностью — мы знаем гораздо меньше заранее об одном и том же объеме кода (чем в ООП). Немногие способны компилировать в голове такой код, в отличии от, например, линейного скрипта. Поэтому детям в школе дают QBasic. Не потому, что на нем нельзя чего-то написать, а потому что на единицу объема доступный для разбора мозгом код. А кто-то способен ночью, проснувшись, прочитать чужой haskell и мгновенно понять.
Так что, выбор парадигмы напрямую зависит от ваших возможностей и потребностей, но именно в связи с совокупными возможностями ваших программистов.
Кстати, советую всем в возрасте от 35 лет (усредненно) принять свой уровень, как данность и не мучаться угрызениями совести от незнания сложных языков.
2) либо вы не понимаете, что такое SRP, либо я не распарсил
3) если у вас с возрастом проблемы, я сочувствую, но не стоит говорить за всех. Робу Пайку скоро 60, а Кену Томпсону вообще уже 73, однако модный у молодежи Go они как-то сделали и продолжают над ним работать.
В ООП уже зашиты некоторые из них (я описал выше какие), поэтому нет, там их меньше, в ФП приходится писать их вручную.
> 2) либо вы не понимаете, что такое SRP, либо я не распарсил
А фишка как раз в том и заключается, что нет формулы определения SRP, понятие завязано на семантике, которая не определена формально даже в естественных языках (а именно с помощью слов из естественных языков и даются названия обобщениям). Например, пресловутый стул, как оказалось может быть и сидением и предметов торговли, иметь и три ножки и вообще не иметь ножек и т.д. Создайте класс стул с его SRP и получите непонимание уже даже вашего соседа) Что уж говорить про интернациональные пакеты.
> 3) если у вас с возрастом проблемы, я сочувствую, но не стоит говорить за всех. Робу Пайку скоро 60, а Кену Томпсону вообще уже 73, однако модный у молодежи Go они как-то сделали и продолжают над ним работать.
Не понял, где тут логическая связь вообще. Я говорю всего лишь о том, что после определенного возраста обучение сложным вещам дается намного тяжелее. А то, что Пайк обладает уровнем для создания простого языка, не говорит о его возможности к созданию (или даже изучению) гораздо сложного, правда? В любом случае, ремарку про усреднение я оставил неспроста. Для кого-то это может быть и 60, но Пайк не тот пример.
семантике, которая не определена формально даже в естественных языках
Спасибо, долго ржал. Формальная семантика часто нормально определяется для искусственных языков, но для естественных всё совсем плохо.
Вообще, сделать простой язык — намного сложнее, чем сложный.
Остальное оставлю без комментариев, у вас просто каша в голове.
Кстати, советую всем в возрасте от 35 лет (усредненно) принять свой уровень, как данность и не мучаться угрызениями совести от незнания сложных языков.Утверждение, типичное для возраста до 25 лет или ранее.
Проблема не столько в возрасте, сколько в опыте. Освоив, например, ООП и простой язык C++, для смены их на что-то другое уже нужны достаточно веские причины, поскольку новый язык придётся осваивать заново. Даже если он отличается несильно, тонкие ощущения будут другими, т.к. некоторые конструкции, которыми привык оперировать, могут отсутствовать, а для их замены есть что-то другое.
Также приходит понимание, что языков много, и изучить каждый вновь придуманный, потому что кто-то считает, что он — лучше всех, просто не хватит времени.
Так не в этом ли и дело, что в более юном возрасте всякие разные новые языки учатся с большим удовольствием и большим стремлением, что ли?
Полно людей, севших 30 лет назад на C и так и не слезших с него.
Но раз автор этого не знает, то он далёк от понимания программирования вообще, так что всерьёз воспринимать его критику…
Но когда я пытаюсь вкрутить саморез в палец, он действительно вкручивается. Когда я бью шуруповертом по гвоздю, он забивается медленнее, а шуруповерт ломается.
Прощай, шурик! Теперь я буду пользоваться молотком. (Потом камнем)
И в каждой такой статье по сути одно и тоже уже. Скучно.
«Почему они выбирают между ООП и процедурами? Надо выбирать между ООП и HTTP.» © Popoff
Особенно забавный пример с «хрупким классом». Автор добавляет в подклассе состояние (поле count), которое по его задумке должно быть консистентно с внутренним состоянием суперкласса (число элементов в нижележащем ArrayList), и при этом почему-то полагает, что ему не нужно вникать в детали устройства суперкласса.
Если в C# ты пометил add() в базовам классе как virtual, но начинаешь его вызывать(т.е. меняется семантика) — это неявный breaking change, и свинство.
Если virtual add(int n){internalAdd(n);} — и проблемы нет.
в Java как-то не очевиднее.
Сначала:
Написал свой первый простенький скрипт на Erlang
И потом:
Впечатления от Erlang после года работы с ним
Никогда-никогда не используйте в своих проектах Си!
Почему Erlang — язык для очень специфичных задач
Так что без работы не останемся :)
class Copier: public Scanner, public Printer {
public:
using Printer::start;
}
Сама статья больше похожа на проявление архитектурной проблемы. Неоправданное применение ООП там, где можно было обойтись ФП не означает того, что ООП — зло. И то, и другое — лишь инструменты. Молотком можно и дом построить, и голову проломить — от этого молоток хуже не становится.
То есть архитектура уже должна приводить к компоновке, а не наследованию и разведению «состояний» «в разные углы». Копир состоит из принтера и сканера, связывая их дополнительными функциями, но не наследует их свойства, как может показаться на первый взгляд.
«Проблема ромба» может возникать и без множественного наследования — это проблема некоторого общего ресурса, имеющего состояние, и нескольких владельцев, желающих его использовать. Открытый файл, общая память, синхронный протокол связи, сеанс на веб-сервере. То есть, столкнуться с нею в ФП-языке так же возможно, как и в ООП-языке, не реализующем множественное наследование. Множественное наследование — лишь частный случай.
Наследование — это ничто иное как агрегация, приправленная делегированием. И копир, вполне может быть наследником их обоих.
class Copier : Printer , Scanner {
/// запускает основную функцию девайса (большая зелёная кнопка)
start(){
return super->Printer::start( super->Scanner::start() )
}
/// конкретно у копира есть дополнительная кнопка запуска отдельно принтера
print() {
return super->Printer::start()
}
/// конкретно у копира есть дополнительная кнопка запуска отдельно сканнера
scan() {
return super->Scanner::start()
}
}
Тогда все становится понятно: Copier implements PrinterInterface, ScannerInterface.
Если в обоих интерфейсах методы называются start, а не scan и print, то будет плохо, но это надо было сразу думать над неймингом.
Если же методы называются print() и scan(), как у нормальных людей, то и проблем нет.
А уж наследоваться или нет — вопрос реализации. Я предпочитаю наследоваться только от абстрактных классов, а во всех остальных случаях делегировать — так явнее и безопаснее.
Для начала нужны PrinterInterface и ScannerInterface.
Не нужны. Нужна возможность наследовать интерфейсы без реализации:
class Printer { ... }
class Scanner { ... }
class Copier implements Printer, Scanner { ... }
Если же методы называются print() и scan(), как у нормальных людей, то и проблем нет.
Если знать, где упадёшь, то можно и соломки подстелить: scanWithLaser(), printToBottomTray().
во всех остальных случаях делегировать — так явнее и безопаснее.
И копипастить проксирование каждого из 100500 методов? А толку? Указывая "extends" вы фактически явно указываете проксировать всё, что не перегружено, до предка.
scanWithLaser — как раз-таки нет, это уже детали реализации. Если кто-то наплодил интерфейсов с методами performAction или go, это уж ССЗБ. Имя метода должен отражать суть действия.
По поводу наследования все написано классиками в GoF — Favor 'object composition' over 'class inheritance'. Не вижу смысла пересказывать своими словами, все равно лучше не получится. Что касается «100500» — а зачем, а откуда? Всего два метода, это не больно. А если и правда 100500 — так боль уже в том, что их 100500, где-то забыли про SRP, типичный God Object. (И, кстати, копипастить надо не во всех языках).
Интерфейсы нужны, потому что ISP.
Объявление класса уже является объявлением интерфейса. В большинстве случаев нет логической необходимости его дублировать отдельно.
scanWithLaser — как раз-таки нет, это уже детали реализации.
Это конкретизация интерфейса. Такая же, как и print|scan или startPrinting|startScanning вместо start.
По поводу наследования все написано классиками в GoF — Favor 'object composition' over 'class inheritance'.
Ни чем не обоснованный догматизм.
Всего два метода, это не больно.
Всего два метода демонстрируют проблему ромба, а всего методов, разумеется куда больше. И, самое главное, нам, при реализации копира, совершенно не важно сколько и каких методов реализуют принтеры и сканнеры, пока их их интерфейсы не пересекаются. А вот места пересечения требуют от нас уточнения, что именно требуется.
А если и правда 100500 — так боль уже в том, что их 100500, где-то забыли про SRP, типичный God Object.
Давно вы практикуете гадание по числу методов? :-)
Все, что мы знаем — что Copier должен уметь scan() и print(). Конкретные реализации Scanner и Printer могут не подходить. Может, у них у каждого независимая очередь, а нам тут нужна одна. Может, еще что. А может, сегодня подходят, а завтра нет. С делегированием такие проблемы решаются проще и безболезненнее.
По числу методов гадать нечего, это такая же объективная метрика, как и цикломатическая сложность.
Это не догматизм, это практика. У того же Фаулера примеры из его практики.
В ваших устах это именно догматизм. А у того же Фаулера используется довольно нейтральное "предпочитайте". Я бы сформулировал проще: "не используйте наследование, если вам не нужно наследование — для повторного использования кода есть и другие техники".
Конкретные реализации Scanner и Printer могут не подходить.
Это не конкретные реализации, а базовые классы, реализующие общую для всех принтеров/сканеров функциональность.
Может, у них у каждого независимая очередь, а нам тут нужна одна.
Скорее всего там будет именно одна, унаследованная от общего предка, очередь. Но если окажется, что там две независимые, то их объединение реализуется в точности так же как и в случае с композицией. Как я уже говорил, наследование — это частный случай композиции и довольно глупо их пытаться противопоставлять.
По числу методов гадать нечего, это такая же объективная метрика, как и цикломатическая сложность.
Только имеет ещё меньше смысла. Вот у меня есть приложение ToDoMVC, полный интерфейс которого насчитывает 70 методов. Это много или мало? Если не использовать автоматическую делегацию агрегату (наследование), то конечно много — это ж чтобы добавить кнопочку в футер, нужно будет вручную повторить весь этот интерфейс.
В рассматриваемом случае наследование плохо подходит, потому что Copier — это совершенно необязательно Scanner + Printer, он может быть устроен совершенно иначе. Завязаться на реализацию можно в качестве «быстрого хака». И потом огрести. :)
Разумеется, есть случаи, когда наследование абсолютно уместно. Например, что-то вроде Postgresql94QueryBuilder extends Postgresql90QueryBuilder extends PostgresqlGenericQueryBuilder extends AnsiSqlQueryBuilder.
Если "он устроен как-то иначе", то наследование тут мимо кассы и обсуждать, собственно, нечего.
Пусть наследуется, пусть что угодно — снаружи знать про это не надо, снаружи интерфейс.
«Не для науки ради, а так, чисто позырить...»
Вам тут наследование что дало?
Реализацию обоих интерфейсов в одном объекте, очевидно.
Если у вас реализации интерфейсов не пересекаются, что дальше то? Кастим к одному из интерфейсов и получем обратно принтер, от которого наследовались?
А что вы дальше ожидаете от МФУ?
Взяли 2 интерфейса, добавили своих методов и получили третий интерфейс (один девайс, выполняющий 3 функции).
Т.е. профит в нарушении SRP?
Профит в появлении третьей функции, как результат объединение первых двух. Аналогичный случай:
InputRange + OutputRange = Channel.
> Аналогичный случай: InputRange + OutputRange = Channel.
А это с наследованием, что бы Channel к RangeBase было интереснее кастить? )
А SRP тут, кстати, не нарушается. Копир не содержит реализацию ни принтера, ни сканера — он их берёт у предка. Когда нужно будет изменить разрешение сканирования по умолчанию — будет меняться класс Scanner, а не Copier.
А зачем вам кастить Channel к BaseRange?
То есть профит от наследования — в возможности создать god object «не нарушая SRP»?
Можно, если вам это почему-то нужно. Хотя, я затрудняюсь сказать зачем такое может понадобиться. Но нарушением SRP это опять же не будет.
Божественный объект — это объект, который содержит не связанную между собой реализацию, а не поддерживает несколько не связанных между собой интерфейсов.
Наследовать не «нужно». Никогда.
Я вот от вас не могу добиться зачем вы отнаследовали 2 несвязанных класса.
> Божественный объект — это объект, который содержит не связанную между собой реализацию
И это именно то, что вы делаете.
Постановка задачи такая — нужен МФУ.
В случае наследования, реализация находится в родителе. В этом, собственно, и суть наследования — хранение общего поведения в отдельном классе (классах).
Мы ещё ваш код копира обсуждаем? Это не МФУ. На копире «scan» мне не нужен.
Если нам нужен МФУ — у вас будет, например, 3 свойства *Settings: от принтера, сканера и МФУ. Это монстр Франкенштейна, а не МФУ.
> В случае наследования, реализация находится в родителе.
Если вы про god object — не имеет значения. God object — это проблема не реализации, а использования. Для внешнего кода не важно как именно класс агрегирует функционал всего.
> В этом, собственно, и суть наследования — хранение общего поведения в отдельном классе (классах).
Да ну? Для «хранения общего поведения в отдельном классе» наследование не нужно от слова «совсем»
Нет смысла делать чисто копир, если можно сделать мфу. Но если нужен только копир, то наследование тут не нужно. Как, впрочем, и композиция сканера и принтера в общем случае.
Корневой неймспейс — вполне себе божественный объект в вашей интерпретации.
А никто не говорит, что оно нужно. Оно удобно.
Нет смысла не сделать god object, понимаю.
> Корневой неймспейс — вполне себе божественный объект в вашей интерпретации.
В моей интерпретации неймспейс — не объект. Вы ещё и объект от неймспейса не отделяете?
> А никто не говорит, что оно нужно. Оно удобно.
> Можно, если вам это почему-то нужно.
Ох…
Ладно, я понял, что не дождусь объяснения где же скрывается удобство от совокупления сканера с принтером методом ромба.
У вас богообъектофобия? Вы видите их там, где их нет.
Не отделяю. Я ещё и классы от объектов не отделяю. Вообще пропащий человек.
А в чём удобство от повторения делегирования каждого метода?
Вам тут наследование что дало?
Например, просто добавить копир на ресепшене в список принтеров компании, доступных отделу продаж для печати накладных.
… а для этого не надо наследования. Для этого надо выставить публичный интерфейс "принтер".
Но на ресепшене не просто копир — там есть ещё факс, поэтому придётся делать отдельный класс с реализацией всех интерфейсов, в том числе копира, т.к. копировать он тоже может.
У интерфейсов есть недостаток — их нужно реализовывать, хотя бы на уровне делегирования.
Это зависит исключительно от языка/фреймворка, которым вы пользуетесь. Но что самое важное, с точки зрения потребления этого класса (а вы написали именно о потреблении), эта проблема несущественна.
Я понял интерфейс, как в Java. Там он не имеет никакой реализации по определению. Конечно, в других языках может быть возможность определить для объекта интерфейс с реализацией, способом отличным от наследования, но зачем?
Add(Copier.GetPrinter())
можно написать
Add(Copier)
Ну, принимается. Хотя ценность так себе.
… для этого, впрочем, наследование не нужно, нужно Add(IPrinter)
и Copier: IPrinter
(или implicit operator IPrinter(Copier copier)
)
Я вообще не уверен, что хочу иметь на копире интерфейс принтера. I feel disturbance in Force.
Скорее MyUberDevice: IPrinter, ICopier
Хотя ценность так себе.
Для меня важно, что в случае наследования в список добавляется весь объект, а не его составная часть.
Компоновку — хорошо — можно предоставить и как наследование при соответствующих возможностях языка, однако как бы мы компоненты ни заворачивали — они должны быть изолированы друг от друга, иначе они друг друга «покрошат», поскольку у них есть похожие части, которые выглядят одинаково, но отвечают за разные состояния и переопределённые в наследнике эти части могут удивить родителя. В примере выше — кроме отдельной кнопки — есть ещё отдельные внутренности, например, сервопривод. Если же всё это изолировано и работает независимо — смысл склеивать всё в один класс, чтобы потом всё время писать «такая-то часть класса» — уже дискуссионен. Но «осуждать» не зная особенностей конкретного языка — воздержусь.
Копир должен именно наследовать принтер и сканер, поскольку может использоваться как эти отдельные устройства, а не только их комбинация.
Лично у меня большие проекты, где основными элементами дизайна (архитектуры) являются сомны сложных объектов вызывающие методы друг друга, а не подсистемы с ограниченным функционалом и конкретным явным уровнем абстракции данных, связанные протоколами различных уровней вызывают головную боль и рвотные позывы.
Особенно когда видишь a++ на верхнем уровне и понимаешь, что сейчас придется пройти через сотню унаследованных классов и операторов, чтобы понять, что здесь происходит :(.
Конечно, сам подход, что программисту надо о чем-то думать заранее, уже несовершенен. Но даже в ФП он присутствует…
Проблема в том что автор не понимает до конца что ему нужно. Копир должен скрнировать, а после печатать и выглядит этот так:
Class Copier {
Scanner scanner
Printer printer
function start() {
page = scanner.start();
printer.start(page);
}
}
Да и остальные примеры высосаны из пальца. Как уже неоднократно говорилось в коментариях к подобным статьям: плохая архитектура не проблема парадигмы.
function start()
{
Scanner::start();
Printer::start();
}
За тем, что мы избавились от множественного наследования в пользу делегирования, как и завещала великая банда четырех. "Используйте делегирование вместо наследования". Наверно это можно повторять неустанно.
Множественное наследование это мощный инструмент, но, как и любой другой мощный инструмент, должен быть использован с умом и осторожностью. Автор об этом не знает.
Обратите внимание, что классы Scanner и Printer оба реализуют функцию start. Тогда какую функцию start унаследует класс Copier? Та, что реализована классом Scanner? Или классом Printer? Не могут же они обе унаследоваться.
Ни у принтера, ни у сканера нет функционала копира, поэтому наследованием проблему копирования не решить. Поэтому у автора и проблема с архитектурой, а не с ограничениями ООП.
Я бы поставленную задачу решал как-то так:
class Copier: public Scanner, public Printer {
public:
void start() { Printer::start(); Scanner::start(); }
using Printer::print;
using Scanner::scan;
void copy() { Page *page = this->scan(); this->print(page); }
}
Какой разносторонний товарищ. Мне кажется, ФП ему нужно чисто в коллекцию титулов…
Во-первых, хоть что-то про программирование
Во-вторых, когда показываются недостатки, или проблемы, возникающие при попытке использования тех или иных средств — сразу задумываешься, а почему так и можно ли улучшить что-то в самом ООП чтобы эти особенности обойти?
Полиморфизм — не есть уникальное свойство ООП, каждая парадигма предлагает полиморфизм, просто реализации разнятся. ООП имеет естественный полимформизм, но может пользоваться и функциональным полиморфизмом, просто не бесплатно.
Буллшит-бинго!
Ты слышал про парня, который попрощался с OOП?
О нет. Ещё один? Что же он сказал?
Совсем недавно писали по этому поводу.
Я открыл статью, задавшись вопросом: «А что взамен?», но так и не понял, чем так замечательно ФП и почему автор выбрал именно его.
Иногда лень писать шаблонные методы, пользуюсь копипастом, но я с этим борюсь, в случае с ФП копипаста было бы намного больше.
Потерянное поколение XD
А если по существу — всё уже сказано выше. Автор статьи строит кривую архитектуру, после чего начинает ругает ООП за то, что архитектура получилась кривой. Если б я со своим почти нулевым опытом ФП бросился писать статью о ФП — от ФП вообще камня на камне не осталось бы. Кстати, о самом ФП автор пишет только, что ФП рулит… И всё (впрочем, это тоже уже заметили комментирующие выше).
П.С.: Печально что такая серьёзная компания, как mail.ru допускает в своём блоге публикацию столь маргинальных статей (ну, или переводов статей — без разницы).
Если бы программисты понимали принцип подстановки Лискофф и знали, что такое коммутативные диаграммы в теории категорий, то проблем бы не было.
И, кстати (в поддержку ФП), с иммутабельными объектами наследование работает лучше, с мудательными принцип подстановки соблюдать сложнее.
ООП — это это не классы, это когда объекты обмениваются сообщениями.
Бессмысленно просить кофеварку помыть посуду, а пылесос сварить кофе.
Контракт всегда есть. Он выражен в виде слова interface в коде, в юнит-тестах, в документации или в голове у разработчика — но он есть. Видишь суслика?
Но у термина «ООП» есть автор. Он и словами, и делами объяснил этот термин: что именно под ним подразумевается и в каком виде.
А по поводу ООП на C можно ответить так: прежде, чем вы сможете на C писать объектные программы, вам придется сначала создать соответствующую «инфраструктуру» (те же объекты + механизм посылки сообщений и связывания их с кодом) — другими словами, написать объектный «DSL». Так что без языка — увы — все равно ничего не выйдет.
Вот в Objective C технически отправка сообщений, а в Swift-е — технически вызов метода. И ничего, ООП-программы писать можно и на том, и на другом.
На С можно обойтись структурой с указателями на функции и договориться первым аргументом передавать this (привет, python).
Кажется, вы слишком буквально воспринимаете «отправку сообщений».
Вы хотите рассказать мне о том, как я это понимаю? :) Ну, ок, давайте…
Вот в Objective C технически отправка сообщений, а в Swift-е — технически вызов метода.
Что такое «технически отправка сообщений»? И что такое «технически вызов метода»? Я-то по простоте душевной думал, что независимо ни от чего, получивший сообщение объект должен как-то связать этот факт с кодом, который это сообщение должен обработать. Оказывается — нет. Очень-очень интересно, продолжайте.
На С можно обойтись структурой с указателями на функции и договориться первым аргументом передавать this (привет, python).
И что же это будет, если не язык?
Принципиальная разница, в том, в какой момент связываем сообщение с кодом-обработчиком — compile time или run time.
Хотя на самом деле все сложнее. Например, компиляторы Objective C могут оптимизировать «сообщение» в «вызов метода», если во время компиляции обрабатывающий код очевиден. Когда (и если) в PHP сделают JIT, там наверняка будет то же самое.
Опять же, это несущественные детали. Парадигма — она о способе думать, а не о технической реализации.
> И что же это будет, если не язык?
На уровне языка — структура и указатели. А объект и сообщение — у нас в голове.
Принципиальная разница, в том, в какой момент связываем сообщение с кодом-обработчиком — compile time или run time.
Ох, я начинаю жалеть, что вступил в беседу. Давайте вы сначала разберетесь хотя с тем, что передача сообщения не может быть реализована в «compile time», хорошо?
…А потом, надеюсь, и с другими понятиями. Удачи!
Заметил такую особенность в своём коде — почти все поля классов readonly, за исключением классов для данных.
Как-то само собой стало так получатся, может поэтому нет проблем про которые пишет автор?
Если нужно организовать in-memory DB использую разделяемые списки, как я понимаю чистое ФП этого не может, а вот мне надо.
Так что ООП никуда не денется.
Процитирую статью с Хабра:
Алан Кэй, создатель ООП, про разработку, Лисп и ООП
ООП для меня это сообщения, локальное удержание и защита, скрытие состояния и позднее связывание всего. Это можно сделать в Smalltalk и в LISP.
Мне жаль, что давным давно я использовал термин «объект» для этой темы, потому что из-за этого многие люди фокусируются на меньшей из идей.
Большая идея это «сообщения».
Может кто-нибудь объяснить мне подробнее «Проблему ссылки (The Reference Problem)», а то автор статьи как-то не очень понятно ее расписал? а в поисковиках что-то нашел, но не то
Может кто-нибудь объяснить мне подробнее «Проблему ссылки (The Reference Problem)»
Если вы при создании объекта А передаёте ему ссылку на другой объект B, то у вас нет гарантии, что дальше вы можете спокойно продолжать использовать данные объекта B, так как объект А вполне может их поменять как ему вздумается.
Вам надо создать копию объекта B (B') и передать ссылку при создании объекта A на эту копию (на B'), а самим спокойно использовать данные объекта B.
Но эту копию (объект B') не всегда можно создать.
Например, на С++:
Ничего не мешает передавать в А константную ссылку на В, если нужны 100% гарантии*, что данные в В не изменятся. В данном случае, если конструктор А требует неконстантную ссылку, то проект не скомпиляется — будет возможность продумать дальнейшие шаги по устранению до запуска программы
Если объект А все-таки принимает неконстаную ссылку, значит он меняет что-то в В, значит создание А должно сопровождаться ожиданием, что в В что-то может измениться
Объект А для своих внутренних нужд может изменять только свои приватные данные, а не внешние, т.е. В в данном случае
То есть, при самом обычном (даже не самом хорошем) проектировании указанной проблемы с ссылкой не должно возникнуть, в принципе
*100% гарантии все равно не будет, т.к. существует const_cast, но это крайний случай, который опять-таки вытекает из проектирования. Константные ссылки, константные методы и т.д. придумали не просто так
наследование является ключом к повторному использованию
Нет
Но переданный объект небезопасен!
Нет
Так что без лишних разговоров прощаемся с ОО-полиморфизмом, и приветствуем полиморфизм на основе интерфейсов.
Простите, а ОО-пролиморфизм он не на основе интерфейсов?
Простите, а ОО-пролиморфизм он не на основе интерфейсов?Возможно ключ в том, что автор писал только на C++, где ключевых слов для интерфейсов нет.
Но это не мешает всем желающим делать классы с чисто виртуальными методами и, например, именовать типа ISupplier
, если уж захотелось побольше явности.
Привет, функциональное программирование.
… и где вторая половина статьи?
Где вторая половина статьи, где объясняется, почему именно ФП, а не скажем ProLog или ещё что-то иное?
Ну да. ООП без паттернов проектирования само по себе не суперполезно. Я получил большой профит, когда стал выделять фрагменты декларативного программирования, освоил и стал активно понимать и использовать паттерны.
Все это в сумме очень ускоряет разработку. По отдельности — ломает мозг.
> Эта проблема — ещё одна трещина в Столпе Наследования.
Это трещина не в наследовании, а в башке автора
1) ООП не предназначено для прямого уменьшения повторного использования, оно предназначено для корректного моделирования системы. Повторное использование если вообще и бывает, является побочным эффектом.
Дублирвание кода (текста) делается макросами или еще какой копипастой. Дублирование высокоуровнего смысла — это совсем другая область, другой логический уровень.
Соответственно, сама система должна быть спроектирована для увеличения повторного использования. А не говнокод которым ты это всё замоделил. Чтобы система была правильно спроектирована нужен хороший разработчик систем, например выделенный системный аналитик или архитектор.
Аналитика у человека не было, сам он не то чтобы гений проектирования, поэтому подпер убогую идею костылями на С++ и Java, и плачет теперь что говно почему-то С++, а не его подход к решению задачи.
Т.е. в проблеме поиска сотрудников (аналитиков, архитекторов) обвинили ООП. Хм, отличная отмаза. На менее подготовленного читателя даже проканало бы, но у нас в Инквизиции за такое сразу же сжигают на рее.
2) > Проблема с ОО-языками заключается в том, что они тянут за собой всё своё окружение. Вы хотели всего лишь банан, но в результате получаете гориллу, держащую этот банан, и все джунгли впридачу.
> Джо Армстронг
В 1986 году Джо Армстронг сделал Эрланг. Оперативная память стоила очень дорого, и Джо пришлось капитально попотеть, чтобы сэкономить хоть немного. И процессоры были очень медленные, анализ контекста в компиляторе занимал очень много вычислительных ресурсов.
А потом прошло 30 лет.
3)
> Я в течение десятилетий программировал на объектно-ориентированных языках.
> private ArrayList[Object] a = new ArrayList[Object]();
Вместо private List используется private ArrayList. Абстрагирование, полиморфизм подтипов — не, не слышали. Загримированный под матерого ООП кодера, Штирлиц никогда не был так близок к провалу.
4)
> Возьмём следующий базовый класс
> public class Array { private ArrayList[Object] a…
Знаете, чем хороша Java? Тем что в ней, к счастью, нет такого базового класса.
Автор написал поверх класса List прокси с названием Array (sic!), и запроксировал в нем метод add(element). Потом запроксировал addAll, в котором полностью проигнорировал тот факт, что addAll и add это не какие-то произвольные операции, а жестко связанные. В addAll уже нельзя использовать оригинальный add, нужно использовать свой прокси. После столь свинского с собой обращения, всё похерилось. Конец был немного предсказуем, верно?
5)
> Мы должны работать только с интерфейсом. Это раздражающая тенденция...
… раздражающе мешающая писать говнокод, ясно.
6)
> Идея иерархии “общее/частное”…
> Каждый раз, создавая новую компанию, мне приходится решать проблему, связанную с выбором места для документации компании. Нужно ли мне сделать папку Документы, а внутри неё создать папку Компания? Или мне нужно сделать папку Компания, а внутри неё — Документы? Оба варианта рабочие, но какой из них правильный? Какой лучше?
Вы действительно хотите сделать лучше? Тогда вот мой совет как вашего адвоката:
Лучше всего курить дудку немного реже. Мужик, поверь старому норкоманту, это не доводит до добра. Папка документы — это частный случай сущности Компания, серьезно?
7)
> иерархия “общее/частное” не работает. Так для чего тогда хороши иерархии?
> Для включения (Containment).
Компания включает в себя папку. Если спиздить папку и утащить за пределы офиса Компании, мир рухнет, а автор статьи отвалится с сегфолтом. Ясно. К папкам нужно относиться весьма серьезно, это вопрос национальной безопасности.
С 1 сентября 1960 года (или когда там в продакшене появится ООП), писать на нем разрешается только отряду Мстители под руководством отца Тони Старка. (Marvel не похоже на людей, свято блюдущих хронологию)
8)
> Инкапсуляция, второй обрушившийся столп
Учитывая объем текста, описывающий проблему, к этому моменту автор уже понял, что что-то пошло не так. Проклятое ООП не сдается даже на его территории — посте на Хабре.
9)
> The Reference Problem
Автор сам придумал этот термин?
Я вижу только https://en.wikipedia.org/wiki/Reference_class_problem
и это вообще из статистики, а не программирования
10)
> Если ссылка на объект передаётся конструктору объектов, то он кладёт её в приватную переменную, защищённую инкапсулированием. Но переданный объект небезопасен! Почему? Потому в какой-то части нашего кода содержится указатель на объект, то есть код, вызывающий конструктор. Но он ДОЛЖЕН иметь ссылку на объект, в противном случае он не сможет передать её конструктору.
Автор скорей всего как бы хотел сказать о паттернах fluent interface + builder, примененных в concurrent случае? Что кроме кроме религии запрещает ему сделать все доступы безопасными?
11)
> Конструктору придётся клонировать переданные объекты.
А, автор решил нам продать defensive copying! Об этом рассказывают на первом курсе универа на предмете «программирование на языке высокого уровня» (ПЯВУ). Так что, кто не пробовал поступить даже на первый курс, или никогда не читал классические тексты по Java, или википедию, или вообще ничего, эта информация безусловно будет вам очень полезной.
> в лучшем случае клонировать их бесполезно, а в худшем — невозможно. В каждом из мейнстримовых ОО-языков существует такая проблема. Прощай, инкапсуляция.
Ну а невозможно это потому что? Да черт его знает. Я уже задолбался писать этот комментарий, если честно :-)
12)
> Полиморфизм, третий обрушившийся Столп
> Куда бы они не отправились, он был с ними, но всегда на вспомогательных ролях.
Барбара Лисков и Джанет Винг, Андрей Александреску и Герб Саттер, Роберт «Uncle Bob» Мартин, Девид Хейден и Бертранд Мейер, и другие, менее популярные товарищи, плачут кровавыми слезами и крутятся в постелях как вентиляторы.
13)
> Блог компании Mail.Ru Group
Мэйлру капец. Ясно.
В Новосибирске уже ночь, надо с этим заявязывать.
Спят усталые игрушки, книжки спят,
Одеяла и подушки ждут ребят.
Даже сказка спать ложится,
Чтобы ночью нам приснился
Мир, в котором ООП больше не работает
Опять! Недели не проходит, чтобы кто-нибудь не стал наезжать на ООП.
Появился новый проект, я не забывал о своей идее с классом и испытывал большой энтузиазм. Без проблем. Повторное использование спешит на помощь. Нужно просто взять класс из другого проекта и применить его. Ну… вообще-то… не просто класс. Понадобится родительский класс. Но… Но это всё. Гхм… погодите… кажется, нужен будет ещё родитель родителя… А потом… в общем, нужны ВСЕ родители. Хорошо… хорошо… Я с этим разберусь. Без проблем.
Мне просто нужна была функция, но она внутри вызывала композицию из нескольких функций, а они в свою очередь еще несколько функций...
Пример со сканером, принтером и копиром — это классика плохого дизайна и неудачных абстракций. Зададимся вопросом, является ли поведение сканера и принтера частями поведения копира. Ответ довольно очевиден: нет. Копир может и не уметь сканировать или печатать. Некоторая реализация копира может использовать сканер и принтер, но это детали реализации, которые скрыты инкапсуляцией. И да, инкапсуляция про отделение деталей реализации от интерфейса, а не про защиту от доступа. Что ж, неудачный дизайн — не проблема ООП, а проблема тех, кто его использует.
В общем, ждем статью «Прощай, функциональное программирование».
Ну а ФП — это вовсе не следующий, последовательный шаг в развитии, это параллельная ветка. Которая возможно когда нибудь и объединится с ООП,
Проблема банана и гориллы решается использованием Dependency Injection — все зависимости должны внедряться в конструктор, причем, в идеале, как интерфейсы.
Проблема хрупкого класса не являтся проблемой ООП. Это просто — антипаттерн. Такой же, как, например, GOD-object. Не надо так писать. А если почему-то надо, то стоит запретить наследование таких классов.
Вот тут , например, автор хорошо показывает, что стрелять себе в ногу можно и на функциональном языке. Было бы желание.
Т. е. статья о том, что если вы пытаетесь с помощью ООП сделать какую-то херню, получается херня? Ок.
https://www.youtube.com/watch?v=HTLu2DFOdTg — я просто оставлю это здесь.
Отсюда автоматически вытекает необходимость наследования и полиморфизма, а инкапсуляция идет как маленький бонус — было бы странно носить данные объекта (ака его внутреннее состояние) отдельной структурой данных.
Ну и касательно повторного использования — если вспомнить времена Turbo Pascal 5.5 и становления Java — основная киллер-фича ООП, которая хорошо покупалась бизнесом, звучала именно как «возможность повторного использования кода». Это исторический факт.
Другое дело, что практика показала, что профит от повторного использования ООП-кода чуть менее чем ноль.
Такие дела.
Профита от повторного использования ООП-кода ровно столько же, сколько от повторного использования не ООП-кода. Фреймворки, библиотеки, вот это все.
каноническое определение ООП на классах звучит так: ООП — это парадигма программирования с использованием объектов, которые являются экземплярами классов, которые в свою очередь образуют иерархию (т.е. наследуются друг от друга).
Пикантность как раз в том, что это далеко не каноническое определение ООП. Но все почему-то хватаются за него, а потом плюются. Исходная статья — показательный пример. Комментарии к ней — тоже. Обсуждаются симптомы, а причина — неверное понимание принципов ООП — как-то остается незамеченной. В результате ООП в очередной раз мертв и все бросаются уродовать «новую» (старую) идею с тем чтобы лет через *цать от нее с негодованием отказаться.
Я не знаю кто и в каком языке впервые ввел ООП, но одним из самых известных пионеров, кто начал его популяризаю, думаю, был Алан Кэй. Он утверждал, что основной задачей данной парадигмы, является уход от статической модульности, в виде поключаемых файлов, к динамическим модулям в виде объектов, реализующих как обобщенную, так и конкретную функциональность. Он никогда не говорил, что основной сутью ООП являются объекты как таковые, но именно инкапсуляция в объектах независимых частей общей системы (откуда и растут ноги многократно используемого кода) и взаимодействие данных частей. Главной задачей была именно динамичность системы: горячие обновления путем замены объектов в runtime, как, например, обновление в erlang, взаимодействие модулей, основанное прежде всего на интерфейсах, обеспечивающих гибкость и легкость изменения объектов без нарушения работоспособности системы в целом, общение объектов между собой посредством сообщений, что давало возможность общим объектам делегировать выполнение задачи более специализированным объектам и многое другое.
В целом, можно сделать вывод, что большинство сторонников ООП вообще не понимают первоначальную идею данной парадигмы, считая её неким выражением философской когнитивной модели объект-субъект (причем с отсутствием последнего). Так что ждать от современных ООП-языков соответствия первоначальным принципам не приходится, а попытки переосмыслить все «по-новому» окончательно выродились во что-то неосмысленное и привели к бесконечному потоку подобных статей.
Kay is one of the fathers of the idea of object-oriented programming, which he named, along with some colleagues at PARC and predecessors at the Norwegian Computing Center.
Думаю, при чтении, следует вдумываться в текст. Также вам следует изучить термин «канонический» и отличия терминов «каноническое определение» и «идея»
Думаю, при чтении, следует вдумываться в текст.
Вот именно: «which he named». Да и другими источниками иногда пользоваться не мешает. Он сам неоднократно называл себя изобретателем термина, описывал при каких обстоятельствах это произошло. А также объяснял, что именно под этим термином понимается. И, например, такие слова как «инкапсуляция», «полиморфизм» я в его высказываниях не припомню.
Другие люди тоже с удовольствием много раз говорили о нем как об авторе термина. И я не видел никого, кто бы пытался уличить его во вранье по этому поводу. Если вам что-то известно об этом — будет интересно узнать, что именно.
В целом, можно сделать вывод, что большинство сторонников ООП вообще не понимают первоначальную идею данной парадигмы
А вот с этим я абсолютно согласен.
Но зачем нам читать такие книжки, они же для новичков, а мы же «в течении десятилетий программировали на ООП». Лучше я напишу свою статью, где выведу на чистую воду языки, ставшие промышленным стандартом программирования…
А если по статье: слабая аргументация, практически все можно опровергнуть. Про полиморфизм дак вообще ничего внятного я не увидел.
Как уже писали
Либо несколько изменить ArrayCount, либо оба Array и ArrayCount(если есть возможность).
Что то мне подсказывает, что в текущей реализации будут и другие проблемы с использованием и ожидаемым результатом.
Надеюсь только, что когда автор разочаруется и в функциональном программировании (а это с его ожиданиями от жизни — вопрос времени), он не поленится на такой же расширенный вброс, с примерами, блок-схемами и картинками. И желательно в какую-нибудь пятницу. Ну и с вектором своих дальнейших действий. Чтобы, так сказать, сохранить интригу объективности.
Прощайте стереотипы, здравствуй здравый смысл. Завтра вы поймёте, что и ФП не является рецептом от всех болезней. Послезавтра засомневаетесь в парадигмах. Послепослезавтра научитесь верить внутреннему голосу. А вот после этого уже поймёте, что не так всё плохо и с ООП, и с ФП, а для собственника оплачивающего ваши плюшки бизнеса (о, ужас!) абсолютно фиолетово, каким именно образом убирается его головная боль и автоматизируется та или иная активность, т.к. IT – это всего лишь инструмент (да, мощный, но один из многих). А если собственником бизнеса вдруг окажетесь вы, так вообще количество откровений превысит все мыслимые и немыслимые ожидания.
Прощай, объектно-ориентированное программирование