Комментарии 367
Объектная модель всем хороша в однопоточной среде.
придётся писать практически в функциональной парадигме, попутно сражаясь с примитивами языков, принципиально заточенных в бо́льшей степени на объектную модель
При реализации собственных пет-проектов на C++ / WTL, использование многопоточности не привело, лично у меня, к каким-то особым проблемам в использовании ООП (см. скриншот моей неопубликованной программы «МедиаТекст»: http://scholium.webservis.ru/Pics/MediaText.png ). Да, менеджер событий можно было бы оптимизировать, но менеджер потоков там работал нормально.
Другой мой проект (см. мою статью: «Новая компьютерная программа для запоминания иностранных слов и фраз» в https://habr.com/ru/articles/848836/ ) использовал, в том числе, эмуляцию видео-режима. Не знаю, можно ли считать работу с таймером многопоточностью либо его эмуляцией, но проблемы там были не в ООП. Наоборот, решение своих проблем я связывал с «правильным» использованием ООП.
наследование, инкапсуляция, классы
Как бы там ни было, но для пет-проектов это вполне работает.
любой, кто раскрыл рот, чтобы сообщить мне, что я просто не умею их готовить ...
Два вызова метода increment из двух разных потоков приведет к тому, что мы дважды вычитаем предыдущее значение из базы, а потом дважды запишем обратно его же, увеличенное на единицу.
Оказывается, у вас проблемы не только с мьютексами, но и с БД
Да я вообще тупой, что уж там.
Так не нужно корявым примером работы с БД аргументировать наличие проблем в ООП. Или используйте атомарный инкремент средствами самой БД или меняйте алгоритм работы класса на многопоточный.
Ведь ни мьютексы, ни ООП тут не причем, так как эти инструменты действительно нужно уметь правильно готовить использовать.
Давайте вы не будете говорить мне, как мне нужно излагать свои мысли, а я в благодарность не стану говорить, куда вам нужно идти.
атомарный инкремент средствами самой БД
ООП как бы обещает защиту данных через инкапсуляцию и абстракцию, а у вас абстракция протекает
Вы можете развернуть свой комментарий? Если честно, то я его не понял.
Чтобы инкапсуляция работала, как задумано, — надо правильно написать код внутри.
фальшивые дублоны принимаете?
по быстрому могу только через LLM
Обещание ООП
ООП говорит: "Вот объект
BankAccount
. У него есть приватное поле_balance
. Только его методы, напримерDeposit()
, могут менять это поле. Я, объект, гарантирую целостность своих данных". Это и есть инкапсуляция. А абстракция в ООП говорит, что мы не должны заботиться о реализации - БД это или скажем in-memory key-value словарь.Суровая реальность (где протекает абстракция)
На самом деле, "истинное" значение баланса лежит не в памяти моего C# объекта, а в строке таблицы
Accounts
в PostgreSQL. Мой объект — это всего лишь временный, кешированный представитель этих данных.И тут возникает конфликт:
Наивный ООП-подход (протекающая абстракция):
Мой метод
Deposit(amount)
делает:this._balance += amount;
Затем другой слой (Repository/ORM) смотрит на измененный объект и генерирует SQL:
UPDATE Accounts SET Balance = [новое значение] WHERE Id = ?
Проблема (Race Condition): Если два потока одновременно загрузят счёта с балансом 1000, оба в памяти увеличат его до 1100, и последний записавший затрет результат первого. Баланс будет 1100, а не 1200. Инкапсуляция объекта в C# никак не защитила данные в БД. Абстракция "безопасного объекта" протекла и оказалась ложью.
Правильный подход (признание, что ты не главный) - протекающая абстракция:
Как вы и сказали, нужно использовать
UPDATE Accounts SET Balance = Balance + 100 WHERE Id = ?
.Эту операцию БД выполнит атомарно. Она сама защитит свои данные.
Что это значит для моего ООП-объекта? Его метод
Deposit()
больше не должен менять свое состояниеthis._balance
. Он должен сгенерировать команду (UPDATE...
) и отправить ее в базу данных.Итог:
Когда появляется внешний авторитетный источник состояния (как БД), классическая ООП-инкапсуляция и абстрагирование от реализации в БД ломается. Объект больше не является хозяином своих данных. Он превращается из хранителя состояния в генератор команд (Command Emitter) или фасад для внешних операций.
Попытка притвориться, что объект в памяти — это и есть "настоящий" объект, и есть та самая "протекающая абстракция". Правильное проектирование (например, в CQRS) признает этот факт и явно разделяет модели для чтения и модели для изменения, которые только принимают команды и валидируют их, а всю работу по атомарному изменению делегируют БД.
Фу. Это мой пример, только мысью по древу.
Если нужен правильный пример без стороннего источника данных, то лучше взять стейт-машину, с «инкапсулированным» стейтом, который точно так же течет без всякой базы, и вылечить это без мьютекса уже невозможно.
Вы реально думаете, что LLM сумел уловить вашу мысль и у него получилось написать понятный ответ?
У вас странное понятие о протекающих абстракциях.
ООП говорит: "Вот объект
BankAccount
. У него есть приватное поле_balance
. Только его методы, напримерDeposit()
, могут менять это поле. Я, объект, гарантирую целостность своих данных". Это и есть инкапсуляция.
Это не инкапсуляция.
"Я объект BankAccount, представляющий банковский счет. У меня есть методы Deposit и Withdraw, позволяющие менять баланс счета. Я гарантирую целостность своих данных. Как я это делаю и в каком виде храню баланс - не твое собачье дело" - вот это инкапсуляция.
На самом деле, "истинное" значение баланса лежит не в памяти моего C# объекта, а в строке таблицы
Accounts
в PostgreSQL
Вообще по барабану, где оно лежит и как реализовано до тех пор, пока подробности не торчат наружу.
Смысл абстракции именно в том, чтобы ты не знал, что внутри. Это гарантия, что ты можешь безопасно поменять внутреннюю реализацию, не задевая зависящих от нее частей программы.
Вот у тебя вышеуказанный BankAccount. Допустим, ты пишешь реализацию: методы Deposit и Withdraw набирают номер телефона оператора и заставляют нейросеть зачитать голосом "увеличить баланс счета №192839102931012 на 100 рублей". Реализация говно, конечно, но абстракция - идеальна.
Почему идеальна абстракция? Ну, например, ты достаточно быстро понял, что дозвон может занимать до 5 минут, что сильно тормозит поток транзакций. Надо что-то делать. Например, батчевать. Пока номер набирается, копить команды и вываливать на оператора разом то, что накопилось: "счет 1923891243 +100, счет 932181292348 -100". Прелесть в том, что весь твой код, который подсчитывает, на сколько менять баланс, вообще ни в одной строчке не изменился. Все изменения - внутри класса BankAccount.
Окей, на 18-й итерации до тебя дошло, что с оператором что-то не так: то опечатывается, то трубку не берет, то рыдает и в запой уходит. Ты ставишь какую-нибудь постгрю и выкидываешь к чертовой матери всю эту дичь с телефонией. Заменяешь тупым запросом к БД. Изменения - монументальные, а абстракция не изменилась. Чтобы поменять баланс, тебе нужно дергать все те же ручки с теми же параметрами.
Вот это - абстракция, и она не течет. Хочешь пример текущей абстракции - посмотри на слайсы в Go. А вот то, что ты напридумывал, не течет.
И тут мы вспоминаем, что данное обсуждение происходит в комментариях под определенным текстом, добавляем второй поток обработки (это — по сути тема текста) — и ваша прекрасная абстракция начинает не просто подтекать, а фонтанировать.
Иногда полезно немного думать, прежде чем что-то писать в публичный доступ.
Иногда полезно немного думать, прежде чем что-то писать в публичный доступ.
Вот это я вам и пытаюсь донести.
И тут мы вспоминаем, что данное обсуждение происходит в комментариях под определенным текстом, добавляем второй поток обработки (это — по сути тема текста) — и ваша прекрасная абстракция начинает не просто подтекать, а фонтанировать.
Каким боком она подтекать-то начинает???
Вот у нас метод BankAccount.Deposit(decimal amount)
- это наша абстракция.
public void Deposit(decimal amount) {
this.amount += amount
}
Да, допустим, мы тупые и не предусмотрели возможность конкурентного доступа. И что? Абстракция-то тут причем, проблема-то в реализации.
Вот, смотрите, ща я реализацию исправлю, а абстракция та же самая останется:
public void Deposit(decimal amount) {
this.mutex.Lock()
this.amount += amount
this.mutex.Unlock()
}
А абстракция та же, ничего никуда не утекло!
Так что, прежде чем писать в публичный доступ о текущих абстракциях, потрудитесь, хотя бы, нагуглить, что такое "текущая абстракция"
В многопотоке абстракция течёт. Потому что появляется время, которое разделяет состояние на до изменения и после. Но если объект сам не управляет временем, то до и после перемешивается.
Приведите пример в многопотоке.
Lock управляет временем (заставляет ждать), но способ не безопасный и не эффективный
В многопотоке абстракция течёт. Потому что появляется время, которое разделяет состояние на до изменения и после.
Ну, т.е. любая абстракция течет? о_О Всегда есть время до и после!
Приведите пример в многопотоке.
Приводил уже.
public void Deposit(decimal amount) {
this.mutex.Lock()
this.amount += amount
this.mutex.Unlock()
}
Lock управляет временем (заставляет ждать)
Все верно, в этом и есть смысл абстракции! Абстракция - это класс BankAccount, который позволяет просто дернуть ручку Deposit, на задумываясь, что внутри. Мьютекс - внутри, в реализации. Нам не надо заботиться о необходимости лочить мьютекс снаружи, потому что внутри мы этим уже озаботились - это и называется "абстракция".
но способ не безопасный и не эффективный
"Других у меня для вас нет". Но, точнее, есть, но они все так или иначе либо небезопасные, либо неэффективные.
Вы можете развернуть свой комментарий? Если честно, то я его не понял
Могу и развернуть:
Учите, что такое инкапсуляция и абстракция. А потом задавайте вопросы.
Учите, что такое инкапсуляция и абстракция. А потом задавайте вопросы.
Вам самим это не мешает хорошенько выучить, так как LLM за вас думать не будет.
А с точки зрения ООП, речь всегда идет о внутренних данных объекта, тогда как в примере в статье используются внешние по отношению к объекту данные из БД.
с точки зрения ООП, речь всегда идет о внутренних данных объекта, тогда как в примере в статье используются внешние по отношению к объекту данные из БД
public int increment() {
int current = this.value;
// управление переключилось
this.value = ++current;
return this.value;
}
Завидую, когда у людей абстрактное мышление полностью вытеснено прикладным. Так понятнее?
Конечно! Ведь в этом примере можно вызывать increment() хоть в миллиарде потоках без каких либо гонок!
Вы сейчас серьёзно? Вы правда думаете, что гонки тут нет?
Конечно нет. В начеле метода компилятор вставит захват мьютекса, а в конце его освобожнение.
Это какой же компилятор такими чудесами промышляет?
В начале метода компилятор вставит захват мьютекса, а в конце его освобождение.
Так и пишите: если класс напрямую объявлен, как synchronized
, то […]. Так и джава умеет, вот только это кувалда, которая решает одну незатейливую проблему, а приносит — миллиард очень затейливых, наподобие неожиданных дедлоков в самых непредсказуемых местах.
Методы экземпляров нормальных объектов, про которые в этом треде идет речь, ни в какие мьютексы, разумеется, не оборачиваются, и гонка в примере выше есть.
А не синхронизированный класс компилятор не даст вам расшарить между потоками.
Чаво? [Здесь и далее я предполагаю, что под «не синхронизированный класс» имелось в виду «экземпляр класса, объявленного без ключевого слова synchronized
».]
А как компилятор узнает, что я собираюсь экземпляр передать в другой поток?
Понятнее не стало, а тратить время на подробное разбирательство с D у меня желания нет.
Насколько я понял, чтобы передать экземпляр в другой поток, я должен руками объявить его как shared
. Что, конечно, прям повышает удобство вывода в параллельные вычисления любого старого кода. Borrow checker в расте делает примерно то же самое, но язык от этого не становится волшебным образом готов к параллелизму.
Причём тут язык D? Мы более абстрактно, не зависимо от языка обсуждаем вроде
Или по вашему ООП только там? Java не ООП?
Если вы вызываете метод increment() у одного объекта из нескольких потоков (когда объект глобальный), тогда естественно гонка будет. Если объект локальный для потока (сервис, асинхронщина и т.д.), то гонки нет.
Тогда как в вашем изначальном примере с внешними данными из БД гонка есть всегда и не зависимо от локальности объекта.
в этом примере можно вызывать increment() хоть в миллиарде потоках без каких либо гонок!
Если вы вызываете метод increment() у одного объекта из нескольких потоков (
когда объект глобальный), тогда естественно гонка будет.
Я вычеркнул бессмысленную часть комментария, потому что объекту вообще необязательно быть глобальным, это может быть любой объект.
Так вот, вопросик: какое из ваших альтер эго получает право решающего голоса? С каким из этих взаимоисключающих утверждений вы согласны прямо сейчас, в эту секунду?
это может быть любой объект.
Вариантов объектов может быть целая куча, плюс еще маленькая тележка.
Только в С++ время жизни объекта может быть глобальным, локальным для области видимости и локальным для потока.
И для каждого варианта стратегия синхронизации доступа к методам объекта будет разной. Точнее, синхронизировать доступ к такому объекту нужно только тогда, когда он глобальный. Для локальных объектов синхронизация не требуется, а вот thread_safe зависит от условий его использования.
Короче, срочно в школу учить матчасть!
Для локальных объектов синхронизация не требуется […]
Это еще почему? Я не имею права внутри функции объявить переменную, присвоить и передать в два разных потока? Компилятор запретит?
Можно и адрес локальной переменой возвращать в return, можно даже писать по 0x0 адресу, никто вам этого не запретит и даже в Rust можно писать unsafe код.
Однако мы ведем разговор про синхронизацию доступа к объекту и в контексте нашей переписки "локальный" объект подразумевает отсутствие доступа к нему снаружи локальной области видимости.
Но для С++ это будет только соглашением, т.к. компилятор это не может проверить.
мы ведем разговор про синхронизацию доступа к объекту и в контексте нашей переписки «локальный» объект подразумевает отсутствие доступа к нему снаружи локальной области видимости
Если честно, то я понятия не имею, о чем ведете разговор вы. Я вяло реагирую на логические ошибки и нестыковки, просто потому, что плохо переношу алогизмы.
Если нет доступа — бессмысленно рассматривать гипотетическую ситуацию «вы вызываете метод increment()
у одного объекта из нескольких потоков […]» (это прямая цитата из вашего комментария, если что). Если доступ есть — то есть и гонка (пока нет явного мьютекса, или его неявного аналога, известного в джаве под именем «synchronize
»).
Всё вот именно так просто, тут нечего обсуждать.
Видимо, под глобальностью вы имеете в виду обратное от локальности в одной функции. То есть объект могут иметь с двух сторон
То есть shared state.
с точки зрения ООП, речь всегда идет о внутренних данных объекта, тогда как в примере в статье используются внешние по отношению к объекту данные из БД
Так и есть. Но ООП заявляет (через инкапсуляцию и абстракцию) что оно может решить этот вопрос без погружения программиста в кишки СУБД. И ведь многие верят!
Я уже переписал пример без СУБД (и даже в сам текст дисклеймер вставил). Вот: https://habr.com/ru/articles/920898/comments/#comment_28475464
Это в каком месте ООП такое заявляет? Может, вы просто неверно поняли то, какие гарантии ООП дает?
Эта ситуация: самолёт ждёт указаний диспетчера, но диспетчер не сможет дать никаких указаний, пока самолёт не сядет, — и есть типичный дедлок.
Кстати, насчет мьютексов и дидлоков. Эта проблема не мьютексов, как объектов, а их архитектуры (или интерфейса использования). Два взаимосвязанных метода (захват и освобождение) получаются не связаны между собой в коде программы.
А вот если бы мьютекс был реализован как объект ООП, тогда такой ситуации возникнуть просто бы не могло, так как сам компилятор стал бы автоматически управлять взаимозависимыми вызовами одного и того же объекта (например за счет применения какого нибудь std::lock_guard).
В каком смысле бы не могло возникнуть? Какая разница, как реализован мьютекс, если два несвязанных куска кода в разных потоках могут ждать друг друга? Как это вообще может разрулить компилятор? Это же типичная гонка.
Кроме того, в руби это объект. Да и в джаве это почти объект.
Захват и освобождение мьютекса это всегда два несвязанных между собой куска кода и причем не важно, в одном потоке это происходит или в разных.
Использование концепции ООП позволяет (в теории) обращаться к объекту как к одному целому и контролировать логику его использования, например за счет отказа от вызова отдельных методов (lock и unlock) в пользу применения объектов владения и в этом случае правильностью вызова блокирующих и разблокирующих методов будет управлять компилятор (например, вот так).
Гарантировать отсутствие циклических ссылок можно только путем их запрета на уровне типов (определений классов).
Поскольку это в принципе абсолютно неверно, дальше я читать не стал, прошу прощения.
в этом случае правильностью вызова блокирующих и разблокирующих методов будет управлять компилятор
А, я понял, наконец: вы предлагаете решить проблему гонок запретом на параллельное выполнение кода, оперирующего одними сущностями, то есть, вернуться в однопоток. Перхоть усекновением головы, иными словами. Нет, спасибо.
Сниппет с кодом могли бы и получше придумать, если уж так хочется ООП прополоскать. Потому что ООП там не видно. Видно, как одна функция вызывает другие. И видимо под капотом идет общение с базой. Насколько я понимаю, антиппаттерн read-modify-write может стрельнуть и в ООП и в модели акторов. Один актор сам с собой не будет конкурировать, но 2 актора на разных потоках вполне могут.
У меня и в мыслях не было полоскать ООП. Этот пример показывает, почему инкапсуляция методами не такая уж и инкапсуляция. Ну замените там вызовы БД записью в лог, ничего же не изменится.
2 актора на разных потоках вполне могут [конкурировать]
Никто в здравом уме не станет создавать два актора на одну сущность.
Этот пример показывает, почему инкапсуляция методами не такая уж и инкапсуляция. Ну замените там вызовы БД записью в лог, ничего же не изменится.
Есть разница между шарингом состояния самого объекта, который действительно защищен только честным словом. И шарингом состояния внешней системы: БД, лога и т.п. Это уже вне рамок языка, фреймворка или парадигмы.
Никто в здравом уме не станет создавать два актора на одну сущность.
Ни один истинный шотландец...
[…] нагадить в БД можно в любых условиях. Был бы доступ.
Ну вот в решении, под которым мы ведем обсуждение, ставшем темой моей предыдущей заметки, у прикладного (пользовательского) кода — доступа на запись в базу нет. А состояние самого объекта, которое попадает в базу когда надо библиотеке, — защищено на 102% от постороннего вмешательства, вы даже хаками не сможете его изменить.
Почему бы? Акторы имеют только локальные данные и посылку сообщений наружу, так что не могут.
Только локальные данные? А баланс из примера кто хранит? БД? А БД на чем написана? Ну если ООП язык то понятно, "фу, гамно... " и т д и т п. А если нет, и это тоже актор, то тогда как без мьютексов, семафоров, либо других примитивов будут синхронизироваться поступающие данные?
Предвосхищая возможный ответ "этого там не нужно, в эрланге всё асинхронно" e.t.c, допишу дополнение: Если что, все эти эрланги работают поверх ОС, которая "внезапно" использует очереди запросов и "внезапно" снова на мьютексах, критических секциях и вот этом вот всём, так не любимом функциональщиками. Надо признать, что обе самых больших ОС написаны на Си, который не разу не ООП. Но написаны в ООП стиле (особенно windows), где пользовательскому приложению предоставляется АПИ для создания и манипулирования, о Боже!, указателями на инкапсулированное состояние, т е по сути объекты! (Без наследования. Да, больше похоже на объекты в стиле Алана Кеч, но претензии автора поста и особенного его пример с таким же успехом и на такие объекты распространяются. Он же не конкретизировал...)
обычно ооп понимали по мануалу разница c и c++
Все верно. Именно проблемы с многопоточностью в Java подвигли Рича Хики на создание Clojure. Туда же вскоре двинулся и Роберт Мартин.
ООП и акторная модель многозадачности - вещи ортогональные. Ничто не мешает вам исполнять методы одного и того же объекта на одном и том же потоке, даже если вызывается он из другого.
Разумеется. Но и вот прям совсем никто не помогает, правда? Несмотря на заявленную инкапсуляцию и прочие плюшки.
Сделать объект однопоточным? Гениально!
Объект эксклюзивно владеет своей памятью и не допускает конкурентный доступ к ней, да.
Не нужно вводить людей в заблуждение, пожалуйста. Пока класс объекта специальным образом не объявлен замьюченным, ничего такого ни в каких языках не происходит.
Так это же медленно
То есть в ООП-шном подходе вы в лучшем случае получите ошибку компиляции, как в Rust. Собственно ваша критика сказанное в статье не отрицает, а подтверждает: "Классическое джаваподобное ООП не поддерживает многопоточность."
Вполне логично, потому что полагается на мутабельные структуры. А если эти самые мутабельные структуры из него выкинуть, то окажется, что и методы доступа не очень нужны. И получится ФП.
Ошибку компиляции при попытке пошарить беззащитную область памяти между потоками. И никакой ошибки в остальных случаях. Например, синхронзированный объект инкапсулирует внутри себя мьютекс, избавляя программиста от необходимости вручную следить за эксклюзивностью доступа к его памяти.
Это будет совсем не акторная модель уже. Тем более смешно тем, что изначальное ООП как раз и было тем, что сейчас называют акторной моделью.
Объект (идентичность) должен защищать свои данные. В частности, в императивном стиле объект получает команду измениться, но он ещё не изменился. И до завершения изменения он получает ещё другую команду измениться.
Тут либо считать его всё ещё той же идентичностью, с однопоточной очередью либо считать до и после разными сущностями.
1. Однопоточная очередь, классика ООП (Мьютекс): Плохо масштабируется. Становится узким местом.
2. Разные сущности (Иммутабельность): Отлично масштабируется для чтения, хорошо для записи.
3. Хитрые блокировки (Оптимистические блокировки): Лучший выбор для смешанных нагрузок, но деградирует при частых конфликтах
Оптимистическая блокировка лучше всего реализуется в ФП в STM. Она сильно зависит от деталей внутренней реализации, т.е. абстракция протекает и в общем случае сложность не оправдана.
Есть ещё вариант сразу писать объект на атомарных операциях так, чтобы он спокойно переносил многопоточное использование. Это не всегда возможно, и, зачастую, сложно, но проблему тоже решает.
Да, это хороший способ.
Исключительно lock free, без мьютексов нельзя сделать некоторые задачи.
Перевод денег с одного счета на другой к ним относится. Либо мьютексы и их производные либо eventual consistency. Может, ещё STM
Исключительно lock free, без мьютексов нельзя сделать некоторые задачи
Само собой. Но там, где требуется повышенная эффективность, лучше делать lock free (при условии, что это не повысит вероятность багов — а в этих штуках они допускаются легко и потом сложно отлавливаются)
Проблема ООП в многопотоке не в таких простых примерах как счетчик.
Правда, абстракция ООП протекла, но что мешает сделать мьютекс руками?
Но сложность и риски реализации эффективных блокировок быстро растет со сложностью объекта.
Пример могу дать, но из LLM, pardon
Да что вы все к примеру-то прицепились? Ну да, пример тривиальный, но суть-то раскрывает ведь?
Конечно, в жизни ошибка будет заковыристее.
Давайте свой пример, чего уж там.
Проблема не в примере, а в выводах, которые вы делаете на основании этого примера.
Да ну? Я делаю вывод: заявленная инкапсуляция — ложь. Демонстрирую это примером. В соседней ветке вам то же самое демонстрируют более навороченным примером с комментариями от LLM. Но вы не понимаете, о чем вам говорят, и продолжаете упорствовать.
Ну, бывает, мне-то что. Я не ставил перед собой цель сделать умнее каждого джуна. Я пишу для тех, кто хочет понять и подумать.
Понятно, нормально общаться вам корона мешает.
Я делаю вывод: заявленная инкапсуляция — ложь.
Очень странный вывод. Инкапсуляция - это сокрытие, и оно вот прям в вашем примере есть, и даже работает.
Ну началось. Окей, давайте поанализируем то, что в Вике написано:
Термин «инкапсуляция» может означать следующее в разных языках программирования:
механизм языка, ограничивающий доступ одних компонентов программы к другим;
языковая конструкция, связывающая данные с методами для их обработки.
Слово «инкапсуляция» происходит от латинского in capsula — «размещение в оболочке». Таким образом, инкапсуляцию можно интуитивно понимать как изоляцию, закрытие чего-либо инородного с целью исключения влияния на окружающее, обеспечение доступности главного, выделение основного содержания путём помещения всего мешающего, второстепенного в некую условную капсулу (чёрный ящик).
Итак, мы видим "черный ящик", а также 2 положения, первое из которых - буквально "сокрытие".
А теперь про второе, давайте посмотрим интересный пример (договариваемся сокрытие не использовать, все public).
class RectOrdPoint { // точка на определенной диагонали квадрата
public int coordX;
public int coordY;
public void setCoord(int coord) {
this.coordX = coord;
this.coordY = coord;
}
public void setX(int coord) {
this.setCoord(coord);
}
public void setY(int coord) {
this.setCoord(coord);
}
}
Ну вот у нас абстракция над точкой на диагонали в системе координат квадрата. Что мы знаем? При смещении точки по любой оси, она должна сместиться и по другой (иначе перестанет принадлежать этой диагонали).
Вроде все норм, с методами все отлично. Осталось только понять, кто запретит мне, как потребителю этого класса ручками поле поменять? Мы напишем в заголовке "отдельно координаты менять нельзя, координаты должны относиться друг к другу по формуле x=y". Ну вот это буквально "текущая абстракция", которой тут все пугают.
Как же защититься от такой закавыки? Сделать так, чтобы координаты могли меняться только совместно. Ну, например, объявить поля приватными, ибо "нефиг лезть". Я других действительно действующих механик не вижу, а конкретно эта называется "сокрытие".
Вот и выходит, что в отрыве от сокрытия в том или ином виде инкапсуляция в дикой природе почти не встречается. В концепции ООП, особенно в исходной формулировке Алана Кея, именно сокрытие и является краеугольным камнем.
«ООП для меня означает лишь обмен сообщениями, локальное сохранение, и защита, и скрытие состояния, и крайне позднее связывание». Алан Кэй
Из "троицы" наследование-инкапсуляция-полиморфизм в основе ООП только инкапсуляция и есть, причем в виде сокрытия.
«Я не понял» ≠ «Очень странный».
При несложных объектах (типа счетчика) разницы в скорости почти нет. Затраты и сложность на отправку сообщения в Akka и на захват мьютекса сопоставимы.
При сложных объектах всё кардинально меняется.
И вот здесь абстракция в ООП начинает не просто протекать, а прорывать плотину.
Почему для сложных объектов ООП с блокировками — это боль
Представим сложный объект "Банковский счет" с методом Transfer(BankAccount other, decimal amount)
.
1. Проблема композиции и Deadlock
ООП с локами:
Поток А вызывает
account1.Transfer(account2, 100)
. МетодTransfer
внутри себя делаетlock(this)
(т.е. наaccount1
).Далее ему нужно изменить
account2
. Он пытается сделатьlock(account2)
.В это же время Поток Б вызывает
account2.Transfer(account1, 50)
. Он захватываетlock(account2)
и пытается захватитьlock(account1)
.Результат: классический deadlock.
Решение? Ужасное. Нужно вводить глобальное правило порядка захвата блокировок (например, по ID счета). Абстракция "просто вызови метод" сломана. Вам нужно знать о глобальных правилах, чтобы просто перевести деньги.
2. Проблема гранулярности блокировки
ООП с локами:
Метод
GetHistory()
должен блокировать весь объект? Или только коллекцию с историей? АChangeAddress()
? АGetBalance()
?Вы начинаете вводить несколько объектов-блокировок внутрь одного объекта:
balanceLocker
,historyLocker
,_addressLocker
.Код превращается в минное поле. Забыли взять нужный лок — получили data race. Взяли не в том порядке — получили deadlock. Абстракция инкапсуляции трещит по швам, наружу торчат детали реализации синхронизации.
3. Проблема блокирующего I/O
ООП с локами:
Что если в конце
Transfer
нужно записать транзакцию в базу данных? Делать это внутриlock
— самоубийство. Вы заблокируете оба счета на время долгой сетевой операции.Делать это после
lock
? А если запись в БД упадет? Как откатить изменения в счетах, которые уже "отпустили"? Это требует сложнейшей логики компенсаций.
Как Akka решает эти проблемы для сложных объектов
Akka заменяет прямые вызовы методов асинхронными сообщениями.
Нет Deadlock'ов: Нет блокировок — нет дедлоков. Поток А шлет сообщение
Withdraw
акторуaccount1
. Тот обрабатывает его, меняет свое состояние и шлетDeposit
акторуaccount2
. Никто никого не ждет на блокировке.Гранулярность "из коробки": Каждый актор — это единица гранулярности. Сложная система разбивается на много мелких, простых акторов. Вам не нужно думать о
_balanceLocker
, у вас естьBalanceActor
.Асинхронность "из коробки": Операция с I/O — это просто еще одно асинхронное сообщение. Актор
account1
отправил сообщение вDatabaseWriterActor
и забыл про него. Он свободен принимать новые сообщения. Когда БД ответит (успехом или провалом),DatabaseWriterActor
пришлет ответное сообщение. Это идеально ложится на асинхронную природу реального мира.
Вывод

Для сложных, взаимодействующих систем подход Akka несравнимо лучше, потому что его базовая абстракция (асинхронное сообщение) гораздо лучше моделирует конкурентный мир, чем абстракция ООП (прямой вызов метода), которую приходится "чинить" костылями в виде блокировок.
Вместо вопросов "метод GetHistory()
должен блокировать весь объект? Или только коллекцию с историей? А ChangeAddress()
? А GetBalance()
?" Akka решает это не улучшением блокировки, а уничтожением самого понятия "сложный объект". Принцип: одна обязанность — один актор. Вы не создаете один гигантский актор BankAccount
, который делает всё. Вместо этого вы разбиваете его на несколько мелких, специализированных акторов.
...
... ну в этой части мне самому надо разобраться прежде чем копировать LLM
... ну в этой части мне самому надо разобраться прежде чем копировать LLM

боюсь мои комментарии к вашему фейспалму будут слишком токсичными
вы пока ничего толкового не утверждали, показывали только свои вопросы и непонимание
В место того, чтобы подробнее раскрыть свою мысль, вы копируете портянки LLM, которые сами до конца не понимаете. Это вообще нормально? Может мне тоже через LLM вам начать отвечать? :-)
Ну что-же, самое время напомнить вам про ваш же собственный комментарий
Давайте вы не будете говорить мне, как мне нужно излагать свои мысли, а я в благодарность не стану говорить, куда вам нужно идти.
А чё, молодец ваша ЛЛМка, столько воды налить в то, что я в один абзац уместил! Если стало понятнее — я только рад.
Принцип: одна обязанность — один актор. Вы не создаете один гигантский актор
BankAccount
, который делает всё.
Вот к этому надо скептически относиться. Как всегда, зависит от ситуации. Иногда бывают довольно развесистые акторы, и с этим ничего не поделать. Но как усреднение по больнице — тоже верно.
LLM подстраивается конкретно под меня, мне понятно. Более того, если мне не понятно я прошу переделать ответ. Ну вот так... Наверное те кто больше в теме могут думать более кратко, через красивые абстракции
Смысл не делать гигантский актор в том чтобы не блокировать его целиком надолго. Всё равно блокировки там внутри есть. Неявные, через логические зависимости, но есть
В классической модели — блокировок, как таковых, нет. Есть гарантия, что пока не выполнилась обработка сообщения, новая не запустится. И есть «почтовый ящик сообщений», в который будут падать все входящие.
Вообще, в асинхронных моделях, — блокировка это слово из чужого словаря. Отсылка сообщения мгновенна, а код принято писать так, чтобы синхронных (блокирующих) запросов было бы насколько можно меньше.
Очаровательно-инфантильный подход на особенно удачном примере "банковских транзакций" (к которым вас с вашей ЛЛМ на пушечный выстрел подпускать нельзя)
По порядку:
Представим сложный объект "Банковский счет" с методом
Transfer(BankAccount other, decimal amount)
.
Зачем мы его представим? В продакшене мы такое никогда использвоать не будем. У нас будет достаточно простой объект BankTransaction(decimal amount, BankAccount from, BankAccount to). Целиком всю высосанную из пальца проблему решит.
Проблема композиции и Deadlock: ее нет, это транзакция, она одной операцией выполняется. Мы лочим оба баланса разом.
Проблема гранулярности блокировки: ее нет. У нас нет сложного объекта. Есть объект BankAccountBalance, есть BankAccountHistory, есть BankAccountOwner - буковка S из замечательной аббревиатуры SOLID. И мы всегда знаем, что лочить.
Блокирующий I/O... Кхм, тут только цитировать:
Что если в конце
Transfer
нужно записать транзакцию в базу данных? Делать это внутриlock
— самоубийство.
Самоубийство - это писать банковскую проводку в БД вне лока транзакции. Вот тут прям настоящее самоубийство.
Предлагаемое вами решение - эпический фейл.
Вы предлагаете Аккаунт1.СписатьДеньги -> Аккаунт2.ЗачислитьДеньги -> ПисательВБД.ЗаписатьВБД. Теперь, внимание, вопрос: с первого аккаунта деньги списались, а Аккаунт2 зафейлился. Чо делаем? А если оба ОК, но БД упала - чо делаем? Если упростить, то буквально:
Как откатить изменения в счетах, которые уже "отпустили"? Это требует сложнейшей логики компенсаций.
Ах, блин, это же в недостатках логики с локами написано! Или ваша модель ортогональна решаемой проблеме (и более того, абсолютнейшим образом не подходит для ее решения)?
Мы лочим оба баланса разом.
Дальше я читать не стал, потому что залочить оба баланса разом в высококонкурентной параллельной среде невозможно.
transfer( Account source, Account target, Decimal amount )
synchronized( source, target ) {
source.ballance -= amount;
target.ballance += amount;
}
}
Нет у меня никакой базы. Я не работаю с проектами, в которых есть база, с этим прекрасно справляются специально обученные хомячки с интеллектом уровня робота-пылесоса.
Нет у меня никакой базы. Я не работаю с проектами, в которых есть база, с этим прекрасно справляются специально обученные хомячки с интеллектом уровня робота-пылесоса.
Что только подтверждает преположение, что у вас не только с ООП все плохо, но и с базами данных толку не хватило разобраться, хотя даже специально обученные хомячки с интеллектом уровня робота-пылесоса с БД прекрасно справляются.
Вас мало повозили вчера мордой по грязи, что вы опять тут вылупились?
Разве? Это вам и вчера и сегодня популярно разжевали, что у вас нет понимания ни в ООП ни в БД (о чем вы сами и написали). А после нескольких высокомерных закидонов с вами и другие нормальные люди перестали общаться и осталось вам только переписываться с LLM :-)
у вас нет понимания ни в ООП ни в БД (о чем вы сами и написали)
Врать-то завязывайте хотя бы. Я нигде такого не писал, хотя бы потому, что это не так.
Вы считаете, что данные могут храниться только в БД и других мест не может быть. Я правильно понимаю? И о долгоживущих Erlang процессах, которые могут быть запущены годами и хранить внутри себя состояние, вы тоже, судя по всему, не в курсе.
Мне очень интересно наблюдать, как людей корёжит, стоит слегка отойти в сторону от привычной парадигмы.
Сам придумал тезис, сам с ним поспорил. Вы уверены, что именно вас таким образом "не корежит"?
Вы что-то явно путаете. Никакой "тезис" я не придумывал. Если вы о долгоживущих процессах, то это придумано и реализовано в виртуальной машине Эрланга аж 40 лет как. И успешно используется начиная от сетевого оборудования и заканчивая джабберами и вотсапами.
Никакой "тезис" я не придумывал.
Ну как же? Вы считаете, что данные могут храниться только в БД и других мест не может быть. Я правильно понимаю?
Я не считаю, что данные могут храниться только в БД.
Место хранения внешних данных, это обычная абстракция с API для обращения к этим самым данным. А уж где они физически хранятся, в БД, процессе Erlang, который живет уже 40 лет в виртуально машине, распределены между разными узлами или вообще вычисляются на лету по мажоритарному принципу, это дело десятое.
Место хранения внешних данных, это обычная абстракция с API для обращения к этим самым данным.
Рыдаю. А чё только одна абстракция, может понадёжней будет ну штук пять взять?
И это, что такое «внешние данные»? Насколько имя пользователя для банковского приложения — внешнее?
И это, что такое «внешние данные»? Насколько имя пользователя для банковского приложения — внешнее?
Вот это вы странное спрашиваете. В контексте беседы "внешние данные" - это все данные, хранящиеся не в памяти приложения.
А если я его храню именно что в памяти приложения, но иногда (при изменениях) записываю в некое персистентное хранилище (не обязательно СУБД)?
Но для всех операций в коде оно хранится строго в памяти приложения?
Надеюсь, для вас не будет откровением, что любой код так или иначе работает с данными, расположенными в памяти приложения. Ну вот не умеет он иначе.
Если есть некие структурированные данные, с которыми вы работаете - это данные в памяти (они же локальные для приложения данные). В случае наличия персистентного хранилища, ну вот просто без вариантов, в этом хранилище будет копия ваших данных. И вот эта копия, получается, будет внешней для приложения.
Не суть есть важно, БД это, диск, лог, очередь - вообще по барабану, это копия, с которой надо так или иначе синхронизироваться. Вы можете брать значение из памяти и отливать в хранилище или спрашивать из хранилища значение и класть в память - вопрос только в том, кто "источник истины". Суть от этого не изменится, для работающего приложения значение в хранилище будет внешним.
Причем, тут уже без разницы, ФП/императивный подход. Надеяться, что ФП магическим образом само как-то решит за вас проблему консистентности данных - какая-то наивность. В случае любой программы есть данные в памяти, и данные за ее пределами.
Вот тут вы несколько несправедливы. Беседа таки в ключе исходной статьи ведется, а в статье таки в качестве примера BankAccount приводится. И вот хранить стейт банковских счетов в "долгоживущих Erlang процессах" (т.е. инмемори, я же верно понимаю) - ну вот такая себе идея.
В исходном ключе обсуждения таки да, данные о состоянии банковских счетов могут храниться только в БД. Мало того - еще и обязательно в транзакционной!
Не ткнёте пальчиком в то место исходного текста, где упоминается банковский аккаунт? Заранее спасибо.
И это, есть куча примеров хранения счетов в ивентлогах. Например, потому что так безопаснее.
в статье таки в качестве примера
BankAccount
приводится
Так чего, Господин Фантазёр, покажете, где именно в тексте я хоть что-то говорил про банковсие аккаунты, или признаете, что заврались вконец?
Ну и с понятием транзакции вы, видимо, тоже не знакомы. И реальных проектов не писали...
Зато мнение имеете.
Реальных проектов без базы с транзакциями не бывает? Ясно.
Ну вы же сами в пример привели класс BankAccount! И, внезапно, проектов по управлению балансами банковских счетов без базы с транзакциями - действительно не бывает.
Был бы другой пример, другой бы разговор был. А для банковских счетов есть 2 неотъемлемых требования:
Транзакционность
Последовательность операций.
Требование параллельно 2 операции по одному счету выполнять - ересь, высосанная из пальца. На банковские транзакции ФП ложится отвратительно, не надо его тащить. А ООП вы не понимаете, от слова "вообще"
Давайте вы отучитесь говорить за всю сеть. Вы не видели банковских счетов вне транзакционных баз данных? — Бывает, люди не рождаются на свет с широким кругозором.
Приведите пример счетов и банков без транзакций(что бы я ими не пользовался)
любая с конечной согласованностью подойдет и событийная туда же, "счета и банки" бывают с разными требованиями
Я думал хотя-бы на тему блокчейна пофантазируете))
любая с конечной согласованностью подойдет
Буду краток: нет.
и событийная туда же
Еще раз: нет.
банки" бывают с разными требованиями
И еще раз: нет.
банки это не только перекладывание денег - в перекладывании со счета на счет я бы поостерегся от конечной согласованности
выражайтесь точнее
Да блин куда уж точнее: "проектов по управлению балансами банковских счетов"
Управление балансами (читай, изменение циферки на счету) банковских счетов.
Ну ок, сократим формулировка: банковские транзакции.
Почему же? На самом деле она ровно такая и есть, несмотря на то, что внутри у (каждого участвующего) банка Oracle с транзакциями. Ну вот просто открываю я детализацию по своим покупкам, если мне интересно, и вижу, что деньги за вчерашнюю еду в супермаркете на самом деле висят в статусе HOLD. Их отпустит, когда пройдет клиринг (или как-то так, не помню уж точно). То есть для конечных юзеров - что меня, что магазина - создают впечатление, что деньги перечислились мгновенно и транзакционно. По сути, это оптимистический подход в создании видимости, а на самом деле там работает более сложная логика, которая может откатить это обратно через пару суток (а то и больше, если кто-то откроет диспут).
Выражаясь иначе, если разные банки и так друг другу выступают как акторы, никто не мешает делать того же самого для счетов внутри банка.
Их отпустит, когда пройдет клиринг
вроде, от такого отказываются уже
а то можно вспомнить понятие «банковский рейс» — комплекс операций по приёму и обработке платежей, которые проводятся несколько раз в день в соответствии с определённым графиком.
При межбанковских переводах Центробанк не переводит деньги моментально, а совершает их перевод только в определённые промежутки времени. Поэтому средства могут поступить на счёт не сразу, а в течение дня или даже нескольких рабочих дней.
Почему отказываются, если это основа работы международных платежных систем типа той же Visa? Остальное про Центробанк звучит как что-то вообще докомпьютерной эпохи.
Могу только примерно. Я мало отношения к банкам имею.
Можно делать платежи напрямую (золотом, гыгы), а чтобы на каждый платёж не возить золото можно делать расчет через клиринговый центр, который осуществляет как бы кэширование записей и реальные расчёты в конце торгового периода, дня или рейса (лет 300-400 назад это был физический рейс с золотом). Между странами специальные клиринговые центры, внутри через ЦБ.
Каждая транзакция через ЦБ обрабатывается индивидуально (это и есть «валовые расчеты» / gross settlement). Это долго и дорого, применяется к большим суммам.
Небольшие платежи ЦБ может реально завершать быстро, через систему быстрых платежей. Такие есть ещё свои у крупных банков. Ну и на закуску прямые переводы (неттинг), если банки доверяют друг другу а потом рассчитываются через клиринг.
ЦБ это гарант и финальное юридическое подтверждение платежа. На нём транзакция заканчивается. А все эти быстрые переводы к которым вы привыкли это ещё не завершенная транзакция, платеж можно оспорить и вернуть.
Как работают карточные платежи и палка и почему такой платеж можно оспорить чуть ли не спустя месяц - я не знаю.
Я тоже не знаю во всех деталях (чисто с какими тасками сталкивался, и это еще до даже идеи СБП было), но в контексте обсуждения достаточно того, что в большом масшабе нет никакого "лочим обе записи сразу в рамках одной транзакции", а делается по вполне акторной модели.
А вы устанавливаете транснациональные законы написания банковского ПО, или просто попердеть в лужу зашли?
Потому что ну вот я скажу: да, и еще раз да. В РФ мне с банками работать не доводилось, зато могу много порассказать про Европу и США.
Любой эквайринг в каждом втором американском банке.
Eventual consistency?
Там есть ненулевая вероятность не прийти к согласованности никогда. Хотя, вероятность может быть очень низкой и приемлемой далее в финансах. Но надо уметь считать риски. Интересно, как оно там
Странно как-то, почему-то в статьях они обсуждают что выбрать SQL или NoSQL, ну и очевидно пишут что SQL с его транзакциями лучше всего подходит для обслуживания денежных переводов)
Эти мифические «они» — это кто?
Все забываю что у вас гуглеж сломался, там даже графики сравнения транзакций и других аспектов есть)
А, всемирно известная Priyanka Gowda Ashwath Narayana Gowda, в своей курсовой работе? — Тогда конечно.
В продакшене мы такое никогда использвоать не будем
Лейстрид тоже был очень полезен как человек начисто лишенный воображения ©
Это игрушечный пример. Но уровень дискуссии такой, что все просто рефлекторно кидаются какашками в оппонента быстро и не совсем точно отвечают.
Проблемы ООП с многопоточностью начитаются с определенного уровня сложности модели. На простых можно решить вручную. На сложных придется использовать ФП или акторную модель.
Простой пример от автора тоже не годный.
...только хотел плюсануть (всё верно, за вычетом того, что я не знаю Akka), и тут ВНЕЗАПНО всё портит LLM.
ладно, не буду больше в LLM
буду гуглить и постить другую глупость, человеческую
почему гуглить? уж очень термины все в программировании нечеткие, просто бесит
"по определению дяди Боба микросервис то, а по определению тети Сони сё"
Это годная LLM, раз в год и палка стреляет :)
https://habr.com/ru/articles/920898/comments/#comment_28475174
Как обсуждение ООП так все бросают друг в друга ссаными тряпками
Да чтож такое опять?
Лол
Хм, ну ведь можно же без портянок LLM мысли формулировать? :-)
Причем под ними я подписываюсь на все 146%
LLM я знаю что читать не очень приятно, о чём я предупредил. Но тяжело сформулировать сложную мысль на бегу. Я же не сижу за компом целый день. Не хотите не читайте. Всё честно.
А вы не изволили сформулировать ничего
Предпочту вообще ничего не читать, чем читать тонны LLM мусора в комментариях. Тем более, когда автор сам не разобрался, что же он там нагерерировал.
Причем, я там в тексте в двух местах намеренно подставился, и люди, умеющие в ООП, должны были бы атаковать эти уязвимости, но нет: пока цепляются к пуленепробиваемым аргументам.
Два вызова метода
increment
из двух разных потоков приведет к тому...
два раза это два раза (с) и уже не важно сколько там было потоков.
Что забавно: в однопоточных языках (JS, python, PHP) объекты не очень-то и нужны. Они там выглядят как что-то инородное.
Я обожаю руби, в котором буквально всё — объект (даже примитивных типов нет). Там объекты выглядят как влитые, если что.
Но вот именно руби не претендует на все эти умные паттерны типа «инкапсуляции» и прочих. Там даже приватные методы можно задокументированными средствами языка вызывать извне. Можно подменить класс Integer
, чтобы он хранил текстовые представления строк на суахили. Можно буквально всё. И именно за это я его люблю. Вот в такой парадигме — ты программист, ты умный, ты разберёшься, и ты ответишь, если что — мне ООП очень нравится.
Ну приехали. И как же писать на JS без объектов?
Изначально прототипное наследование. ООП (классы) только потом прикрутили
Ок, объекты были. Классов изначально не было.
Технически, JS разделяет объекты и массивы, а массивы, очевидно, диффеоморфны объектам. Voilà.
Вот как раз технически они ничем не отличаются.
Семантика добавления элементов абсолютно разная, и, как следствие, объект тащит за собой хэш-таблицу, которая массиву не нужна. Размещение в памяти разное, прототипы разные. Но, конечно, можно трактовать слово «технически» достаточно широко, например: и те и другие являются примитивами языка.
Вы опять рассуждаете о том, о чём не имеете ни малейшего представления. Как таковых массивов в JS вообще нет. Есть лишь объекты с особым поведением свойства length и куча костылей в компиляторе, чтобы это не так сильно тормозило в большинстве случаев.
В былые времена для большого количества потоков мы использовали веблоджик, код под которым был однопоточный, но сервер умел раскидывать его по нодам и воркерам, создавая упрощенную модель. На данный момент это дорого, да и специалисты умеющие в работу с этим инструментом кончились, сейчас по хайпу пилить микросервисы, положив большой прибор на архитектуру с учетом ddd, и логику работы оркестрации процеса в угоду непонятного жонглирования в жрпц сервисах портами и урлами. Дальше выводы делайте сами...
А давайте двум плотникам дадим один топор, и заставим их делать один табурет? Да не, пробовали, они дерутся все время за топор, мешают друг другу. Ну может дадим им второй топор? Еще хуже стало - пальцы друг другу поотрубали... Мораль-то в чем? А, виновато ООП.
Если вы не понимаете разницу между виновато и непригодно — мне жаль.
Топор тоже непригоден для бритья, однако же остается отличным инструментом для своих задач, и эти задачи никуда не делись в 2025. Многопоточный хайлоад это до сих пор узкий сегмент, для которого свои технологии. Хотя вот та же винда наполовину написана на ООП с мьютексами, и ничего, работает.
Я нигде не говорил, что существуют технологии, на которых невозможно написать работающий код.
В тексте заявлено буквально следующее: в многопоточной среде больше половины так называемых «гарантий», краеугольных камней ООП, типа инкапсуляции и двух третей паттернов — превращаются в тыкву. Если вас это устраивает — флаг в руки.
Мне доводилось писать многопоточный довольно конкурентный код на руби с ручной обработкой пула потоков на си, и оно тоже работало. Понравился ли мне этот опыт и хочу ли я его повторить? — Нет, спасибо.
ООП решает другие проблемы. Инкапсуляция - для ограничения области видимости. Наследование - для переиспользования кода. Полиморфизм - для унификации интерфейсов. Никогда, нигде я не встречал обещаний, что ООП обеспечивает синхронизацию доступа к данным.
Не обеспечивает, а только мешает это сделать
Инкапсуляция - для ограничения области видимости
нет, вы читали не те учебники
Инкапсуляция это не столько инструмент (ограничение видимости), сколько цель (защита изменяемого состояния для соблюдения правил,инвариантов).
Этот спор — это отражение эволюции языков. От "вот инструменты, делай правильно" к "ты не сможешь сделать неправильно"
Вашу проблему с плотниками Генри Форд ещё 100 лет назад решил.
Кстати вполне себе в ФП стиле.
При этом бухгалтера у Форда как из одной книжки в другую циферки переписывали последовательно, так и продолжили переписывать. Ну потому что требования транзакционности еще есть...
Конвеер это временнóе разделение, т.е. опять мьютексы.
Это, кстати, любопытный вопрос :)
Из конвейера очень просто сделать не временно́е разделение, достаточно рядом с каждым рабочим установить столько лент, сколько он в состоянии обслужить, тогда пока Вася перед Петей возится со своим болтом, Петя разгребает другую ленту, от Маши. Это уже фактически акторная модель. Жалко, что у меня нет своего автомобильного заводика.
Акторная модель очень далека от производства в реальном мире. Никто не двигает ленты вокруг рабочего, это очень затратно. А если ваш рабочий делает, сколько хочет, и детали берет, откуда хочет - на линии будет полный бардак. Каждая операция должна быть четко тактирована для прогнозируемого результата и общей синхронизации. Я этим в своей дипломной занимался.
Но зато акторная модель годится для асинхронного распространения сообщений, слухов, сплетен, и прочего инфомусора, поэтому она отлично подошла для ватсаппа.
Автор у вас проблемы не с ООП, а с требованиями к коду. Если код должен что то безопасно сделать, то вы в своём коде должны эту безопасность обеспечить.
У вас требование безопасности есть, а в коде выполнения этого требования нет.
Если мы должны атомарно обновить значение при выполнении какого условия, то код который выполняет это обновление должен выполнять его атомарно, то есть проверить что условие выполнилось и только в этом случае проводить обновление значения.
В SQL для этого делается блокировка записи через SELECT FOR UPDATE, или можно делать апдейт на конкретное значение, с проверкой того что текущее значение не изменилось:
UPDATE balance SET amount=150 WHERE amount=200 and user_id=123
Как то так надо выполнчть изменение данных. Если это переменнная в памяти, или байты в файле, то изменение значения надо выполнять с аналогичными проверками. Условие выполняется ? Да - тогда обновляем. А что бы ни какой другой поток переменную не поменял используем блокировки, вы это прекрасно знаете.
ООП это метод. Вы его применяйте с учетом всех требований и программа будет работать как надо.
А если ваш код не обеспечивает атомарности, то пеняйте сами на себя, а не на ООП.
ООП это только способ установить границы того как можно изменять данные, ООП это подсказка программисту какие операции над данными можно выполнить. ООП не говорит нам как эти операции должны быть выполнены.
Вообще поэтому объекты и нельзя использоаать как DTO, именно поэтому мы должны сказать объекту: уменьши баланс на 50, если текущий баланс 200. И объект сам разберется как ему это сделать атомарно с транзакционной целостностью.
Говорить объекту: установи баланс 150, это значит брать на себя ответственность за то что баланс должен быть 150. Когда вы так используете объект, то это не ООП, это процедурное императвное программирование.
Комментатор, у вас проблемы с пониманием несложных текстов.
ООП гарантирует инкапсуляцию? Если вы согласны, что нет — тогда у нас нет разногласий.
Инкапсуляция, пожалуй, единственный неотъемлемый компонент ООП. Под инкапсуляцией подразумевается, как правило, ограничение доступа к внутреннему состоянию объекта извне. Остальное - наносное.
Вот это - гарантируется. Что не так?
ООП не гарантирует инкапсуляцию, ООП предоставляет некоторые средства, которыми можно достичь инкапсуляции (если хватит квалификации). Достичь ее можно и без ООП, с ООП просто удобнее. Как вы вообще себе представляете технически гарантию инкапсуляции в языке, в котором юзер может писать свои классы)
Как вы вообще себе представляете технически гарантию инкапсуляции в языке, в котором юзер может писать свои классы)
Да запросто: например, полная иммутабельность.
С полной иммутабельностью очень тяжело написать что-то полезное, состояние программы должно меняться, то что фп для этого изобретает всякие трюки глобально ничего не меняет. И даже если бы меняло - инкапсуляция не только про изменение состояния, но и про чтение того, что читаться не должно (ака деталей реализации).
Достаточно ли полезен Whatsapp? А Discord?
Кроме того, я просто привел один из тонны примеров в ответ на вопрос «Как вы вообще себе представляете технически гарантию инкапсуляции в языке, в котором юзер может писать свои классы».
У Whatsapp и Discord вообще нигде нет изменяемого состояния? Ни одной бд не используется? (она тоже изменяемое состояние)
один из тонны примеров
спасибо конечно, я тоже могу себе представить программу в которой ничего не меняется) Давайте я перефразирую если это действительно было неочевидно - есть огромный класс программ, в которых состояние должно меняться, и язык, если он не хочет быть узконишевым, должен это поддерживать
Иммутабельность — это свойство объектов языка, а не языка в целом. В полностью иммутабельном языке можно запросто менять состояние.
Зато на неиммутабельном языке не построить, например, конечный автомат с соблюдением гарантий.
везде где можно менять состояние, можно написать такой код, который позволит менять это состояние тому, кто это делать не должен теми способами, которыми это делаться не должно) про это и есть инкапсуляция, а не про конкретные способы, которыми этот эффект достигается
везде где можно менять состояние, можно написать такой код, который позволит менять это состояние тому, кто это делать не должен теми способами, которыми это делаться не должно
Не позорьтесь. Почитайте хоть немного про акторную модель, а потом ввязывайтесь в такие дискуссии.
Не позорьтесь
Хорошо, больше не буду)
Попробуйте включить абстрактное мышление и понять что делает акторная модель глобально, чем она фундаментально отличается от неакторной модели (может быть поможет подумать во что это в конечном счете компиляется и на чем исполняется), подумайте про Тьюринга vs Черча. Не знаю как ещё донести до вас элементарные вещи)
Вам вряд ли удастся донести до меня то, что полнота по Тьюрингу гарантирует вседозволенность, или что изоморфность всех типов лямбдам — как-то специальным образом раскрывает все данные.
Язык может быть триста раз полным по Тьюрингу, но если кусок кода работает с базой по read-only соединению, то поменять состояние ему не удастся. Внезапно, правда?
Инкапсуляция должна включать гарантии потокобезопасности. И в ООП это сложно, компилятор не гарантирует.
Возьмите более современный компилятор. 2к25 год на дворе.
Инкапсуляция должна включать гарантии потокобезопасности
Кому должна?)))
из определения
вот видите, вы даже не видите
цель инкапсуляции - защита инвариантов (и сокрытие реализации, например заменой публичных полей на геттеры и сеттеры под защитными правилами)
В многопоточной среде, если объект не потокобезопасен, любой вызов его публичных методов может привести к гонке данных и разрушить инварианты. Защита не сработала.
из определения
Покажите мне уже это определение, на которое вы ссылаетесь. Я ни одного подходящего не видел.
цель инкапсуляции - защита инвариантов
Нету у нее такой цели.
У вас очень странный подход: придумывать какие-то цели и гарантии, которых изначально никто не обозначал, и сетовать, что они не работают. Ссылаться на несуществующие определения - тоже отличный план.
Я вам сейчас страшное скажу: в определении ФП тоже ничего про потокобезопасность нет. Оно вообще не для того придумывалось, и не те проблемы решало. Потокобезопасность - это следствие из ограничений, в которых существует функциональное программирование (вы не можете сломать разделяемое состояние, если у вас его нет).
И вы, ссылаясь на следствие из парадигмы ФП, утверждаете, что оно же должно быть заложено в определении ООП? Вы в своем уме вообще?
в определении ФП тоже ничего про потокобезопасность нет.
Есть. В ФП вообще нет одновременных потоков. Потому что времени нет.👅
Покажите мне уже это определение, на которое вы ссылаетесь.
Анловики
In software systems, encapsulation refers to the bundling of data with the mechanisms or methods that operate on the data. It may also refer to the limiting of direct access to some of that data, such as an object's components.[1] Essentially, encapsulation prevents external code from being concerned with the internal workings of an object.
Encapsulation allows developers to present a consistent interface that is independent of its internal implementation. As one example, encapsulation can be used to hide the values or state of a structured data object inside a class. This prevents clients from directly accessing this information in a way that could expose hidden implementation details or violate state invariance maintained by the methods. - тут выделил для вас
Иммутабельность — это свойство объектов языка, а не языка в целом.
Вообще-то нет, это буквально основное отличие ФП и ООП, все остальные отличия - это следствия.
Ого, у вас в профиле написано «high load distributed systems»!
Любопытно даже стало, как можно заниматься хайлоадом, да еще и распределенным, и не иметь представления ни про акторную модель, ни про архитектуру вотсапа. Вы просто новые поды в кубер втыкаете, когда метрики зашкаливают?
Зачем мне иметь представление об архитектуре вотсапа, вы думаете это единственный месседжер на земле, или что месседжеры это в принципе какой то ультраинтересный класс систем, о котором должен непременно иметь представление каждый программист?
Вы просто новые поды в кубер втыкаете, когда метрики зашкаливают?
Раскусили)
Бесплатный совет: высокомерие ещё никому не помогало найти общий язык с другими людьми)
Зачем мне иметь представление об архитектуре вотсапа […]
Ну, это как бы самая распределённая система на сегодняшний день. Кругозор там, то-сё.
высокомерие ещё никому не помогало найти общий язык с другими людьми
С чего вы взяли, что я стремлюсь находить общий язык с другими людьми?
Ну, это как бы самая распределённая система на сегодняшний день. Кругозор там, то-сё.
Я нахожу системы с изменяемым состоянием намного более интересными)
С чего вы взяли, что я стремлюсь находить общий язык с другими людьми?
А вы попробуйте, может вам понравится. Не знаю кто как, а я к своим годам ужасно устал от этих тупых бессмысленных срачей.
Не обязательно иммутабельность для всего. Достаточно для того что обрабатывается параллельно. Функциональное ядро, императивная оболочка. Норм
ООП гарантирует инкапсуляцию?
ООП постулирует использование инкапсуляции. ООП это набор рекомендаций, ответственность за следование этим "правилам" / "рекомендациям" - это ответственность вашего кода.
ПДД предписывается движение на зелёный сигнал светофора, если вы ездите на красный, то пеняйте на себя. а не на ПДД :) Можно конечно требовать что бы каждый светофор был оборудован барьерными автоматами, но толку то ? Требуйте конечно, но решит ли это проблему ?
Я ничего не требую, в первую голову.
Я просто указал на то, что те механизмы инкапсуляции, которым все обучены, и которые предоставляются из коробки (наподобие «геттеры и сеттеры лучше прямого доступа к переменной») — рассыпаются в труху в конкурентной среде.
Откройте любую книжку по ООП, найдите там главу синглтон, и выполните предлагаемый код в кластере на N машин. Что получится? — N синглтонов.
Я всего-навсего подсветил проблему, но люди, конечно, не любят сложности, поэтому «надо использовать базу» и «сам виноват».
То что люди думают, что факт наличия геттеров и сеттеров означает, что у них теперь инкапсуляция - не вина ооп) Не понимаю в чем тут трудность, инкапсуляция это цель, а не гарантия, которую кто-то кому-то дает из коробки. Весь код по умолчанию однопоточный, если афтар не предпринял специальных усилий для того, чтобы он стал многопоточным, кои включают защиту разделяемого состояния, что в подавляющем большинстве случаев не представляет никакой сложности, если приходить в сознание хотя бы изредка. Все действительно сложные примитивы уже есть в стандартной библиотеке, ну или в крайнем случае в опенсорсе. Хотя конечно отстрелить себе ноги при желании можно чем угодно)
Весь код по умолчанию однопоточный […]
Волшебно. Я ровно с этим крайне вредным заблуждением и воюю. Почему у меня в машине 32 ядра, а код по умолчанию — однопоточный? Ну, кроме того, что авторы языка — не слишком-то дальновидные люди?
Почему авторам эрланга 40 лет назад ничего не помешало сделать код по умолчанию не только многопоточным, но и размазанным по кластеру? Почему эрлангу всё равно, в каком потоке и на какой ноде выполняется функция, а какой-нибудь джаве из коробки (впрочем, умные люди уже принесли туда Akka) — нет?
в подавляющем большинстве случаев не представляет никакой сложности
У нас очень разный опыт в этом плане. Синхронизация тысячи независимых гринтредов на семафорах и мьютексах — это очень непростая задача.
Почему авторам эрланга 40 лет назад ничего не помешало сделать код по умолчанию не только многопоточным, но и размазанным по кластеру?
Потому что в подавляющем большинстве случаев это никому низачем не нужно. Никто же не говорит что эрланг плохой инструмент для своих задач)
Синхронизация тысячи независимых гринтредов на семафорах и мьютексах — это очень непростая задача.
Тут нет никаких противоречий с тем что я написал)
в подавляющем большинстве случаев это никому низачем не нужно
Кхм. Боюсь, у вас очень поверхностное представление о том, что происходит в индустрии прямо сейчас.
Смотря о какой индустрии речь, я работаю в отечественных бигтехах последние 10 лет, тут ничего фундаментально нового не происходит)
Да, зависит от сложности бизнес логики. Пример со счётчиков ни о чём. Пример с диспетчерами самолётов уже что-то начинает показывать, но вроде тоже можно руками отловить. А вот если совсем много, то да, нужны гарантии.
Ну, может, потому что для подавляющего большинства задач однопоточность достаточна и при этом она проще? Давайте от однострочников на /bin/sh многопоточности потребуем. Да и закон Амдала никто не отменял.
для подавляющего большинства задач однопоточность достаточна и при этом она проще
Я не согласен с обоими утверждениями :)
Узок их круг, страшно далеки они от народа (с)
Я просто тоже так думал, а потом внезапно что-то понял, и мне стало проще в полностью асинхронном мире. Это как с заковыристым ударом на бильярде: вы триста раз пробуете какой-нибудь круазе, уже готовы шарами в стены начать кидаться… — а потом что-то щелкает, и всё: вы больше не понимаете, почему оно у вас раньше не получалось.
Что касается большинства задач — это тоже иллюзия: практически любой бэкенд рано или поздно захочет распарсить CSV, например. Опросить три сторонних сервиса и собрать результаты. И так далее.
То что люди думают, что факт наличия геттеров и сеттеров означает, что у них теперь инкапсуляция - не вина ооп
Верно. Но ООП и не помогает с инкапсуляцией в смысле потокобезопасности. Скорее, мешает.
А мешает, простите, как?
Вот есть поле, поле вы никак защитить не можете. Плохо.
Вот есть приватное поле, а также геттер и сеттер. Пока не понятно, зачем. Но, на самом деле, стало лучше, сейчас объясню, чем.
Теперь мы в геттер и сеттер вкорячили мьютекс. Теперь видно, чем лучше стало. А не было бы геттера/сеттера - было бы все еще плохо.
Т.е., получается, помогает? Ну, исходя из элементарной логики!
Почему это я поле защитить не могу? Еще как могу. Даже в джаве.
И как вы собираетесь, не используя инкапсуляцию, защитить поле? synchronized? Ну так:
Работает либо с методами, либо с блоками кода - т.е. поверх инкапсуляции.
Собственно по сути является инкапсуляцией.
Как без инкапсуляции защищать собираетесь? Обертывать в synchronized каждое обращение к полю ручками? Ну, удачи, чо...
помогает
конечно, помогает
но для сложных приложений так будет много ошибок
а что вам мешает в сеттере геттере ставить / проверять блокировку ? сеттер и геттер могут быть не на одну строку кода, могут быть любого размера.
Все обучающие примеры показывают саму идею, а не то как надо действовать всегда, всегда - надо голову включать. И вы показываете проблему, где код из учебника не годиться, ок. В производственном коде не всегда работает пример из учебника, ок.
Но это не значит что ООП не применимо. Включите голову, применяйте.
С возвращением! Наконец-то можно снова читать ваши срачи с нин-жином
Несмотря на то, что сам я ушел из большого ООП¹ ...
¹ Для занудных буквогрызов: я использую термин ООП не в первородном смысле, в котором его первым употребил Алан Кай, а в том, которое повсеместно распространилось сейчас с легкой руки Гослинга — наследование, инкапсуляция, классы.
Наверное, это случилось потому, что концепция классов/объектов была искусственно привнесена в языки программирования, в которых изначально ничего этого не было. В результате, произошла некоторая подмена понятий. И, вместо того, чтобы реализовывать изначальную концепцию, сделали то, что было всего проще. Это что-то вроде поиска под фонарём.
Кстати, о чём там говорил Алан Кей? Об обмене сообщениями?
... лично мне быстрее, проще и понятнее — реализовывать свои проекты на функциональном эликсире?
Скажите, пожалуйста. Что нужно делать, если кто-то заинтересуется этим самым Элисиром? Как его можно/нужно попробовать? Не означат ли всё это, что в Элисире нет проблемы, о которой Ваша статья?
И вот, наконец, меня озарило. Объектная модель всем хороша в однопоточной среде.
Тут, с очевидностью, возникает два вопроса.
Вопрос первый. Кажется, что многопоточность требует своих специальных средств, а у каждой модели (включая, очевидно, и ООП) есть свои естественные ограничения. Наверное, это должны понимать все специалисты.
Вопрос второй. Ваши рассуждения могут быть вызваны и тем, что Вам могут быть всё ещё неизвестны (на известные другим) способы решения заявленной проблемы. (Идущая ниже беседа может подтвердить или опровергнуть это предположение.)
Есть ещё и третий вопрос. А как можно попробовать многопоточность? Чтобы самому поэкспериментировать и самому почувствовать.
Забавно, что во всех примерах по многопоточности с помощью мьютексов и прочих condition variable, учат не распараллеливать задачи, а наоборот, приводить задачи из всех потоков в один. Что потом и делают ученики.
Дык технически мьютекс — это и есть бутылочное горлышко, куда помещается только один поток. Остальные всё равно замерзают, пока этот не пролезет.
Это не обязательно так, можно и красиво атомарно засемафорить разные потоки, но если они хоть сколько-нибудь связаны между собой — нужно быть прям гением, чтобы не принести дедлок.
Смотря как им пользоваться: я как-то завалил собес, используя мьютекс и condition_variable для каждого экземпляра потока, чтобы сделать блокировку двусторонней: любой из потоков может запретить главному пользоваться ФС, также главный поток может запретить начинать генерацию данных всем потокам до тех пор, пока текущие данные не будут сохранены. Но потенциального работодателя такой креатив не устроил. Судя по критике, ему нужно было именно бутылочное горлышко. А вопроса про смысл многопоточности, похоже, вообще не услышал.
P.S. опережая вопрос про дедлок, он был исключён, так как wait с одной стороны физически не мог начаться во время wait с другой. Это было предусмотрено.
опережая вопрос про дедлок […]
Я бы после одной этой ремарки собес бы закончил, и побежал бы к эйчарам умолять сделать вам предложение в течение часа максимум.
Про решение можно (и нужно) долго разговаривать отдельно: навскидку его довольно непросто сопровождать, если появятся дополнительные причины блокировок, но как стартовая точка для предметного разговора по душам (после того, как собес сам по себе завершен с положительным исходом) — отлично.
Не буду строить из себя жертву, работник я такой себе: мыслю на ассемблере, и смотрю как бы "сквозь код", а потому часто имею своё мнение там, где надо было бы и приподзаткнуться:🤣 Поэтому, мой удел - фриланс, там особо не смотрят на реализацию, так как большинству нужна картинка.
Я знаю достаточное количество контор, где очень ценят умение выразить своё мнение вместо «приподзаткнуться» :)
Бутылочное однопоточное горлышко тоже вариант если операции в нём очень быстро завершаются, доли процента от общей длительности выполнения команды.
А есть ли смысл распараллеливания в таких случаях?
Однопоточная часть занимает небольшую долю времени от общего времени операции. Я это и имел в виду.
Конечно, имеет. Можно вычислить несколько ускорится по формуле / закону Амадала
Интересно, не знаю такого закона, погуглю. А что скажете в случае, когда потоки занимаются подготовкой своей доли массива данных, а главный поток, по мере готовности, окучивает их. И главному потоку нужно заблокировать лишь один рабочий тред, который закончил свою порцию подготовки, к остальным нужно продолжать расчёты? Весьма реальная ситуация.
У акторной модели всё равно кусок кода идёт по однопоточной модели. Асинхронной, неблокирующей, с приятными бонусами, но всё же. Те же мьютексы, вид сбоку. Вот ошибиться гораздо сложнее, да.
Очередная статья на тему какой плохой ооп.
Ответ на вопрос статьи - что не так с ООП в 2025: с ооп все так. Пользуйтесь ооп на здоровье и в свое удовольствие и не слушайте никого.
Статью можно не читать даже
Да-да, лопата гораздо лучше экскаватора, а главное — привычнее и бензин не жрет.
Проблемы дешевле решить добавив нового железа и взяв ещё десяток тупых и дешёвых тушканчиков из "Сова эффективный менеджер"
И опять же - это ваши реалии. В моих реалиях проблем ни с тем ни с тем не возникает. Ну а если вы говнокод пишите то тут ни ооп ни остальные подходы не подойдут в принципе.
А где он, тот "экскаватор"? Назовите тогда уж, а то непонятно. Не похоже, что бы языки, подчёркнуто игнорирующие ООП, этим самым "экскаватором" оказались. По популярности они очень сильно далеко от топа с ООП языками... Выходит, все дураки, лопатами копать продолдаютЪ 😁
Если вам ооп доставляет боль, почему вы не перейдете на го, раст и другую строгую функциональщину?
Го имеет слабые возможности ФП. Для гарантий сложных правил бизнес логики не пригоден, только для простых сервисов request response.
Rust неплохая штука, тренируюсь. Но простые сервисы писать на нём это как гвозди забивать микроскопом
Какую бизнес логику не покрывает язык?
Для гарантий сложных правил бизнес логики не пригоден, только для простых сервисов request response.
Я даже не знаю как тут вопрос сформулировать, у меня мозг разжижается каждый раз когда я такое читаю. Хотелось бы пример какую мега логику нельзя написать на го или вообще на любом языке. Знаете, есть такое понятие как псевдокод, на нем пишется алгоритм, а потом реализуется почти на любом языке...
Любую логику покрывает любой полный язык. Вопрос не в этом, а в том, помогает он, или мешает.
гарантии инвариантов, например
С помощью enum можно сделать некорректные состояния непредставимыми на уровне типов.
Пример: Объект Соединение может быть enum Connection { Disconnected, Connecting(ip), Connected(socket) }.
Нельзя случайно вызвать read() у Disconnected соединения — такого метода для этого варианта enum просто не будет.
Код не скомпилируется. В Go это будет объект с полем state и кучей if-ов, которые легко забыть. Опять же, на простом примере этого не видно. Погрузитесь в сложный бизнес процесс и поймете.
Знаете, есть такое понятие как псевдокод, на нем пишется алгоритм, а потом реализуется почти на любом языке
нет, конечно. Реализации будут разными и в некоторых языках вы просто по статистике статистически будете делать больше ошибок.
Это в каком языке у объекта так можно? Вообще описание выглядит натурально как конечный автомат, который в состоянии Disconnected не принимает сигнал read.
И, пожалуй, я бы хотел язык, на котором нативно можно писать конечные автоматы со стейтами и сигналами (в смысле, чтобы компилятор мог проверки делать).
Rust, ADT
Я уже привык к такому синтаксису и пишу просто при проектировании
Много таких: Haskell, OCaml, F#, Scala, Rust... На самом деле так в любом можно, но не так удобно, просто
Языка у меня пока нет, но библиотеку такую я уже написал: https://hexdocs.pm/finitomata/readme.html
Синтаксис описания — Mermaid/PlantUML, компилятор делает все проверки (пока — почти все, со сложными ветвлениями руки не доходят разобраться, пока просто ворнинги в LSP редактора про то, что вот тут наворочено, перепроверьте).
Я давно перестал пить коньяк по утрам.
Ну комон, ведь раст как раз решает ваши проблемы с рейс кондишинами на уровне компилятора и в нём нет ненавистного ооп. Авторы раста как раз и писали давно, что в ооп никто нормально не умеет, поэтому ну его нафиг.
Вы меня спросили, почему бы мне не перейти, я процитировал в ответ расхожую фразу демонстрирующую неадекватную постановку вопроса.
Раст не решает проблемы с рейс кондишинами, см. кусок про дедлоки. Раст очень сильно переоценён.
Как можно противопостовлять ООП и многопоточность... Вы вообще не понимаете о чем говорите, что за большое ваше ООП тоже не понятно (из которого вы ушли).
Или вы не понимаете, о чем говорю я. Так тоже бывает. Например, я нигде ничего никому не противопоставлял.
Вы просто, опираясь на то, что ФП дает гарантии защиты в многопоточной среде (ввиду отсутствия разделяемого состояния), считаете, что ООП или процедурная парадигма должны давать таковые же.
Ну вот не должны. Потому что ООП - парадигма, построенная на наличии разделяемого состояния, со своими ограничениями и областью применения. Вся фишка в том, что в 90% случаев именно ФПшные гарантии вообще не нужны, а ограничения накладывают.
Аргументы про "у меня много ядер, займите все" - ну, такие себе. Есть десктоп, на котором эта утилизация нафиг не нужна. Есть кейсы (особенно в банкинге, пример из которого вы привели для иллюстрации), когда необходима именно гарантия последовательного исполнения операций - чем тут ФП поможет?
Код на 90% синхронный, и да, в современном серверном применении - тоже. Многопоточные гарантии "искаропки" хорошо, когда вычислительно-сложные задачи выполняешь. Их, как правило, в процессинге пользовательских запросов нет.
Вот и получается, что ФП-парадигма - это, может быть, и хорошо, но в подавляющей массе решений не нужно.
Вы просто, опираясь на то, что ФП дает гарантии защиты в многопоточной среде (ввиду отсутствия разделяемого состояния), считаете, что ООП или процедурная парадигма должны давать таковые же.
Ну вот не должны.
Половина присутствующих считают что инкапсуляция включает потокобезопасность, а половина что нет. Да что же с ООП не так?!
Ну т.е. половина в своих суждениях опирается на определение ООП и какую-то литературу по ООП-парадигме, а вторая - открывает любой текст, но видит перед собой ФП-методичку.
То, что ФП в качестве побочного эффекта дает потокобезопасность, совершенно не значит, что ООП должно давать те же либо сходные гарантии. Список гарантий ООП нужно искать в литературе, посвященной ООП, а не в ФП-откровениях.
Сначала появилось ФП, потом процедурная парадигма (плавно переходящая в структурную), потом ООП, и только потом - многопоточные системы. Вопрос о том, что делать с многопоточностью младше любой из этих трех парадигм. И они дали разные ответы на этот вопрос
ФП: у нас нет разделяемого состояния, можете не заботиться о потокобезопасности (пока не понадобится shared state/shared resourse, но с ними из без потокобезопасности больно было).
ООП: вот вам инструмент, вы его знаете, он с вами давно, называется "инкапсуляция". А вот новые, называются "примитивы синхронизации". Защитите себя сами, вы на это и программисты.
С хрена ли тут какие-то гарантии дополнительные у ООП появиться должны были - решительно непонятно.
убедили, ООП плохой инструмент, пользоваться им не стоит
Сначала появилось ФП, потом процедурная парадигма (плавно переходящая в структурную), потом ООП, и только потом - многопоточные системы.
Хватит уже чушь нести.
Сначала появилось ФП, потом процедурная парадигма (плавно переходящая в структурную), потом ООП, и только потом - многопоточные системы
Да ну неправда. Первым был императивный Фортран, и многопоточностью занялись уже в 70-е, если не 60-е. Даже современные принципы виртуализации были сформулированы и у IBM уже работало в 70-х.
Я не приводил пример из банковской сферы. Я не опирался на то, что ФП дает какие-то гарантии (не дает). Я вообще не про ФП.
Ну пример 5000+ параллельных юзеров норм хайлоад? Проект на c# микросервисы одна бд. Все сделано на ооп и dependency inj. Работает норм. Есть и собственные workflow в коде. Нет никаких проблем с ооп. Проблемы были только с теми методами или запросами к бд которые выполняются долго. Это приводит к перегрузке очереди запросов. Так что главная вещь это оптимизация.
микросервисы это почти наверняка простые запросы, без сложных связей
300+ таблиц в бд. В каждой табличке по 2-10 связей норм?) Про бизнес логику не говорю) это медицинская система
А я где-то говорил, что это невозможно?
Можно и микроскопом шуруп заколотить, было бы усердие.
Все таки, есть способы привести высокосвязный процесс и данные к низкосвязным
Например:
исключить кольцевые зависимости
N-связность привести к нескольким 2-связностям, пожертвовав ACID
и так делее...
Но чем сложнее система тем такие способы изоляции хуже работают
Так тут проблема не ООП, а в управлении состоянием, например на Rust это надо очень сильно извратиться чтобы добиться проблем типа гонки данных на мьютексе, прям осознанно и больно извратиться, а пример с 16 потоками решит любой, кто досчитал до тем Arc/Rc, Mutex вообще не встретив какого-то сопротивления
А раст у нас что, представляет классическую традиционную ООП-парадигму? В расте у вас будут бругие проблемы, когда захочется запустить 1000 и больше потоков (так бывает), вы столкнетесь с дедлоками в самых неожиданных местах. Но всяко проще, чем на классическом ООП.
Я даже маленько опешил от такого наезда...
1) что такое классическое ООП? Автор концепции ООП сказал что C++/Java это вообще не то что он имел в виду и пошёл пилить Smalltalk
2) дедлоки это архитектурная проблема, а не проблема языка, и на чистых функциях хаскеля вы можете добиться дедлока, что показано и через машину Тьюринга и через исчисление Чёрча
3) Еесли мы говорим про обычный компьютер, то 1000 потоков это антипаттерн, у вас на переключение задач в ОС уйдет больше времени, чем на саму задачу, плюс под каждый поток по 2 метра отрезать? Не жирно, есть корутины, есть лёгкие потоки...
Вообще хочется конкретного кода, в чём там у вас проблема
⓪ какого еще наезда? почему наезда?
① я трижды оговаривался, что использую укоренившийся после Гослинга термин, а не Аланкаевский
② язык легко может от них застраховать, в 99% случаев
③ я про гринтреды, конечно
④ у меня вообще нет никаких проблем
дедлоки это архитектурная проблема, а не проблема языка
Костылями неподходящими инструментами можно решить любую проблему, но с ростом сложности стоимость и риски растут очень быстро.
2 метра виртуальной памяти ничего не стоят, это просто ограничение сверху на глубину стека.
А что там за проблемы у них?
Да всё те же: мьютексы вместо мэйлбоксов. При высокой связности такая модель неизбежно рано или поздно приводит к циклической зависимости, а там дальше либо есть гений, который это разруливает каждый раз руками, либо (как и случилось с проектом, ради которого авторы вообще придумали раст) — «да что за день сегодня! вообще ничего не получается!».
Да откуда дедлокам то взяться? Ресурс один - блокировщик один на чтение и запись, пока не завершится работа с данными. Если нужно, можно сделать парадлельное чтение, но на запись нужен второй блокировщик, который и чтение запретит. И это уже реализовано в БД и не связано с ООП вообще ни как
Отсылки к акторной модели и всему такому особенно смешны в контексте того, что она и есть классическое ООП, ибо Alan Kay создавал вовсе не С++ с Java. Так что вместе с, например. вот таким:
Среда, которая буквально приглашает разработчика ошибиться и разрушить тотальность функций потенциальным дедлоком — не должна иметь права на существование в принципе.
статья похожа на толстый наброс, потому что эта цитата, например, вообще про то, как работают процессоры и любой низкоуровневой код, например на Си в ядре ОС, в котором - и на котором - эти самые примитивы синхронизации еще надо создать.
И да, шо такое "тотальность функций"?
шо такое "тотальность функций"?
определенность, вычислимость на всей заявленной области допустимых значений
причем, вычислимость за конечное время )
Я триста раз оговорился же, что использую определение, прижившееся после Гослинга, а не оригинальное Аланкаевское. Иначе я бы еще триста минусов отхватил за незнание того, что такое ООП, плавали, знаем.
Вот про тотальность функций: https://docs.idris-lang.org/en/latest/tutorial/theorems.html#totality-checking
Извините за оффтоп, но трудно молчать. Я стал так часто полагаться на ИИ в вопросах выбора, что такие статьи ввергают в уныние. Словно мы с вами в маленьком клубе радиолюбителей: ведём философские беседы, когда ИИ сделает как-нибудь, и оно будет работать достаточно хорошо.
А потом всё быстро угаснет и останется в прошлом.
Вот и выросло поколение, которому нечему научить даже тупого ИИ.
Магическое ритуальное мышление
Я решаю задачи, которые ИИ и близко решить не в состоянии (и не сможет, пока вместо LLM не придумают адекватный подход).
Я честно пробую каждый месяц или типа того, но «улучшить документацию» — это единственное, что можно хоть как-то передоверить LLM, остальное, даже тесты, — для моих задач — приводит к лютому бреду.
Придумал пример для сложной реализации потокобезопасного решения и при этом быстрого. .
Движок электронных таблиц типа Google sheets. Я такое делал на практике на epplus. Там уровней блокировки очень много
На весь файл
На лист (формулы между листами редко и можно eventual consistency потому что пользователь не видит одновременно несколько листов, если только не придумает открыть 2 браузера рядом. Да и внутри Excel файла каждый лист это отдельная XML
На набор ячеек с формулами, которые ссылаются на изменившуюся ячейку
В общем, было два варианта: с блокировками когда однопоточный движок разделяли между пользователями и вариант создавать движок на каждую веб сессию с синхронизацией через postgres, с предельным временем жизни, чтобы не оставались зомби при разрыве сессии.
Скорость, конечно же так себе, тысяча операций в секунду на файлах 100 столбцов*10000 строк*10 вкладок.
Чужая ООП библиотека, особенно не кастомизировать...
При чем тут ООП я вообще не понял...
Объектная модель всем хороша в однопоточной среде.
Что? На C#, Java, C++ например ООП нормально работает в многопоточности. Честно, дальше не читал, но судя по комментариям, там перлов много. Уже молчу, что по логике автора, те же проблемы могут возникать вполне себе и при ФП. Потому что конкурентоспособность никто не отменял и изменение одного и того же объекта может вызвать такие же проблемы.
Вообще, проблема многопоточности имеется в основном на web бэкенде. Изначально подразумевалось, что потоки должны быть изолированы и потом уже добавили возможность получать или изменять данные из одного потока в другом при помощи всяких invoke. Однако, на серверах может крутиться множество потоков, либо, как с node js, один основной поток и очереди на выполнение в нём кода. При этом все эти потоки по сути выполняют один и тот же код, с одними и теми же данными, что и является проблемой организации конкурентного доступа, а не парадигмы. В других многопоточных приложениях, обычно не такое большое количество потоков и важнее синхронизация данных между ними. Например, игра в которой game loop это один поток с рендером и логикой, звуки про омываются во втором потоке, ui например это третий поток. Пример стрёмный, но в контексте хороший. В такой игре мы имеем изолированные потоки, выполняющие разный код с разной бизнес логикой, их всего 3 и важна синхронизация данных между ними. А на бэкенде код и бизнес логика одна порой на сотни потоков, что приводит к другим проблемам.
Я не работаю с вебом и не противопославляю ФП — ООП.
что же все-таки не так с ООП, если лично мне быстрее, проще и понятнее — реализовывать свои проекты на функциональном эликсире
И далее разговор о проблемах только ООП, и не слово о проблемах с аллокациями памяти и сборщиками мусора, работой с общими состояниями, тот же спагетти код с монадами в сложных проектах. Ваша статья воспринимается, как противопоставление ФП против ООП, даже если вы так не считаете.
Иными словами, в ООП нет ничего прям плохого, но только пока вы не погрузились в сильно связанный хайлоад.
Может вы и не работаете с вебом, но это была ваша претензия к ООП в контексте веба. Ведь термин хайлоад, чаще всего применяется в контексте работы с веб бэкендом.
термин хайлоад, чаще всего применяется в контексте работы с веб бэкендом
Чё?
не слово о проблемах с аллокациями памяти и сборщиками мусора, работой с общими состояниями, тот же спагетти код с монадами в сложных проектах
У меня нет проблем с аллокациями памяти, тем более — со сборщиками мусора. Общих состояний нет в принципе, общее состояние — это ужасный антипаттерн. ПРо проблемы с монадами слышу впервые.
А вариант примера решения без ооп будет? И как объект "диспетчер" оказался в управляемых объектах? И ооп в этом примере за уши притянут. Согласен, проблемы есть, как и есть способы их решений, но пример на псевдокоде - это вообще не ооп ни разу, обычное процедурное программирование.
Примера решения чего? У меня практически в каждом тексте кроме этого есть ссылка на мои библиотеки, которые решают те, или иные задачи.
Что такое «объект „диспетчер“», какие еще «управляемые объекты»? Вы о чем вообще?
Пример на псевдокоде — строго про инкапсуляцию, как там и написано.
Там не совсем инкапсуляция, и к ООП он не относится, ну да ладно. Я имею в виду пример с самолётами, он слишком натянутый, при нормальной архитектуре в ООП такого не должно случаться, если уж зависимость так необходима. Да, дедлоки случаются, обычно при добавлении функционала, которого изначально не было предусмотрено, но это лишь причина пересмотреть архитектуру приложения. При работе с бд в примере, вам нужно создать объект типа похожего по функционалу на lockguard для блокировки изменения данных строки в БД. Просто ещё один класс. И он уже будет освобождать строку был в деструкторе. Это один из вариантов решения.
Я имею в виду пример с самолётами, он слишком натянутый,
Пример с самолётом нам принесла жизнь, простим уж старушку-природу.
при нормальной архитектуре в ООП такого не должно случаться, если уж зависимость так необходима
Ну да, ну да. При нормальной архитектуре языка и среды такого действительно случаться не должно, а перекладывать эту заботу на программиста — типичный пример плохого дизайна.
Автор конечно же мастер провоцировать срачи в комментариях, моё уважение. Ну и очень интересно - так а что у вас за задача на 16 потоков? Если что, высоконагруженную многопользовательскую систему тоже писал, а даже собственные примитивы синхронизации. Разумеется, в парадигме ООП)
У меня типичная задача не на 16, а на 16К (и больше) — ну не потоков, гринтредов, эрланговских процессов, но сути тут это не меняет.
Прилетают из стороннего сервиса со скоростью 60К/сек через раббит обновления примерно 20К разных сущностей. Они зависят друг от друга, их нужно обрабатывать, хранить историю за последний час и всякое такое.
Тут всё же надо сделать уточнение - 20К разных сущностей или экземпляров этих сущностей? Ну например если у меня в системе есть читающие и пишущие потоки в разделяемую бд - то это всего лишь 3 сущности вне зависимости от общего количества этих потоков.
Зависимых друг от друга экземпляров. Пример из реальной жизни: курсы валют из стороннего источника. В принципе, сам по себе курс — почти изолированная сущность, но его надо проверять и/или обновлять в зависимости от соседей: вот пришел курс для экзотической пары Kenyan Shilling (KES) / South African Rand (ZAR).
Такие пары напрямую торгуют крайне редко, поэтому надо удостовериться, что там не фантазия провайдера на тему фазы луны в меркурии. То есть, взять KES/USD, USD/ZAR, перемножить их и сравнить с пришедшим. А если KES/ZAR не приходит уже полчаса — так тоже бывает — то напрямую посчитать через мажорые пары.
База не выдержит такую нагрузку на запись/чтение без очень сильных ухищрений, да она и не нужна тут.
Разумеется, в парадигме ООП
Я же не говорил нигде, что это невозможно. Можно, конечно. Просто ну вот сразу: вам пришлось заморачиваться примитивами синхронизации, а у меня гарантии целостности и отсутствия гонок из коробки. Что удобнее?
Ну лично мне удобнее самому контролировать нюнсы многопоточной синхронизации, а собственные примитивы синхронизации и писались именно для удобства. Решения из коробки не всегда лучше и предпочтительнее кастомизированных. Я например не использую async/await в c# и даже считаю их использование плохой практикой.
Вот хорошее чтиво перед сном: https://getakka.net/
async
/await
— это костыли, конечно, тут спору нет.
На тему срачей в комментариях (я не жалуюсь, мне не 16 лет, но причины настораживают):

Полагаю, у данного срконструктивного обсуждения два корня:
У понятия «ООП» нет единого строгого логически верного определения, с которым согласны все. Разные участники дискуссии подразумевают разное содержание понятия «ООП»
Высоковероятно, что построение многопоточных приложений не входило изначально в круг задач, которые должна была решать парадигма ООП. Подобно тому, как, например, динамика кварков не входит в круг задач, решаемых Общей Теорией Относительности
Дык а я про 2) в тексте прямо написал.
Возможно, я ошибаюсь, но из текста вырисовывается несколько иная позиция.
Иными словами, столь тщательно выстроенные годами паттерны, абстракции и вообще краеугольные камни ООП — рушатся при распределении нагрузки по всем процессорам. Инкапсуляции из коробки больше нет.
Иными словами, в ООП нет ничего прям плохого, но только пока вы не погрузились в сильно связанный хайлоад. Вот тогда придется написать заново неспецифицированную, глючную и медленную реализацию половины акторной модели². ООП был современной и крайне удобной парадигмой в девяностые, когда многопоточное связанное программирование было уделом фриков. В 2025 эта парадигма всячески сопротивляется распараллеливанию задач, что при современной доступности количества ядер на единицу выполнения кода — халатность, граничащая с преступлением.
То есть не «решение проблем построения многопоточных приложений не входит в круг задач ООП», а «ООП не только не помогает, но и мешает решать задачи построения многопоточных приложений».
Это два разных суждения: в первом случае парадигма ООП имеет, если можно так сказать, нейтральную позицию по отношению к многопоточности, а во втором случае — ярко выраженную негативную позицию — она не «не помогает», она «делает хуже».
Мешает не ООП, а мифы, с ним связанные.
Вот смотрите: мне тут только ленивый не сообщил, что транзакции субд гарантируют целостность данных, и мне нужно перестать пыхтеть, и пойти учить транзакции.
Но транзакции решают эту проблему не всегда. Если просто перехватить исключение бд из прикладного кода и повторить запрос, есть риск провести транзакцию дважды. Но Свидетели Великой Транзакции об этом подумают не всегда, потому что они слепо верят.
С ООП так же. Хрен с ней, с инкапсуляцией Синглтон ломается. И разработчики к этому не готовы. Не все, как минимум.
Есть и параллельная проблема: ООП поощряет разделяемое состояние. Это я называю прямым сопротивлением многопотоку.
ООП суть продукт программистов практиков, теоретической научной базой не отягощенный. И современные проблемы не решающий.
Зато понятный и простой в 90% задач. Бери больше, кидай дальше
Но сложные и быстрые системы на нём не построить
Под термином "ООП" каждый может понимать что-то свое, а в большинстве языков программирования могут встречаться различные, зачастую даже противоположные концепции, одновременно.
Однако ООП и многопоточность это всегда перпендикулярные и никак не связанные между собой понятия и любые попытки натянуть одно на другое, это из области совы и глобуса.
Что не так с ООП в 2025