Pull to refresh

Comments 458

Разве смысл Синглтона не в том, чтобы он был глобальным и НЕпеременным?

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

Ну сейчас разработчики языка и стараются это убрать, ввели сначала record-ы, т.е объекты без поведения, потом короткую запись, и сейчас можно писать так - public record Some (int A, string B );

Разве смысл Синглтона не в том, чтобы он был глобальным и НЕпеременным?

тогда это называется константа...

Мы же говорим про объект который синглтон? Он то создается один раз, а какие-либо его поля/параметры/данные могут меняться, а могут и нет, как пример, во время запуска приложения создается синглтон с параметрами конфигурации. Если можно создать второй объект и заменить им первый, то это не синглтон.

Если можно создать второй объект и заменить им первый, то это не синглтон.

Это синглтон. Глобальна же только ссылка! При замене объекта у всех потребителей по той же ссылке будет лежать новый объект.
И вот так "подменять" синглтон, очень удобно, когда работаешь с общим разделяемым ресурсом, который может отключаться/падать. Например, канал связи экзотический какой-нибудь ("переоткрывать" закрытый объект соединения - очень плохая практика).

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

Тогда это уже не синглтон, это просто глобальная переменная.

Рекомендую сходить сюда и прочитать буквально первые два абзаца)

Ну да там всё правильно написано. Особенно про "глобальную точку доступа". Ничего из того, что я написал этому не противоречит.

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

Что подразумевает, что синглтон от перезаписи защищен и что он роджён один, будет один и умрёт один. На него можно сохранять ссылки, указатели и бог знает что еще в программе и это обязано быть безопасным. Вы же пишете:

И вот так "подменять" синглтон, очень удобно, когда работаешь с общим разделяемым ресурсом

Что автоматические создаёт противоречие между определением синглтона и вашим его пониманием.

При замене объекта у всех потребителей по той же ссылке будет лежать новый объект.

Суть синглтона как раз в запрете создать новый объекта того же класса. Вы же описываете что-то другое.

Извне создать и нельзя.
И смысл синглтона немного не в этом - его задача предоставить единое целостное глобальное (в определённом смысле, контексте) состояние. См. например, в ASP.NET Core. Вы там можете хоть сотню экземпляров синглтона создать - по штуке на скоуп (т.е., например, один живёт в обработке запросов, другой в фоновых процессах)

А запретить создавать объекты класса в языках с рефлексией вы всё равно полностью не сможете.

Не важно как это реализовано в каком-либо языке. Речь о шаблоне проектирования. Да и при беглом взгляде на указанную статью, я не заметил противоречий, там написано то же самое, только про сервисы.

Singleton - сервис создается при первом запросе (или при запуске ConfigureServices, если вы указываете инстанс там), а затем каждый последующий запрос будет использовать этот же инстанс.

UART прот в микроконтроллере (при заранее зафиксированном устройстве, с схемой, печатной платой и серией выпущеных образцов) глобальный, не переменный и не константа, потому что имеет поведение.

Это идеальный пример синглетона. Но всё равно я часто слышу людей, которые хают этот паттерн даже в таком случае.

Не уверен что этот пример настолько уж идеален, и что UART-подключений ну вот никогда не может стать два.

Может. И даже станет. И это всегда будет два синглетона, потому что у них будут разные настройки и задачи: они будут подключены к разным устройствам и проводам.

Приведу пример, например :).

У нас есть 2 порта - отладка/консоль и модем. Для синглетона отладочного порта принемать строки это норма. А для синглетона с модемом - ошибка, ибо он должен принемать только пакеты для модема, и ничего больше. И если програмист напишет их как один класс и два объекта то у него будет и оверхед, и возможные ошибки. Да, он потом завернёт это в защиту, но у него в кишках всё равно будет реализована возможность послать модему строку, которой не должно быть.

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

Лично мне нравится ООП в трактовке Алана Кея, тогда можно делать модули, возможно, каждый в своем потоке, и к каждому цеплять очередь входящих сообщений. Если нужен сообщению контекст, пихаем его в сообщение.

Если у вас есть синглтон для отладочного интерфейса, то это нормально. А вот синглтон для протокола UART в принципе - это хрень.

Я же писал "UART порт".

"UART порт" - это пара ножек у контроллера. Драйвер UART порта, в моём понимании, должен отвечать за приём и передачу произвольных байт по этим ногам. А вовсе не за список допустимых команд модема и форматирование отладочных строк...

Вот на эту самую пару ножек у МК и нужен синглетон.

Тайминги в высокоскоростном байтфлове, DMA и мониторинг пауз для пакетов я бы тоже запихнул в слой драйвера. Потому, что он так же напрямую связан с регистрами МК. И тут получаются очень разные драйверы для очень разных UART.

Разные драйвера - возможно (хотя я бы всё равно попробовал использовать один наилучший или хотя бы настраиваемый). А вот с тем, что они должны быть синглтонами - я не согласен.

Смысл сингелтона, что тесли переменная или класс уже в глобальной видимости и крутится в памяти, не создавать новый экземпляр, а использовать уже запущенный, так и до утечки памяти не далеко...

некоторые вообще считают, что синглтон в программе должен быть один.

синглтон в программе должен быть один.

...и это: :-)

class Program 
{
    static void Main(string[] args)
    {
      ...

Пишу на С++ последние лет 20, до этого ещё Delphi лет 10. С годами полностью отошёл от ООП в сторону data-driven design. Классы — практически структуры, из методов как правило только декоративные геттеры. Всё остальное — это просто функции с понятными названиями, сигнатурами и операндами-объектами таких вот классов-носителей данных и состояния. Получается, очень легко дышится и чистенько — состояния изолированы в структуры, логика изолирована в функции. Конечно, в C++ всё довольно печально с ООП как таковым, поскольку нет механизма extension methods — это когда вы собираете методы в класс из разных единиц трансляции; из-за этого, обычные функции C++ значительно удобнее методов класса. Но действительно с годами я начал понимать, почему многие мои профессиональные друзья "немного за 50" перешли вообще на чистый С. За мишурой ООП и других парадигм типа шаблонного мета-программирования мы теряем важное — эффективно расходовать время жизни. Оказывается, убрав лишнее (например, перейдя с С++ на С), мы отсекаем ненужное и фокусируемся на чистом результате. Я не хотел бы полностью отказываться от С++, но объективно сократил использование его фич до очень ограниченного подмножества, это очень здорово помогло.

Конечно, в C++ всё довольно печально с ООП как таковым, поскольку нет механизма extension methods

Можно с натяжкой сюда притянуть перегрузку операторов.

Да :) И где-то рядом ADL (Koenig lookup). Ну вот оно всё такое "не-до", на мой взгляд в С# (сильно позже конечно же) получилось продуманнее. А в С++ конечно всё это достаточно забавно, когда только начинал, пытался найти стройный замысел в хитросплетениях private/protected/public/friend, но с годами настолько это прочувствовал, что понял – а нет этого замысла. Получилось как получилось.

В некотором смысле тут нет противоречия. Если тип - это множество допустимых значений и операций над ними, то есть ли разница, писать o.f() или f(o)? Скажем, в Ada 95 поддерживается только второй синтаксис (при этом с динамической диспетчеризаций), и язык по мнению авторов вполне себе поддерживает ООП.

Да, это классная дискуссия! Помню ещё на заре Рунета горячо обсуждали с одним товарищем, чем len(L) лучше L.length() :) Не с вами, правда, Максим (я вас с давности тут читаю). А Питон тогда ещё был не вполне популярен, почти что маргинален. Думаю сейчас об этом и понимаю: действительно в C с этим проще, ведь решение о том, тащить ли метод в класс или сделать его внешним — его принимать не надо, оно уже и так принято.

Я думаю об этом как о проблеме скорее вот в каком ключе. Пусть у нас есть некоторые связанные данные, которые мы будем обрабатывать "поколоночно". На современных векторных архитектурах это типично. Тогда, с подходом "структура как носитель данных", нам несложно организовать структуру с тремя векторами, которые будут хранить точки (x,y,z) поколоночно, и даже сделать ей специальный геттер, чтобы можно было вернуть отдельную точку как тройку (x,y,z) (в C++20 можно красиво сделать), и это всё классно ляжет на компилятор. А вот адепт ООП наверняка сделает класс Point с тремя числами в нём, и далее по списку. Таким образом, ООП определит структуру хранения, которая в данном случае приведёт к худшей производительности. Конечно, можно заморочиться, и подложить под этот Point-класс какие-то хитрые структуры хранения и аллокаторы, абстрагирующие форму хранения от единичного элемента данных, но это будет весьма некрасиво и потребует много усилий на создание и сопровождение. Да и приведёт к обратному эффекту — мы вроде ООП делали, а в итоге изготовили дикий замес всего и сразу. Отказ от чистого ООП, как мне кажется, просто уберёт ненужную абстракцию и сделает всем счастье.

Спасибо :)

Да, тут масса тем. Касательно len(x) vs x.len() -- у меня был научрук, вот он любил подобные вопросы. Байт-код Java исполняет компилятор или интерпретатор? А если процессор реализует байткод нативно, т.е. байткод является его ассемблером?

Концептуально разницы между этими вызовами нет, но второй синтаксис недаром победил. Рано или поздно появляются случаи, когда приходится копаться в "кишках" объекта. Допустим, у нас len() определяется для C-строки, и тогда вычисление len() занимает O(N). Мы решаем кэшировать значение где-то в поле _lenCached, но если функция len() получает доступ к этому полю, то получают и все на свете, а для типа данных "строка" по идее операция доступа к такому полю не определена ("тип есть область значений + допустимые операции"). Тогда надо определять функцию len() как friend (в терминах C++), и в итоге приходим туда, откуда вышли -- есть функции-члены класса и "внешние".

Для меня ООП -- это прежде всего попытка моделирования мира в рамках классической модели Платона-Аристотеля-Линнея. У вещи есть "идеал" (платоновский эйдос), есть свойства сущностные и случайные (эссенция и акциденция по Аристотелю), есть иерархия типов по категориям рода и вида (Линней). Альтернативы на больших масштабах по сути нет, если только речь не идёт о моделировании процессов как таковых (и на этой базе основана библиотека C++ STL, например, но это именно что библиотека алгоритмов).

Что касается неоптимальности представления точек в виде объектов -- ну, да, есть такое. Наверно, если вы работаете с векторами, то объектом должен быть вектор, а не точка. Можно ещё сделать какой-то собственный "векторный" аллокатор памяти или ещё как-то решить этот вопрос "на другом этаже", но в целом, конечно, ООП про представление знаний, а не о том, как это представление хранить в последовательных ячейках памяти.

Главное преимущество второго варианта - IDE может давать контекстно-зависимые подсказки и заодно автодополнять.

Ну, справедливости ради, в Go можно написать some_array_expression.len, и как минимум Intellij подскажет "возможно, вы хотели len(some_array_expression), если да - давайте заменю". Но это отдельные случаи, да.

Тогда надо определять функцию len() как friend (в терминах C++), и в итоге приходим туда, откуда вышли -- есть функции-члены класса и "внешние".

Это именно что термины С++. В другом языке методы могли бы без всяких костылей определяться так же просто, как и методы в С++, но вызываться как функции.

Истинную причину почему всем так нравится "точка" уже написали выше, это контекстные подсказки.

Не контекстные подсказки, а контекст как таковой. Собственно всё что идёт до точки - это контекст метода. И что, самое удобное - во всех смыслах этого слова. И в академическом, и в "программном", и в просторечном.
Именно поэтому это намного удобнее, чем передача контекста через параметры (как в селф в том же питоне, например)

Контекст — это на мой взгляд хороший аргумент.

Правда, как только будет введена конвенция в имени метода (начинаем с имени обрабатываемого класса), либо метод помещён в namespace, аргумент становится менее актуальным. Понятно, что приятно видеть листинг методов на объекте класса, но если мы "помним" класс объекта, то ситуация упрощается.

А чем передача первым неявным параметром отличается? Это же просто конвенция для доступа синтаксическим сахаром внутри метода к тому самому, что до точки.

Как получить длину строки, а не размер в байтах, для строки в юникоде?

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

Согласен, именно len(x) неудачный пример. Нет единообразного способа получить размер контейнера внешней функцией.

А что для вас "длина строки"? Количество кодепойнтов? Количество графем? Ширина графем в моноширинном шрифте?

Да! супер!

Адепт ООП сделает интерфейс Point и выберет конкретную реализацию исходя из результатов тестов или даже в рантайме.

Да, и это всё перечеркнёт преимущества C++ как такового. Компилятор, как правило, на встраивает код там, где идёт вызов именно виртуальных функций; иногда у него получается, но чаще просто нет достаточно контекстной информации. Там, где скорость важна, это критично. А если не важна, так симпатичнее такие конструкты ваять на C# или Java. Было время конечно, в 90-х всё писали плотно на C++, бизнес-приложения, GUI и т.д. Да прошло. И начинаются в C++ извращения типа CRTP и прочего страха, чтобы получить некое подобие полиморфных иерархий без уплаты цены виртуализации функций. Всё это дико ломает IntelliSense даже в 2024, и в общем не стоит того, как мне кажется по прошествии лет.

Я понимаю, что не о C++ речь идёт, а в целом. К тому, что ООП ООПу рознь, в зависимости от конкретной реализации в конкретном языке.

чем len(L) лучше L.length()

Вот, кстати, NIM как раз хорош тем, что можно записать и так и так:

len(L)

L.len()

и даже:

len L

Выбираешь тот вариант, который лучше с точки зрения читаемости кода именно в данном контексте.

В Perl так тоже можно, уже четверть века, чтоб можно было например писать new Some::Class а-ля как плюсах (это просто вызов метода new) - но наевшись граблей, этот синтаксис был объявлен deprecated.

Вторую можно круто собирать в цепочку! a.ArcTan2(b).Mult(Number.PI).Div(4)
Если пишешь свою систему типов, но по разным (техническим, религиозным) причинам не можешь использовать перегрузку операторов - второй способ предпочтительнее.
Но для коротких записей можно дополнительно осахарить первым.

По мне, для цепочек лучше использовать обратную польскую запись: a|b|arctan2|pi|mul|4|div. Потому что скобочек не надо.

Или "без точечную" нотацию:

div 4 . mult pi . arctan2 b $ a

Только у вас аргументы у div и mult не в том порядке оказались...

В том:
(div 4 (mult pi (arctan2 b a)))

Хотя, если считать div n x как деление на n, а mult n x как умножение на n, то, ве же, в том! Что соответствует исходному a.ArcTan2(b).Mult(Number.PI).Div(4)

Не соответствует, a.Div(b) соответствует div a b и никак иначе

Хотя вы тоже в чём-то правы: обычно такие "красивые" выражения через точку получаются только тогда, когда авторы API намеренно постарались с порядком параметров; но в таком случае авторы функционального API всего-то должны постараться в другую сторону и поменять порядок параметров.

когда авторы API намеренно постарались с порядком параметров

Написал об этом каментом выше

flip перед ними добавить и норм будет

В тот момент, когда появляется flip, однострочник становится write-only. Не надо flip.

В крайнем случае я бы использовал конструкции вроде вот таких:

(`div` 4) $ (`mult` pi) $ arctan2 a b

Я как-то не уловил, чем плохо, когда эта же цепочка запишется как ArcTan2(a,b).Mult(Number.PI).Div(4)

А чего это вы её дальше точкой-то продолжили?

Div(Mult(ArcTan2(a,b),Number.PI),4) и удачи в поисках парных скобок на глаз

Как-то я после Scala решил на Python писать в функциональном стиле. Исплевался. Вообще невозможно прочитать, что написано, а на Scala вообще сказка

Очень захотелось глянуть на примеры такого код, и для скалы и питона.

Разница, тащемта, простая. И она описана в статье: у вас есть пара классов списков с разной реализацией. Имплементация метода len может быть разной для разных списков, а использовать их может хотеться в одном и том же коде. Ну, ок , можно передать ссылку на len. А другие методы списка как? Каждый метод передавать отдельным параметром?

А кто вам сказал, что в произвольном языке программирования у функции len(obj) не может быть разных реализаций в зависимости от типа объекта?

Вам даже в комментарии написали один из таковых языков (Ada).

Могу привести второй: как ни странно, это работает в Питоне. Этого добились примерно так (с точностью до оптимизаций и обработки некоторых ошибок):

def len(obj):
  return obj.__len__()

Какой смысл у этого кода? Лишь бы не через вызов метода объекта сделаем обертку?

Не загрязнять пространство имён обычных методов, и в то же время не заставлять программиста каждый раз писать подчеркивания.

Но да, иногда мне тоже кажется, что "лишь бы не через вызов метода" было одной из причин...

Но ваша функция не может обойтись без метода из объекта. То есть, для вашей реализации нужно, чтобы существовали методы объекта. Но тогда можно обойтись и без нее.
Кроме того, как может эта функция отличить, есть ли нужный метод у объекта или нет? В рантайме? Мне кажется, ваш пример неудачен.

Ну так это ж Питон, что вы ещё от него хотели? Я привёл этот код как пример того, что даже в нём можно сделать подмену реализации функции в зависимости от типа (да, через скрытый метод).

В тех языках, где изначально решили использовать синтаксис f(x) - нет никаких проблем с этим синтаксисом.

Можно точку считать бинарным оператором - своего рода синтаксическим сахаром, например:
а . f(…) => f(a, …)
a .. f(x, …) => f(x, a, …)
a ?. f(…) => IF( a=null, null, f(a, …) )

Ну и прочее синтаксическое баловство, призванное уменьшить уровень скобочной вложенности (на dotnet делал DSL, по синтаксису обратно совместимый с формулами Excel, именно с указанной целью туда операторы . и .. удобно встроились).

С помощью макрооператора-точки в простейшем синтаксисе арифметических выражений с вызовами функций можно "красиво" поддержать fluent-синтаксис, и вместо нечитаемого
Distinct( Where(conds, IF( ISNUMBER(arr), arr, "-" ) ) )
получить приемлемое
IF( arr.ISNUMBER(), arr, "-" ) .. Where(conds) . Distinct()

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

Да, тебе приятно писать и читать этот код, но надо всегда понимать, что ты в угоду собственному комфорту приносишь свою производительность. И тут вопрос, что для тебя важнее: комфортно кодить, получая зарплату и "чистенький, легко дышащийся" код, или получать результат. За пределами системного программирования или подобных нишевых, близких к железу вещей С - это не тот язык, на котором можно получать результат за разумное время.

У меня сын потихоньку изучает С/С++, и дико матерится на то, что в С нет многих очень удобных стандартных вещей. А я смотрю на его программы на C++, и думаю, что все эти "очень удобные стандартные вещи" без понимания их потрохов будут приводить просто к адовым тормозам...

Сейчас сходу не вспомню, какая-то муть с базовыми алгоритмами сортировки и массивами.

Стало вдвойне интереснее, потому как С с его компараторами через void*, конечно, очень способствуют скорости /s

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

Тут проблема точно не в ООП, так и на числом Си наговнокодить можно.

Поможет тут только изучение азов алгоритмов и теории сложности.

Речь о другом: появление встроенных функций скрывает сложность алгоритмов, которые они реализуют. В целом это хорошо, но может приводить к мягко говоря, странным алгоритмическим решениям.

Так на Сях точно так же можно функций насоздавать, которые сложность скроют.

Я как-то (правда, не на Си, а на Паскале), когда только начинал изучать алгоритмы, "изобрёл" быструю сортировку односвязного списка за O(N² log N). Суть "изобретения" была ровно в том что вы пишете - в функции, которая скрывала обход списка с самого начала для получения предыдущего элемента.

появление встроенных функций скрывает сложность алгоритмов

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

Так и в C можно strstr дёргать на каждой итерации.

Это классический компромисс: скорость работы программы приносится в жертву скорости разработки (и, не всегда, но очень часто - меньшему шансу допустить ошибки). В большинстве случаев второе - важнее. Потом возвращаемся и оптимизируем те участки, которые особенно сильно тормозят.

Так в этом и есть смысл ООП.

Парадигма программирования вообще не связана с тем как правильно юзать паттерны.
Она связана с тем, что есть проект, есть 100 разработчиков. Как грамотно распределить между ними задачи, чтобы разработка шла быстро, чтобы разработчики друг с другом не конфликтовали и не ждали, пока один завершит свою задачу, перед тем как начать свою.

Понятное дело, что код надо поделить на кусочки и дать всем писать свой кусочек.
В этом плане ООП оказался прям идеальным, по сравнению со сплошным листингом и по сравнению с модульным подходом, ибо при понимании базовых принципов, можно практически на интуитивном уровне делить код на объекты, а объекты достаточно изолированы друг от друга, чтобы каждый мог писать свой объект и не мешать друг другу.

ООП это парадигма для быстрой разработки крупных продуктов, тогда С++ собственно и предполагался для подобных вещей. Ну и он оказался очень fit to many.

При отсутствии большого количества людей в команде, любая парадигма может обеспечивать скорость разработки не хуже. При наличии 1-2 разработчиков, парадигма вообще не имеет значения, имеет значение больше привычка этих парочки разработчиков.

p.s. да, в скорость разработки может также входить и тестирование и последующая поддержка.

Деление кода на задачи вообще не связано с парадигмой. Это чисто организационный вопрос, и в языках без ООП он решается тем же делением на каталоги, подкаталоги, файлы и т.д., как самое простое.

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

О да! :) После Х лет на Питоне возвращался к Си, чтобы прошивку написать для esp8266. Ох как я плевался первые дни, снова "перекладывая байтики/строки". Такое ощущение траты времени... Как будто пришлось тонны текста набирать на экране телефона одним пальцем.

А взяли бы еще более высокоуровневой язык - плевались бы как я, с Питона, где слишком много низкоуровневых вещей из Си протекает, типа указателей на строки. Нет уж, если я беру Си, то я к нему готов, а половинчатых решений ну нафиг.

Ряд высоконагруженных (около)финансовых проектов смотрит на это утверждение с недоумением.

Я имел в виду производительность разработчиков, а не программ. Вы мой комментарий до конца не дочитали, да?

Вы приблизились к изобретению Go)

+10000

поймал себя на мысли что стал писать на с++ как 25 лет назад, когда только начал его изучать. с минимумом реально нужных штук, даже операторы не использую. старость наверное:)

как стракты с функциями

Нам всем нужно вспомнить вот такой мем :)

В целом плюсую подход с ограничением подмножества фич. Даже плюс-плюсую)

Я, как эмбеддед с микроконтролерами, в основном чисто сишник. Из плюсов беру некоторые вещи, которые одновременно экономят и (объём кода=время разработки), и (процессорное время=потребляемая мощность=цена железа). Для меня плюсы главным образом меняют 1 на 2

sometask( register *this, types args );
this.sometask( types args );

+ область видимости переменных. Пользуясь случаем передаю отдельный "привет" питонщикам с ихним бл$дь self через слово)
Шаблоны и наследование - для мк-проектов редко нужны, но иногда бывает удобно. Всё, что касается динамической памяти в прошивках для девайсов без аппаратной подстановки страниц ОЗУ, это конечно у нас запрещёнка.

Но в целом, конечно, для каждой задачи - свой инструмент.

Да, кстати та же история с Питоном, я как-то попробовал там ехать на ООПе, и мне это очень не понравилось из-за того, насколько сбоку с этим self оно там прилеплено. Выхлопа вообще никакого в нём от ООП, только раздражение.

Сам по себе может быть питон и не плох. Он другой, не для промозглых железячников. Во всякие краткие записи, которые по факту разворачиваются в циклы, я только-только въезжаю. Каким-то умным словом это называется) а ещё и глубже есть

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

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

с Питоном, я как-то попробовал там ехать на ООПе, и мне это очень не понравилось из-за того, насколько сбоку с этим self оно там прилеплено

Пример бы кода посмотреть.

А что там в этом вашем Питоне с self? Я просто перловик, и у нас this тоже называется $self, и какая проблема, непонятно.

​1) self должен присутствовать обязательным первым аргументом в методах, привязанных к объекту. (В принципе ничто не мешает назвать его при этом не self, а mamarama или хвостПитона, но все коллеги и статические анализаторы забьют ногами.) Для помеченных @classmethod точно так же должен быть cls.

​2) Ссылка через self на поля объекта обязательна, такого, как в C++ с его "не нашли в локальных переменных - ищем в классе" нету и не предполагалось.

Оба пункта могут считаться и достоинством, и недостатком, кому как - но предмет для холивара дают по полной.

передаю отдельный "привет" питонщикам с ихним бл$дь self через слово)

:) Так и питонщики сами тихо офигевают, когда видят чей-то код, загаженный классами там, где реально нет никаких классов. Прочитай тут статью "Перестаньте писать классы", например.

Пользуясь случаем передаю отдельный "привет" питонщикам с ихним бл$дь self через слово)

Пример бы кода посмотреть.

Да любой. Вот из одной стандартной библиотеки:
https://github.com/python/cpython/blob/main/Lib/zipfile/__init__.py

2330 строк.
Слов "return" - 116. Слов "def" - 123. Примерно столько функций, но часть не ООПэшная.
А слов "self" - 649.

И эти люди мне запрещают ковыряться в носу в пример ставят ";" ...

Ну да, если сравнивать с какими-нибудь Java или C# - этих слов и правда довольно много.

Но вы ж сишник, а в Си точно так же нужно повторять через слово тот объект, с полями которого идёт работа...

Да, я про любой С-подобный синтаксис.

Но, как говорил знаменитый В.И., есть один нюанс. Си уже пол-сотни лет в обед, и ни про какой ООП ни тогда, ни сейчас речи не было. Для этого сделан обратно совместимый ++. Но в питоне-то в принципе всё есть объект. Поэтому неизменный self там выглядит странно.

Вероятно, у разработчиков питона всё-таки была какая-то причина так сделать. Пришлось идти на компромисс. Хотели с одной стороны обеспечить полный волюнтаризьм в динамическом добавлении свойств и методов, как в транслируемых языках. С другой стороны питон весьма шустрый в скомпилированном виде, пускай и в байт-коде. По скорости выполнения классика типа JavaScript и TCL даже рядом не стоят.

Это Питон-то шустрый?..

Что значит "неизменный" self? Выглядит так же, как self в перле или this в других языках, это ж просто имя текущего объекта.

Да? А гитхаб в поиске показал, что их всего 200. Потыкал первый десяток, в чем проблема, не понял.

Хром поиском по странице выдаёт "self" 649 штук. С точкой "self." и с запятой "self," чуть поменьше, но их тоже больше 600 штук.

Для замшелого Сишника странность в том, что у питона область видимости переменной нужно задавать явно. Для глобальных переменных - один раз на функцию. А для свойств текущего объекта - каждый раз при упоминании этого свойства. Поэтому код становится похож на ООП, написанный на ассемблере Си без плюсов - нужно всегда явно указывать тот самый this/self.

Я бы сразу горячюю клавишу для ввода "self." назначил. Как в ZX-Spectrum)
Я бы сразу горячюю клавишу для ввода "self." назначил. Как в ZX-Spectrum)

Мы с вами точно про один и тот же Си думаем? В "моём" Си вы не можете обратиться к полю записи по короткому имени!

Кстати, "поле текущего объекта" не является разновидностью области видимости.

А что там в этом вашем Питоне с self? Я просто перловик, и у нас this тоже называется $self, и какая проблема, непонятно.

А кто поддерживает ваш код?

Я сталкивался с кодом на чистом С, который поддерживали его разработчики. Это работает, если разработчики хорошего уровня, хотя и тут бывают проблемы.

Но как только такой код попадает к новому разработчику, код оказывается хрупким, поддерживать его крайне сложно, иногда проще переписать.

У меня первое прозрение произошло, когда я после стандартной библиотеки стал Qt использовать. Вот это вот "а что, можно было просто str.split("/") написать?" Второе - когда перешёл на web и стал писать на javascript/typescript. Помню, какое снисходительное, даже пренебрежительное отношение в плюсовой среде было к javascript. А я как перешёл, просто выпал с того, что мне для того, чтобы писать вообще весь код достаточно одной языковой конструкции function. Что задачи, для которых я бы городил вспомогательные виртуальные классы, какие-то перегрузки, шаблонную магию, они решаются как бы автоматически, просто написал две строчки, следишь за тем, что написал, и все.

Ну это известный снобизм плюсовиков. Пренебрежительное отношение к JavaScript должно быть не от плюсов, а от сравнения с другими высокоуровневыми языками, в которых описанные в этом комментарии преимущества (просто функции первого порядка без перегрузок и шаблонов) тоже есть.

У опытных товарищей особого снобизма не наблюдаю. Понятно, что транслируемые, компилируемые и байт-кодные языки - каждый для своих задач удобен.

Я сам недолюбливаю javascript по другой причине. Не сам язык, а его использование. Потому что де-факто это стандарт для "фронтовиков", ну и для браузеров. При этом он способен превратить кБ веб-страницы в ГБ ОЗУ. И опустит производительность проца с ГГц до кГц.

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

ActionScript, который Flash-анимация, когда-то пытался ужать этот объём, но не сраслось. Стандартизация - это хорошо, но не за счёт таких ресурсов.

Извини, наболело)

Я довольно странный, писал на Си в 90-е и в конце 90-х сел за Delphi, но за 10 лет не создал ни одного класса, то есть заворачивал ПП в разные обработчики. Потом ушёл от программирования почти на 10 лет (это не моя профессия если что) и возвращаясь абсолютно не хотел опять пользовать Паскалем, ну не нравится он мне. И меня познакомили с C#. И по сей день пишу на нём всякую ерунду и мне он безумно нравится.

Но каждый раз когда хочу написать программу думаю - ну вот сегодня я буду писать в парадигме ООП. Сажусь, что-то думаю, что пишу и просто отказываюсь от этого. Я не умею мыслить в этой парадигме, не понимаю чужой код написанный в ООП, просто не понимаю как прыготня туды-сюды может выстраиваться в что-то ровное и логичное.

На самом деле примеры типа dog.bark(), который печатает на экране "bark" это неправильное применение ООП. Так что возможно вы все делаете правильно. Объединить схожие процедуры в один класс и вынести общее состояние в поля класса, вот и получился нормальный объект в стиле ООП.

dog.bark() может возвращать строку "bark", а вот как ее выводить, это уже не ответственность dog.

dog.voice() же
И все логично
Если voice унаследован от энимала

Главное, что не voice.bark(dog) - а то бывает же и такое.

Ну, зависит от сложности моделируемой системы. Ваш пример, например, вполне приемлим если вы моделируете поведение всей биосферы AbstractPlanet

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

С наследованием да, будет voice(), просто это связано не только с наследованием. Класс Dog не должен работать с экраном, даже если он не наследуется от Animal.

UFO just landed and posted this here

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

Вот отсюда и появляются эти усложнения. Сначала выводим в классе Dog на экран, потом делаем абстракцию для устройства вывода, потом интерфейс для нее, потом делаем обертки для устройств, которые следуют интерфейсу, потом базовые классы для оберток, потом говорим "Ну ООП это сложно, заранее нельзя всё продумать". А вызывающий код находится в 2 разных точках приложения, где каждая работает со своим устройством, и там можно вывести на нужное устройство разным неунифицированным кодом.

Ты не прав. По твоему метод, который называется "лаять", должен что-то возвращать? Если что-то возвращать, то это результат выполнения команды - успех/неудача. В чём смысл возвращения оттуда строки? Тебя пугает что собака взаимодействует с консолью? Окей, чувак, это просто DI. Ты вообще не знаешь почему оно написало в консоль. А может реализация метода использует синглтон звукового пространства и эмитит туда событие в асинхронном режиме, а? Это вообще не важно, как оно работает. Главное что оно соответствует своей сигнатуре.

По твоему метод, который называется "лаять", должен что-то возвращать?

Конечно. Действие "Лаять" возвращает лай. Дальше он обрабатывается окружающей средой, которая от собаки не зависит. Например, лай в пустой комнате будет отдаваться эхом, а на улице нет.

В чём смысл возвращения оттуда строки?

В избегании второй ответственности где и как ее рендерить.

Тебя пугает что собака взаимодействует с консолью?

Да, потому что завтра придет заказчик и попросит выводить еще и графикой, и придется менять кучу кода, а я этого не хочу.

Окей, чувак, это просто DI.

К чему это приводит, я написал в предыдущем комментарии. Особенно если Dog создается в рантайме по данным из базы.

А может реализация метода использует синглтон звукового пространства и эмитит туда событие

Вот-вот, когда DI не хватает, то появляются события, которые обрабатываются фиг пойми где где-то вне класса. Если мы все равно обрабатываем их вне класса, то непонятно, зачем тогда туда эмиттер пробрасывать.
Возврат строки (или иной структуры данных) из метода это и есть событие.

Это вообще не важно, как оно работает.

Мне важно, потому что я делаю реализацию того, как оно работает.

 Действие "Лаять" возвращает лай.

Тогда это не совсем удачный нейминг
Заменить .bark() на .getBarkOutput() - и все снова становится логично, и можно передавать его в поток вывода данных

Да, можно сделать геттер. Только output это слишком техническое название, сущность это бизнес-логика, поэтому название должно быть связано с бизнес-логикой, getVoiceSounds() хотя бы. Собственно, так и получаются сущности, которые содержат только геттеры и сеттеры.

Действие "Лаять" возвращает лай. Дальше он обрабатывается окружающей средой, которая от собаки не зависит. Например, лай в пустой комнате будет отдаваться эхом, а на улице нет.

Проблема в том, что в сильно большом количестве случаев, чтобы создать/сконфигурировать действие - нужно много об этой окружающей среде знать.

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

Потому что когда код выходит за рамки учебного примера, это становится сложно поддерживать. Лучше сделать класс типа DogRenderer, который будет брать свойства собаки и окружающей среды, и выводить их как нужно на устройство. Зачем нужно обязательно стремиться поместить этот код в класс, моделирующий собаку, мне непонятно.

Поддерживать - дело темное. Зависит от того, что, где и как часто меняется и что нам менять больше (не)хочется. Что зависит от того, с какой стороны мы смотрим. Либо со стороны программистов/пользователей класса собаки, либо со стороны программистов/классов вывода/рендеринга.

Логика "отобразить вот этот кусок собаки на такой-то среде" - она же все равно где-то будет. Либо в рендерере, либо в собаке. Разница только в том, где искать, и какой именно класс изменится, когда ее придется менять.

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

Зачем нужно обязательно стремиться поместить этот код в класс, моделирующий собаку, мне непонятно.

Ну вот нужно положить(отрендерить) собаку в базу данных, отобразить на экране, передать по сети, описать голосом. Все это - рендеринг на какую-то среду. И для того, чтобы это делать правильно и полно, рендереру придется знать порядочное количество потрохов собаки.

Как бы нехорошо. Потому что устройство собаки - обычно считается, что это исключительно ее дело.

Разница только в том, где искать, и какой именно класс изменится

Нет, разница в том, как создается объект этого класса и как туда пробрасываются зависимости. Сервис DogRenderer можно создать один раз при инициализации приложения, DI-контейнер подставит в конструктор нужные зависимости, возможно вообще при компиляции. А сущности обычно создаются позже во время работы приложения во многих экземплярах, пробрасывать туда зависимости сложнее.

И для того, чтобы это делать правильно и полно, рендереру придется знать порядочное количество потрохов собаки.

Не, в том и дело, что для разных сред рендеры могут быть разные. Рендерам надо знать только свойства сущности, которые мы выделили при анализе предметной области. А вот когда этот метод находится в классе Dog, который у нас один, то ему надо будет знать всё про все среды.

Потому что устройство собаки - обычно считается, что это исключительно ее дело.

Свойства собаки это не детали ее реализации. Детали реализации свойств надо скрывать, факт наличия свойств нет. Если бы это были детали реализации, мы бы про них не узнали при анализе предметной области, мы же ее анализируем снаружи сущности.

Не, в том и дело, что для разных сред рендеры могут быть разные. Рендерам надо знать только свойства сущности, которые мы выделили при анализе предметной области. А вот когда этот метод находится в классе Dog, который у нас один, то ему надо будет знать всё про все среды.

Но обычно у нас есть не только Dog но и Cat и Fish

В результате дилемма - либо Doc, Cat, Fish знают про все среды, либо все среды знают про всех этих животных.

Т.е. у наст есть (Dog, Cat, Fish) x (база данных, экран, сеть, динамик) - 12 кусков логики, которые должны что-то делать, вытягивая релевантные свойства среды и животного. И эти куски логики можно распределять по разному.

Вплоть до того, чтобы написать 12 штук функций render(<животное>, <среда>) для каждой возможной пары вывода.

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

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

При появлении нового животного - то ли в каждый существующий рендерер его добавляем, то ли новое животное пишем со знанием того, как на каждую среду рендерится.

Конечно, обычно так и получается. База данных - repository / entity manager, экран - view, сеть - serializer, динамик - в контексте веб-приложений тоже view. И для каждой сущности они свои. И вот в этих условиях получается, что удобнее делать просто сущность со свойствами, а всё остальное в отдельных классах.

Конечно, обычно так и получается. База данных - repository / entity manager, экран - view, сеть - serializer, динамик - в контексте веб-приложений тоже view.

И тут спотыкаемся о том, как обычно сделан полиморфизм.

Если написать условное

Animal dog = Dog()
Animal cat = Cat()
View view = GetScreen();
view.render(dog)
view.render(cat)

То чтобы вызвались, соответственно, разные "Нарисовать собаку на экране" и "Нарисовать кошку на экране" - это надо с паттернами упражняется. Диспетчеризации по реальному типа аргумента же нет. Вот и делают 'Animal.render(View)

А если надо так:

Animal dog = Dog()
Animal cat = Cat()
Renderer screen = GetScreen()
Renderer db = GetDb()
screen.render(dog)
screen.render(cat)
db.render(dog)
db.render(cat)

То вообще получается довольно таки некрасиво.

Ну так кладем внуть animal поле animalType равное энуму по названию классов-наследников и проверяем перед отрисовкой

Смотри где-то рядом как я говорил про боязнь перекомпиляции. "А вдруг новый Animal появится? Это нам что, нужно будет switch где-то в другом классе/библиотеке менять и заново компилировать?"

Так это уже обычный структурный подход получается, со switch/case . Во всех ООП материалах его ругают и предлагают заменять полиморфизмом, а вы хотите назад откатиться )

Зачем вам одно поведение для screen и db? Для screen один класс, для db другой, в том и смысл.
Полиморфизм тут вообще не нужен.

class DogRepository {
  public function save(Dog $dog) {
    dbConnection.insert('dog', ['name' => $dog->name, ...]);
  }
}

class CatRepository {
  public function save(Cat $cat) {
    dbConnection.insert('cat', ['name' => $cat->name, ...]);
  }
}

class DogService {
  public function save(Dog $dog, SaveDto $newData) {
    $dog->setName($newData->name);
    $this->dogRepository->save($dog);
    
    $this->queue->send(
      'event.dog.updated',
      $this->dogSerializer->serialize($dog)
    );
  }
}

class DogSerializer {
  public function serialize(Dog $dog): string {
    return json_encode([
      'id' => $dog->id,
      'name' => $dog->name
    ]);
  }
}

class DogController {
  public function show(int $dogId, int $catId) {
    $dog = $this->findDogEntity($dogId);
    $cat = $this->findCatEntity($catId);
    
    return $this->view(
      'animals.html',
      ['dog' => $dog, 'cat' => $cat]
    );
  }
}

// animals.html

<div>Dog: {{ dog.name }}</div>
<div>Voice: {{ dog.bark() }}</div>

<div>Cat: {{ cat.name }}</div>
<div>Voice: {{ cat.meow() }}</div>

Это как делается на PHP, на других языках не знаю, но раз на них делают веб-сервисы, значит это возможно.
Если надо работать с разными сущностями в одном коде, там да, делается интерфейс, или switch по типу, ну или рефлексия. Сделать общий интерфейс для DogRenderer и CatRenderer проще, чем для Dog и Cat, или тем более для Product и Order. Помещение работы с экраном в Dog эти сложности не решает.
Но например для репозиториев это обычно не требуется, на каждую сущность свой репозиторий, который знает, как с ней работать. И вызывающий код обычно знает, что он хочет сохранить.

UFO just landed and posted this here

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

Конечно. Но я не знаю, почему вы решили, что вызывающий код это хозяин.

Как не постараешься - все равно всего не усмотришь

Мой подход прекрасно совместим со всеми этими вариантами.

то DI получается вообще нигде будет не нужен?

Нужен, только не в сущностях, а в сервисах, которые создаются при старте приложения. обычно в одном экземлпяре.

Ну, например, чтобы собака могла в нее гавкнуть когда ей придет в голову

Мы говорим про метод bark(), который в любом варианте должен кто-то вызвать снаружи класса.

Или если операция гавканья дорогая, она не гавкала когда звуковое пространство никто не прослушивает?

Во-во, еще и третья ответственность появилась. Потом Dog превращается в God-object.

UFO just landed and posted this here

Про наличие вызывающего извне кода писали как раз вы.

Да. Как из этого следует, что вызывающий код это хозяин?

Тогда вызывающий код будет в самой собаке, например в ее голове

Вызывающий код будет в отдельном потоке, текущая позиция которого хранится в процессоре в регистре instruction pointer. Я не знаю, что из этого вы называете головой. Класс Dog никаких решений сам принимать не может, должен быть поток, который выполняет конкретный метод, принимающий решения. Это и есть вызывающий код для метода bark. Он может находиться в классе Dog или где-то снаружи, но с реализацией метода bark он никак не связан, поэтому bark может возвращать строку и в этом варианте.

а собака сама решит полаять

Я не знаю, что вы подразумеваете под словами "сама решит полаять" в контексте выполнения последовательности машинных команд.

Возврат строки как раз вызовет тут проблему.

Нет. Поведение собаки будет выполняться в отдельном потоке, он вызовет генератор случайных чисел, проверит заданное условие, при соответствии вызовет метод bark, который вернет сообщение, и отправит это сообщение в рендерер.

Либо вызовет кто-то снаружи, а собака спит и ответит только когда проснется через пару часов

Ради бога, в моем варианте на реализацию метода bark это никак не влияет. В моем варианте он вызывается в момент когда нужно фактически издать звук. Для отложенного выполнения будет другой метод, который будет вызывать bark когда нужно.

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

У меня есть что сказать о том, что отраженные лучи света это сообщения и как их нужно моделировать в программе, но это оффтоп.

God-object - не про то, что у собаки должен быть маленький функционал

Я не говорил, что это про то, что у собаки должен быть маленький функционал. God-object это объект, который совмещает слишком много ответственностей.

UFO just landed and posted this here

Метод может вызываться не только снаружи

Ну и пусть, как это мешает всем окружающим услышать собаку, если метод bark возвращает строку? Будет другой метод, который отправит его всем окружающим.

И вовсе не обязательно чтобы окружающим было необходимо вызывать метод для получения звука

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

Голова собаки - это логика ее поведения.

Логика находится в каком-то методе, этот метод и вызывает метод bark. Поэтому непонятно, почему вы противопоставляете пример "она сама лает" моему подходу. В моем подходе этот пример прекрасно работает. Просто поведение собаки не находится в методе bark, и он прекрасно может возвращать строку.

По вашей логике дальше голова должна полученный ответ обработать и послать куда-то рендериться?

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

Можно рассмотреть более детальную абстракцию, где голова посылает сигнал голосовым связкам, они производят звук, звук попадает в рот, отражается от зубов и губ, и покидает собаку измененным относительно исходного. Но метод mainLoop здесь все равно не является только головой, он связывает поведение всех этих компонентов.

Поэтому да, mainLoop получает звук от голосовых связок и отправляет его дальше в окружающую среду. Но это не модель головы, поэтому никакой проблемы в этом нет.

Выходной результат работы потока с поведением собаки это сообщение, описывающее лай. Обрабатываться оно будет в другом потоке. Передать его туда можно разными способами, но с методом bark это не связано.

Когда у собаки ответственность лежит в области "как ей себя вести" - это одна ответственность и единственная причина

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

Действие "Лаять" возвращает лай. Дальше он обрабатывается окружающей средой, которая от собаки не зависит. Например, лай в пустой комнате будет отдаваться эхом, а на улице нет.

Так а почему "действие" должно что-то возвращать, если это именно действие, а не функция?

dog = new Dog(); // здесь собака существует в вакууме и никто не услышыт её лай
room = new Room(); // создаём комнату, унаследованную от Environment
room.Add(dog); // здесь комната сама пропишет собаке в поле ParentEnvironment ссылку на себя
room.Add(cat); // поместим туда ещё и кошку
room.Add(mp3Recorder); // поместим туда ещё и подслушивающее устройство
mp3Recorder.Start(); // не забудем кнопку записи нажать
dog.Bark(); // теперь все собачий лай услышат и должным образом отреагируют, а комната добавит необходимую реверберацию в звук для рекордера

Если что, этот пример не высосан из пальца, а взят из реального проекта, просто названия поменял.

Да даже чисто по семантике сигнатуры! Метод dog.Bark() - должен

  • менять внутреннее состояние объекта. Например, переводить собаку в состояние "гавкающая"

  • посылать события (т.е. вызывать методы) связанных объектов (например, послать сообщение в консоль)

Даже возврат кода ошибки - не самая лучшая практика (лучше иметь что-то типа dog.GetError())

Но вообще не хорошо, чтобы всё запускалось от собаки. Если мы захотим, чтобы одновременно и кошка мяукала - это будет не очень удобно.
С консолью-то нормально, если нам порядок не важен. А вот в вашем примере по идее запись должна появляться по вызову что-то типа: engine.Render(room); record = mp3Recorder.GetRecord()

По чьей идее, по вашей? По вашей это типично процедурный подход получается. А по моей - низкоуровневая логика отдельно, высокоуровневая отдельно. Bark() не пишет ничего в консоль, он меняет внутреннее состояние и уведомляет об этом содержащий его контейнер. Bark() это абстракция для внешнего мира, для дирижёра собачье-кошачьим оркестром. А mp3Recorder - это абстракция для звукорежиссёра, и ему неинтересно, как он там внутри устроен и кто там на чём в оркестре играет. Он нажал Start(), затем Stop(), затем SaveToFile("...").

почему "действие" должно что-то возвращать

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

здесь комната сама пропишет собаке в поле ParentEnvironment ссылку на себя

Ну я так понимаю, собака через эту ссылку просто уведомляет комнату, что она гавкнула, а комната сама рассылает сообщение всем внутренним объектам, а не так, что собака знает, что в комнате содержится mp3Recorder, и работает с ним напрямую. Тут меньше зависимость от конкретной реализации, по сравнению с исходным примером, что надо сделать именно printf на консоль. Думаю можно сделать то же самое и без ParentEnvironment, но без реализаций нельзя сказать как именно. Что-то типа message = dog.Bark(); room.sendMessage(message);.

Не возвращает, а влияет. Эхо может полсекунды отдаваться, а может полминуты. В какой момент нужно возвращать управление с результатом? Системы, в которых функции выполняются неопределённое количество времени, сложно назвать надёжными.

В тот же, в который это происходит в процессе, который мы моделируем. В моем варианте управление в вызывающий код возвращается сразу, это вызывающий код решает, как выводить строку "bark", а не класс Dog. Он может использовать ее как имя файла "bark.mp3" и отправить его в mp3player с нужными параметрами, который будет его воспроизводить в отдельной нити нужное время, когда действие самой собаки уже завершилось.

Нет, стопэ, давай таки разберёмся с тем что должен возвращать метод

dog.bark();

Не понимаю твоего любого негодования. Я не могу вспомнить ни единого кейса, когда подобный метод возвращал строчку. Ты в какой парадигме то двигаешься? Вот тебе примеры.

console.log() печатает в консоль, ничего не возвращает. thread.start() запускает поток, ничего не возвращает. array.sort() сортирует список.

А вот array.sorted() возвращает отсортированный список. System.currentTimeMillis() возвращает текущее время, потому что это НЕ глагол.

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

Если хочешь чтобы твой метод возвращал что-то, назови его не "лаять", а "[собачий] лай" (не путать с обращением от второго лица):

barking1 = dog.barking();//OK

barkResult = dog.bark(); //AMAZING

console.log() печатает в консоль, ничего не возвращает. thread.start() запускает поток, ничего не возвращает.

При чем тут примеры методов, которые ничего не возвращают? Разговор о том, что собака не должна сама печатать на консоль, независимо от того, кто что возвращает.
console.log() должен работать с консолью, thread.start() должен работать с тредом, dog.bark() должен работать с собакой.

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

Если там столько исключений, то может не стоит употреблять слово "никогда"?
findById() вообще возвращает целые объекты. Билдеры и фабрики тоже возвращают. string.split() возвращает array.

назови его не "лаять", а "[собачий] лай"

Как назвать метод, было обсуждение в другой ветке, пришли к выводу, что можно сделать и геттером. Правильным применением ООП печатание на консоль из собаки все равно не становится. Как и rectangle.draw(), когда фигура сама себя рисует на экране.

Я бы для более ясного примера предложил вариант, что "сферическая собака в вакууме" должна вернуть WAV файл. Но по неймингу я согласен с комментом выше. :) Например (не идеально), audio = dog.createBarkingWAV() А дальше это audio уже используй, накладывай куда угодно, проигрывай...

Почему не просто глагол bark()? Просто глагол подразумевает выполнение действия, команда. Но мы ж не делегируем собаке таких полномочий (хоть многие этим и излишне увлекаются, запутывая всю логику).

Правильным применением ООП печатание на консоль из собаки все равно не становится. Как и rectangle.draw(), когда фигура сама себя рисует на экране.

Отож. А потом сидят и думают ... "кто нам ком стоял" (с). :) И не могут эту связанность потом распутать.

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

По какой логике собака должна возвращать wav файл? Особенно для тех, кто ничего не понимает в обработке звука. Wav файл логично возвращать устройству записи звука. Слово "гав" логично возвращать устройству записи текста. А для собаки логично ничего не знать про эти устройства - она же всё-таки собака, а не инженер-программист.

Это я привел пример САМОЙ ПРИЕМЛЕМОЙ модели. По факту вообще не надо делать никакой гавкающей собаки, так как ее невозможно смоделировать сколько-нибудь приближенно к реальному миру. А раз уж очень хочется, то пусть это будет что-то, что создают ее голосовые связки контактируя с воздухом среды и больше ни с чем. :) Условная вибрация воздуха, спектрограмма. Такая себе jailed-dog. Но если нам ничего не нужно будет кроме самого звука от этой собаки, то не фантазировать лишних абстракций, а реализовать именно генерацию аудио и на этом остановиться.

должна вернуть WAV файл

Зачем мне WAV файл, если я хочу показать гавкание в виде надписи на экране?

Просто глагол подразумевает выполнение действия, команда. Но мы ж не делегируем собаке таких полномочий

Делегируем. Тут 2 действия - собака гавкает, и мы это отображаем. bark() выполняет первое. В какой-нибудь игре он может например уменьшать запас сил у собаки.

что многие пытаются моделировать объекты реального мира

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

https://jsfiddle.net/jod34gq7/

Вот сделал демонстрацию с методом bark(), который возвращает строку.
Есть собаки на экране, добавляются через + и -, текущую можно двигать стрелками, текущая собака переключается по табу, при нажатии пробела текущая собака гавкает. Это отображается в виде строки "bark | baark | baaark" рядом с собакой, количество букв "a" зависит от силы собаки, которая задается про создании. Сообщение отлетает от собаки на 3 шага, потом исчезает. Вывод идет одновременно в текстовом и графическом виде. Желающие могут попробовать переписать в своем стиле, где bark() занимается выводом.

должна вернуть WAV файл

Зачем мне WAV файл, если я хочу показать гавкание в виде надписи на экране?

Должно создаваться событие, на которое могут отреагировать другие объекты.

Перебарщиваете вы. Bark() должен прозвести лай и уведомить среду через бродкаст события вовне.

Не плодите сущности.

Вот-вот. Тут гипотеза Сапира-Уорфа очень в тему - язык определяет мышление. Сначала выбирают подмножество слов, которыми описывается, те же существительные vs глаголы, а потом начинают неявно именно в этом русле и дальше думать, и всё заверте...

Правильным применением ООП печатание на консоль из собаки все равно не становится. Как и rectangle.draw(), когда фигура сама себя рисует на экране.

Правильное применение ООП, это когда всем сотрудникам проекта интуитивно понятно почему метод barking у объекта собака, а не у объекта, ну например sound_device.

Разделение кода на объекты это исключительно субъективизм, который живет внутри конкретного проекта, и обсуждать "общие правила", дергая частные примеры из каких-то конкретных проектов, без вникания в суть этого проекта - ИМХО суета сует.

Основная задача ООП - разделить код на объекты, чтобы ускорить разработку и поддержку. То есть нарезать длинную простыню кода удобными логичными кусками.

По поводу соглашения имен и паттернов - это вряд ли относится к ООП, это относится к этикету/архитектуре конкретного проекта.

Разделение кода на объекты это исключительно субъективизм

Не совсем, разделение кода на объекты, а точнее на классы, должно соответствовать бизнес-требованиям. Бизнес-требования, в первую очередь инструкции выполнения действий, должны как можно точнее отражаться в код. Существительные в сущности, глаголы в методы. Субъективизм если и есть, то он идет из бизнес-требований и естественного языка.

Можно делать и без соответствия, тогда код будет менее понятный и менее поддерживаемый. Погружение в проект это изучение соответствия между кодом и бизнес-требованиями, у таких проектов это занимает больше времени.

За вычетом того, что т.н. "паттерны проектирования" появились как костыли для архитектурных проблем определенных ООП-языков, всё верно.

Паттерны проектирования появились как обобщение опыта. Причём не только в ООП. Многие паттерны ведут родословную чуть ли не первым ЭВМ размером со с здание.

Паттерны проектирования появились как обобщение опыта.

опыта реализации тех возможностей, которых нет в языке, но которые хочется/надо иметь. Double Dispatch того же. Паттерн Visitor, скажем - он практически про это. И одновременно делает происходящее уродливым, потому что объекту приходится про этот самый Visitor знать.

Если язык тьюринг-полный, то в нём есть все возможности.
Я на обычных С под микроконтроллер писал и тот же самый Double Dispatch и посетителя. И, если честно, было даже немного поудобнее, чем на С++ (каждая конкретная структура сама себе таблица функций).

Одно дело, когда надо писать(используя паттерны), а другое дело - когда оно само сразу работает, выражаясь коротким способом прямо в языке. Потому что создатели языка сразу знание того, как это делать, в компилятор/среду выполнения заложили.

Ага, только Double Dispatch невозможно сделать быстро, тотально и расширяемо одновременно. И совершенно не факт, что знание того, как это делать, было заложено именно то, что нужно...

Вот когда завтра придет заказчик, придет время и думать о том. Ранняя генерализация кода - такое же зло, как ранняя оптимизация. :) Есть хороший принцип - YAGNI, в большинстве случаев ему и стоит следовать. Я не отрицаю, иногда можно предсказать, как заказчик захочет использовать ваш код, тогда и можно пошевелиться. Но без фанатизма.

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

UFO just landed and posted this here

Поведение методов - вопрос реализации и зависит только от задачи.

Обычно одна из целей в любой задаче это обеспечить удобную поддержку. Если код не надо поддерживать, то конечно без разницы как писать.

Если задача состоит демонстрации работы методов - это лишняя работа.

Я говорю про реальные приложения, а не про учебные примеры. Это подразумевается по контексту. В учебных примерах или для отладки можно что угодно написать.

Превращение правил в собственную проблему.

Да нет никакой проблемы, откуда вы это взяли?) Я просто помещаю тот же самый код в другое место в программе, где им удобнее управлять.

А если собака не раз лает, а раз в 5 секунд в течении минуты?

Не вижу проблемы, как надо так и будем вызывать.

А что если собака сама по себе гавкает когда ей вздумается, без требования хозяина?

В вашем варианте этот случай тоже не реализован, у вас тоже есть метод bark(), который кто-то должен вызвать снаружи класса Dog.

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

В вашем варианте может и будет, а у меня нет. Мне удобно так взаимодействовать с собакой во всех этих кейсах.

ООП призван облегчать жизнь, а не усложнять, не обязательно в правила "упарываться"

Я понятия не имею, почему вы перенос printf из одного места в другое называете словом "упарываться". Вопрос не в следовании правилам, а в удобстве дальнейшей поддержки.

UFO just landed and posted this here

Так речь что в реальных приложениях так делать неправильно, или в примерах?

Речь о том, что примеры, написанные в таком стиле, показывают неправильное применение ООП, а потому для обучения применению ООП в реальных приложениях не годятся.

Я выше как раз и говорил о том, что для "примера" типа dog.bark() реализовывать вывод в методе - нормально, потому что вы говорили о примерах.

Я говорил про примеры правильного применения ООП, а вы говорили про демонстрацию работы методов. Это разные понятия, непонятно почему вы пытаетесь их подменять.

Если в примерах использовать неправильно, в реальном коде тоже неправильно, а в каком кейсе тогда этот вариант правильный?

В кейсе демонстрации работы методов, что этот метод реально вызывается. Или в отладочном выводе, когда этот код после отладки удаляется.

Собака может лаять и сама без вызовов.

Тогда этот пример нерелевантен теме обсуждения. В данной ветке обсуждается вызов и реализация метода bark().

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

Я не понимаю смысла вопроса. Как нужно по требованиям, так и будет работать. Надо ждать, значит будет ждать. Не надо ждать, значит не будет, а будет отдельный поток.
Выполнение поведения собаки в отдельном потоке это другая тема обсуждения. Там будет метод mainLoop(), который будет вызывать метод bark() когда нужно. И метод startBarking() с аргументами длительность и частота. Что-то будет вызываться в основном потоке, что-то в потоке поведения собаки. На реализацию метода bark() это никак не влияет.

Если предоставить собаке интерфейс вывода "окружающая среда", то она сама будет лаять в него

Как она будет лаять, если метод bark() никто не вызывал?

вместо того, чтобы хозяин просил собаку лаять

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

Поддержки примеров?

Нет, поддержки реальных приложений. Если пример предназначен для демонстрации правильного применения ООП в реальных приложениях, то он должен показывать код, который будет проще поддерживать, даже если на самом деле никто не будет это делать. Показывать правильное применение это основная цель обучающих примеров.

Комментарий предназначался скорее всего не мне. Но я бы сделал просто

Console.WriteLine("bark");

Я не знаю зачем мне создавать отдельный класс для этого.

Естественно, если цель выплюнуть куда-то (консоль, лог, поток, не важно) "bark", то писать dogservice, dogcontroller и так далее, как выше было, смысла нет. Но ведь это просто иллюстрация, а на деле там скорее что-то important_class.do_important_stuff_with_side_effects_don_t_forget_about_exceptions_and_thread_safety.

у меня достаточно знакомых родившихся в середине 00-х и сейчас пишущих на питонах и C#, они в принципе не представляют как можно писать что-то в парадигме ПП. Они постоянно пишут разные обёртки, что-то куда-то заворачивают и разворачивают. Я не понимаю их код. Он прекрасно работает, но он для таких как он же сам )

Просто когда мне говорят - выведи "bark" на экран, я пишу одну строку, а они пишут программу и размещают её на github. Это другое поколение имхо. Ни плохо, ни хорошо (может и наоборот - хорошо).

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

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

Но они код друг друга прекрасно понимают

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

С функциями тоже можно наворотить: встречал код, где функция вызывала атомарную функцию свыше десятка раз и всё это жутко тормозило.

Просто когда мне говорят - выведи "bark" на экран, я пишу одну строку, а они пишут программу и размещают её на github.

Нет, они тоже пишут одну строку. А ООП нужен потому что заказчикам нужно не только вывести одну строку на экран. Им нужно пара сотен сущностей по 20 полей в каждой, пара десятков изменяющих сценариев для каждой сущности, валидация для каждого сценария, пользовательский интерфейс для их вызова, похожая функциональность типа методов оплаты, веб-клиенты для разных API, которые работают по одинаковому HTTP. И без какой-то группировки и вынесения общего в абстракции сделать это и потом поддерживать практически невозможно.

Нет, они тоже пишут одну строку. А ООП нужен потому что заказчикам нужно не только вывести одну строку на экран

причем тут какие-то заказчики которых постоянно приплетают. Речь о способе мышления.

При том, что вы высказали негативное отношение к людям, которые так делают, с намеком на то, что они всё усложняют без надобности. Я описал, в чем тут надобность. ООП это инструмент для решения определенных задач, и оно появилось задолго до 00-х.

я пишу о себе и своём субъективном восприятии, вообще нет мыслей негативно отзываться о людях, я негативно отзываюсь о самой ситуации в которой оказываюсь. Для меня конечно источником проблемы будет тот кто использует ООП и его я понять не могу. Это не лучше и не хуже, это просто так есть.

ООП это инструмент для решения определенных задач, и оно появилось задолго до 00-х.

Вообще с этим не спорю, сам ООП стал изучать в 1993 году. А про 00-е я написал в контексте того как именно всё происходило в моей жизни. Уточню еще раз, я с программированием связан не больше чем каждый мужчина державший в руках молоток - со строительством.

Скорее плохо. Но последствия этого отдалены во времени.

Комментарий предназначался вам. Если вам кажется, что "парадигма ООП" это что-то особенное, значит вы неправильно понимаете, что это такое, и скорее всего ваше представление сложилось на основе неправильных примеров.

Но я бы сделал просто Console.WriteLine("bark");
Я не знаю зачем мне создавать отдельный класс для этого.

Но вы же понимаете, зачем нужен класс Console? Вот объединение методов типа WriteLine в класс это тоже ООП. К тому же я предполагаю, что даже в процедурном стиле вы даете общие префиксы для названий процедур, относящихся к одной функциональности. Вот этот общий префикс и есть класс.

Но вы же понимаете, зачем нужен класс Console?

Я это понимаю как что-то внутреннее для самого языка, для меня это черный ящик. Мне сказали писать Console.WriteLine - я пишу, вопросы в духе почему-зачем-кто_виноват меня не волнуют.

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

Совсем ничего не понял. Если я пишу процедуру void ShellSort(byte[] bytes), то при вызове я пишу ShellSort(new byte[] {4, 5, 2, 7, 1}), что ничем не отличается от того что я писал в Си или Паскале 30 лет назад.

А представьте, что вы пишете нечто, у чего больше 1 процедуры. Монитор производительности, например. Вот, цель чтобы у пользователя этого монитора был такой же чёрный ящик, как для вас Console. Чтобы он просто мог сказать PerfMon.WatchThreadCPU(thread_id, threshold), или PerfMon.WatchProcessMemoryConsumption(process_id, threshold), или PerfMon.AlertOnDeadlock(process_id), или PerfMon.AdjustSettingsThread(thread_id).CPU(new_threshold). Про вот эти префиксы шла речь.

А представьте, что вы пишете нечто, у чего больше 1 процедуры

Я не только писал, но и продавал написанное.

Чтобы он просто мог сказать PerfMon.WatchThreadCPU(thread_id, threshold), или PerfMon.WatchProcessMemoryConsumption(process_id, threshold), или PerfMon.AlertOnDeadlock(process_id), или PerfMon.AdjustSettingsThread(thread_id).CPU(new_threshold). Про вот эти префиксы шла речь.

У меня это и были бы

WatchThreadCPU(thread_id, threshold)

WatchProcessMemoryConsumption(process_id, threshold)

AlertOnDeadlock(process_id)

AdjustSettingsThreadCPU(thread_id, new_threshold)

без дополнительного префикса.

Скажу больше, я пишу программы с общими глобальными переменными, не использую private и public со всеми сопутствующими прослойками и не изолирую код. Я же объясняю - я в принципе не умею мыслить в ООП, совсем. Если бы я мог этому научиться то это случилось бы еще в середине 90-х, но сейчас 2024, а воз и ныне там. Я предпринимал попытки неоднократно, но линейность ПП для меня понятная и органична, а читать код с тонной однострочных методов раскиданных по десяткам файлов я не в состоянии.

Я даже в DOS писал весь софт в одном файле. В Delphi к несчастью с каждой формой появлялся свой файл что было адски неудобно, но поэтому я давно им и не пользуюсь, а в C# мне хватает Program.cs, как скачаешь что-то с github, там 73 файла, начинаешь их просматривать, по 5-10 строк в каждом файле, скачешь туда-сюда, где-то в середине SingleThreadID = DEF_ERR; что это, зачем, почему??? Хорошо если попался тип который комментарии нормальные написал, хотя обычно это комментарии в духе капитана очевидность. Типа "Define SingleThreadID".

а читать код с тонной однострочных методов раскиданных по десяткам файлов я не в состоянии

Это не ООП.

У меня это и были бы
AlertOnDeadlock(process_id)

И что если вам нужен AlertOnDeadlock в контексте не монитора производительности, а в контексте работы с базой данных или с портами ввода-вывода? Как вы будете их различать?

вы даете общие префиксы для названий процедур, относящихся к одной функциональности
Если я пишу процедуру void ShellSort(byte[] bytes)

Я же написал про несколько разных процедур, при чем тут пример с одной процедурой?
Что если вы пишете процедуры для работы с файлами? fopen, fread, fwrite, fclose. Или с сокетами - socket_open, socket_read, socket_write, socket_close. Что если вам надо работать в одном приложении и с сокетами и с файлами? Вы их будете различать по префиксу, иначе у вас компилятор выдаст ошибку, что объявлено две функции open. Это то же самое ООП.

скачешь туда-сюда, где-то в середине SingleThreadID = DEF_ERR; что это, зачем, почему???

Что бы изменилось, если бы все методы из этих 73 классов были в одном файле?

Это не ООП

я понимаю что это не совсем ООП, но так уж принято - для каждого класса свой файл.

И что если вам нужен AlertOnDeadlock в контексте не монитора производительности, а в контексте работы с базой данных или с портами ввода-вывода? Как вы будете их различать?

AlertOnDeadlockDB

AlertOnDeadlockIO

Это то же самое ООП

Я понял о чем вы. Нет, это не ООП, иначе всё что есть вокруг связанное с программированием стало бы ООП, а это не так.

Что бы изменилось, если бы все методы из этих 73 классов были в одном файле?

В начале файла были бы все дефайны, а 73 метода превратились бы в 4 процедуры.

AlertOnDeadlockDB
AlertOnDeadlockIO

Ну вот вам и ООП. От того, что вы для обозначения контекста использовали суффикс, а не префикс, принципиально ничего не поменялось. Можно взять все функции с суффиксом DB, и получится класс. Дальше можно подумать как сделать так, чтобы при их вызовах не надо было постоянно копипастить handle первым аргументом.

Нет, это не ООП, иначе всё что есть вокруг связанное с программированием стало бы ООП

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

В начале файла были бы все дефайны, а 73 метода превратились бы в 4 процедуры.

Можете привести пример? Я сильно в этом сомневаюсь. Смысл методов или процедур в том, что их можно вызывать из произвольных мест, и без копипасты реализации превратить их в 4 принципиально невозможно. Если бы было возможно, то и сделали бы 4 класса, а не 73.

вы говорите о понимании принципов

А. вон вы к чему это. Так проблема не в этом.

Можете привести пример?

Не уверен что смогу воспроизвести такие моменты. Как правило это конструктор, куча методов чтобы обойти private, куча методов как выше спор о том что там и как лает, а что занимается выводом в консоль. В итоге распатронивается суть приложения по разным модулям (классы, методы). У меня всё в кучу, поэтому и места не занимает, но да, кроме меня наверное никто и не прочитает код, но и я чужой код читаю очень плохо, даже если он ПП.

Проблема как раз в этом, вы делаете то же самое, что и другие с ООП, просто немного в другой форме, но не понимаете этого, и считаете, что ООП это что-то другое. Вы не замечаете сложности с вашим подходом, потому что пишете маленькие приложения. В больших, где работают 20-30 человек, вы бы сами пришли к выводу, что вот этот чужой код надо бы как-то сгруппировать, куда-то вынести, чтобы было понятнее, какая часть что делает.

Суть приложения у вас тоже распатронивается по разным процедурам, не вижу никакой разницы. Ну вынес кто-то процедуры socket_open/socket_close в отдельный файл, что от этого принципиально поменялось?

кроме меня наверное никто и не прочитает код, но и я чужой код читаю очень плохо, даже если он ПП

Тогда непонятно, почему вы хотите объяснить, что ПП имеет преимущества.

и считаете, что ООП это что-то другое

это не я так считаю:

Объе́ктно ориенти́рованное программи́рование (сокр. ООП) — методология или стиль программирования на основе описания типов/моделей предметной области и их взаимодействия, представленных порождением из прототипов или как экземпляры классов, которые образуют иерархию наследования[1].

Идеологически, ООП — подход к программированию как к моделированию информационных объектов, решающий на более высоком абстрактном уровне основную задачу структурного программирования — структурирование информации с точки зрения управляемости[2]. Это позволяет управлять самим процессом моделирования и реализовывать крупные программные проекты.

Процеду́рное программи́рование — программирование на императивном языке, при котором последовательно выполняемые операторы можно собрать в подпрограммы, то есть более крупные целостные единицы кода, с помощью механизмов самого языка[1].

Процедурное программирование является отражением архитектуры традиционных ЭВМ, которая была предложена Фон Нейманом в 1940-х годах. Теоретической моделью процедурного программирования служит машина Тьюринга. Выполнение программы сводится к последовательному выполнению операторов с целью преобразования исходного состояния памяти, то есть значений исходных данных, в заключительное, то есть в результаты. Таким образом, с точки зрения программиста имеются программа и память, причём первая последовательно обновляет содержимое последней.

Тогда непонятно, почему вы хотите объяснить, что ПП имеет преимущества

вроде нигде я такого не утверждаю.

это не я так считаю:

Это не противоречит моим словам. Вы пишете процедуры, да, но организовываете их в группы и обозначаете группу префиксом или суффиксом, аналогично классам с набором методов. Я пытаюсь объяснить, что ООП это почти то же самое, что делаете вы, а не что-то совершенно другое, и не стоит его так воспринимать.

вроде нигде я такого не утверждаю

Вы утверждаете в скрытом виде, когда говорите "Я пишу просто, а они сложно". Это сравнительные характеристики, которыми вы показываете положительные стороны ПП.

Я пытаюсь объяснить, что ООП это почти то же самое

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

Это сравнительные характеристики, которыми вы показываете положительные стороны ПП.

Это для меня и из-за меня, мы же говорим о моём непонимании ООП, а не ООП в целом. Поэтому это простое оценочное суждение, то как я это вижу. В этом и проблема.

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

Ну вот, мейнстримное ООП своими щупальцами уже определения корежит. Нет там в оригинале ни классов, ни иерархии наследования, ни даже полиморфизма.

Ну, вообще-то нет. Можно взять язык, в котором неймспейсы отделены от объектов, например Перл, и будут просто наборы функций с префиксом DB::

У меня это и были бы

А потом мы вместо того чтобы промониторить, что поток не жрёт выше определённого порога, выставили ему планку в обозначенный порог, потому что без явного указания на объект нельзя сказать, принадлежит ли AdjustSettingsThreadCPU к PerfMon, или к PerfLimiter.

И ООП ортогонально числу строк в методе или количеству файлов в проекте, я не понимаю, почему вы к этому привязались.

А потом мы вместо того чтобы промониторить, что поток не жрёт выше определённого порога, выставили ему планку в обозначенный порог, потому что без явного указания на объект нельзя сказать, принадлежит ли AdjustSettingsThreadCPU к PerfMon, или к PerfLimiter.

Не особо понимаю какое отношение ко мне имеет эта умозрительная конструкция. Вы придумали какой-то кейс, придумали проблему, с которой не справится какой-то умозрительный код написанный непонятно кем и непонятно как. И это якобы всё должно мне объяснить. Ну - не объясняет.

Если мне нужно будет промониторить что поток не жрёт выше определенного порога я посмотрю значение переменной ThreadLoad и причем тут вообще какой-то объект я не в курсе. Я сам лично знаю какая переменная и для чего в моей программе, для этого мне ООП не требуется.

И ООП ортогонально числу строк в методе или количеству файлов в проекте, я не понимаю, почему вы к этому привязались.

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

я посмотрю значение переменной ThreadLoad

А просмотр нужно синхронизировать с её обновлением каким-либо образом. Т.е. будет скорее GetThreadLoad(), чтобы руками не синхронизироваться каждый раз. Собственно, в этом моменте ООП и появляется: объединяются данные и способы работы с ними.

А просмотр нужно синхронизировать с её обновлением каким-либо образом.

SomeSync();
Console.WriteLine("ThreadLoad: {0}", ThreadLoad);

Т.е. будет скорее GetThreadLoad()

Можно код выше завернуть в процедуру, это сути не изменит.

Собственно, в этом моменте ООП и появляется

Нет. Иначе суть ПП была бы сутью ООП.

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

Никто тебе не мешает в Delphi писать всё в одном файле. А в C# на каждую форму тоже отдельный файл создается.

А в C# на каждую форму тоже отдельный файл создается

Я просто не пользуюсь Forms XD

в C# мне хватает Program.cs, как скачаешь что-то с github, там 73 файла, начинаешь их просматривать, по 5-10 строк в каждом файле, скачешь туда-сюда, где-то в середине SingleThreadID = DEF_ERR; что это, зачем, почему???

Вот поэтому раньше программистам хватало Vi, а теперь IDE с подсветкой синтаксиса и визуализацией структуры программы - обязательная часть инструментария.

Потому что языки стали делать такими, что без IDE не управиться. И именно поэтому продолжают появляться языки, которые позволяют вернуться к возможности работать без IDE в просто продвинутом текстовом редакторе. Сначала так взлетел Ruby (не только на этом, конечно, но картинка с двумя книжками против стопки Java впечатляла), потом появился Go. Наверняка и еще что-то будет.

Попробуйте посмотреть на ООП не как оно есть мейнстримно, а как оно было в оригинале - то, что Алан Кей сделал в Smalltalk. Т.е. посылка сообщений. И соответствующие языки, например Objective C (легко и быстро учится чистым сишником, в отличие от плюсов) или Erlang, на худой конец Perl (после некоторых сведений о том, как ООП типично реализуют внутри, типа таблиц виртуальных методов).

Возможно, после этого придет понимание мейнстримного ООП как кастрированного из сообщений в методы, и станет проще такой код читать (писать не предлагаю, мейнстримное сам не люблю).

я 25 лет иду рядом с ООП, но я совсем не могу так мыслить, а если не можешь так мыслить, то не можешь чужой код конвертировать в идею.

Ну и чем же сообщения принципиально отличаются от методов-то?

Ну и чем же сообщения принципиально отличаются от методов-то?

Наверное тем, что для того, что послать сообщение получателю, про получателя не нужно знать вообще ничего?

Не уверен что это преимущество. Всё же как программист вы обязаны знать хоть что-то о получателе, ведь иначе с чего бы вам вообще пришло в голову посылать это сообщение?

Кроме того, если такой свойство действительно необходимо - оно спокойно реализуется интерфейсами.

Не уверен что это преимущество.

На самом деле, это охренеть какое преимущество :-)

Всё же как программист вы обязаны знать хоть что-то о получателе ведь иначе с чего бы вам вообще пришло в голову посылать это сообщение?

Зачем?! Если "на пальцах": что вы "знаете" о получателе, когда отправляете сообщение, например, в какой-нибудь exchange AMQP брокера? Ну или, когда добавляете запись в какой-то topic kafka - вы много "знаете" о том, кто будет эту запись потом "читать"? А когда уже вы читаете запись и топика, вы что-либо "знаете" об "отправителе"? Зачем это вам?!

Кроме того, если такой свойство действительно необходимо - оно спокойно реализуется интерфейсами.

Ну, нет. "Интерфейсы" (а точнее, их реализация) - это просто "вот такая вот" форма subpyping'а. А protocol'ы smalltalk'а, behaviour 'ы erlang'а и прочая, и прочая - это не subtyping ни разу.

Я в одном, пожалуй, соглашусь... если рассматривать исключительно "статические" системы, то такого рода преимущество не совсем очевидно. Ну действительно - "зачем козе баян", если "всё, что может быть" мы и так уже описали "до того как".

Но как только вы перейдете к "динамическим" системам - полезность "такой штуки" становится более чем очевидна. Представьте себе, что у вас "рождаются и умирают" не только экземпляры типов, а сами типы - "рождаются и умирают".

если рассматривать исключительно "статические" системы

Ну то есть, исключительно такие, в которых можно спросить компилятор "а это всё вообще имеет смысл?", а не только постоянно "просто быть внимательным", да.

Ну то есть, исключительно такие, в которых можно спросить компилятор "а это всё вообще имеет смысл?" ...

Если я правильно понял о чём речь, то "компилятор" - сам по себе - тут вообще не при чем. Статический анализ применим и к языкам с динамической типизацией. Главное, чтоб она (типизация) строгой была.

Но я-то не про типизацию... я про связывание (binding). Оно - вообще говоря - ортогонально типизации.

Зачем?! Если "на пальцах": что вы "знаете" о получателе, когда отправляете сообщение, например, в какой-нибудь exchange AMQP брокера?

Здоровенную схему, описывающую сообщение плюс описание в документации, что этот получателе делать должен. Потому что если не знаем - то смысла посылать? Просто на всякий случай, если вдруг обработчик события найдется? А почему именно этого события а не другого? Или вообще на каждое движение будем броадкасты посылать, потому что когда-нибудь по какому ни будь поводу захочет это обработать?

Так что знание таки всегда есть, пусть и не формализованное в языке.

Здоровенную схему, описывающую сообщение ...

Какой "объем знаний о получателе" это нам добавляет?

... плюс описание в документации, что этот получателе делать должен.

:-) А это точно нужно знать отправителю?!

Потому что если не знаем - то смысла посылать?

Например, потому что "что, куда и когда" определяется не отправителем (и не получателем), а протоколом. Точнее, контекстом, в котором сейчас находится отправитель, и протоколом, который этим самым контекстом задается.

Если вам удобнее смотреть на это (контекст и протокол) через призму "интерфейсов", то упрощенно это можно представить себе некоторый медиатор (в терминах GoF).

Так что знание таки всегда есть, пусть и не формализованное в языке.

Ну вот и представьте, что это таки может быть "формализованное в языке".

Какой "объем знаний о получателе" это нам добавляет?

Примерно соответствующий (в вашей аналогии) сигнатуре метода в ООП

Ну вот и представьте, что это таки может быть "формализованное в языке".

Ну так оно и формализовано в большинстве языков, в виде интерфейсов...

Примерно соответствующий (в вашей аналогии) сигнатуре метода в ООП

Каким образом?! Я ещё могу представить, как из "здоровенной схемы, описывающую сообщение" выводятся функциональные типы. Это не сложно. Но, что они вам дадут?! Что бы "выйти" на методы нам нужны имена. А тут они - буквально - любые.

Ну так оно и формализовано в большинстве языков, в виде интерфейсов...

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

Но, что они вам дадут?! Что бы "выйти" на методы нам нужны имена.

Имя метода обычно содержится в сообщении, в той или иной форме.

Только на уровне формализма подтипа. Его - чаще всего - не достаточно.

Достаточны только завтипы, и то не факт. Но недостаточный формализм не то же самое что отсутствующий. Кроме того, вы так и не показали, что ваш формализм лучше.

Имя метода обычно содержится в сообщении, в той или иной форме.

Это только если какой-то аналог RPC делается. Сообщение это данные. Обычно, если там и есть имя - это тип этих данных. Но ни как не тип потребителя этих данных. Тем более, что обработчик - вообще говоря - может быть и не один.

Достаточны только завтипы, и то не факт.

С одной стороны, хочется уточнить: достаточно для чего? А с другой - формализмы зав. типов - это "охренеть как смело" (с) :-)

Кроме того, вы так и не показали, что ваш формализм лучше.

Мой это какой?! Я - вроде как - никаких новых формализмов не вводил.

Сообщение это данные. Обычно, если там и есть имя - это тип этих данных. Но ни как не тип потребителя этих данных.

А какое отношение имя метода имеет к типу потребителя данных?

Мой это какой?! Я - вроде как - никаких новых формализмов не вводил.

Тогда зачем вы их вообще упомянули?

 а протоколом

Протокол (если он соблюдается) - это и есть знание и об отправителе и о получателе. О том, что они его соблюдают и что-то вполне конкретное в процессе делают. Об их устройстве, да, в нем ничего не сказано.

Ну, нет. "Интерфейсы" (а точнее, их реализация) - это просто "вот такая вот" форма subpyping'а. А protocol'ы smalltalk'а, behaviour 'ы erlang'а и прочая, и прочая - это не subtyping ни разу.

И в чём же разница-то, блин?

И в чём же разница-то, блин?

:-) Ну вот как это объяснить "на пальцах"?

Смотрите... вот у нас есть обычные типы (что бы это не значило). А есть функциональные типы. Какая между ними "разница"? Достаточно ли будет сказать, что функциональные имеют более высокий порядок? И в чём разница между типами разного порядка?

Или - с другой стороны заход - есть тип, а есть его экземпляры (что бы это не значило). Вот представьте себе эдакий НЕХ - экземплярами которого являются типы. Какая между ними разница? Мы на уровне типов даже выразить этот НЕХ (да... это про категорию типов) не сможем :-(

Т.е. эти вот "протоколы"/"поведения"/"классы типов" - это всё про "ограничения", которые накладываются не на уровне самих типов. Это про ограничения уровнем (я порой и не одним) выше, так сказать.

Когда вы реализуете "интерфейс", вы - фактически (через subtyping) - накладываете ограничения на [используемые] типы. С одной стороны, это вроде как "то, что нужно". А с другой - очень часто, это "не те" (либо слишком строгие, либо слишком слабые) ограничения, которые мы (на самом деле) хотим. По большому счету - это из-за того, что сам "интерфейс" - это тоже тип. Соответственно, он и описывается исключительно в "терминах" типов, со всеми их ограничениями. Даже если притащить сюда ещё и параметрический полиморфизм, то ограничения на параметры обобщенного типа - всё равно описываются в терминах типов. Не вырваться :-(

А если мы будет - по сути - тоже самое делать "уровнем выше", то никаких проблем такого рода у нас просто не будет.

Если идти дальше, то это уже какой-то "код" надо будет писать, чтобы показать "разницу". Оно надо?

Или - с другой стороны заход - есть тип, а есть его экземпляры (что бы это не значило). Вот представьте себе эдакий НЕХ - экземплярами которого являются типы. Какая между ними разница? Мы на уровне типов даже выразить этот НЕХ (да... это про категорию типов) не сможем :-(

Эта "НЕХ" называется либо метаклассом (если смотреть с точки зрения реализации), либо родом (если смотреть с точки зрения теорката).

Т.е. эти вот "протоколы"/"поведения"/"классы типов" - это всё про "ограничения", которые накладываются не на уровне самих типов. Это про ограничения уровнем (я порой и не одним) выше, так сказать.

А вот и нет, протоколы находятся на том же уровне, что и типы.

Даже если притащить сюда ещё и параметрический полиморфизм, то ограничения на параметры обобщенного типа - всё равно описываются в терминах типов. Не вырваться :-(

А зачем вырываться-то? Вы вообще сейчас решаете философскую задачу или прикладную?

А если мы будет - по сути - тоже самое делать "уровнем выше", то никаких проблем такого рода у нас просто не будет.

Каких именно проблем?

Попробуйте посмотреть на ООП не как оно есть мейнстримно, а как оно было в оригинале - то, что Алан Кей сделал в Smalltalk.

Про "как" и "почему так", Алан "сделал в Smalltalk" лучше, имхо, почитать у самого Алана. Благо его The Early History of Smalltalk давно есть в сети. Очень познавательно.

Вот вам цитата. Это прям самое начало... даёт представление о "какую проблему решаем".

Though OOP came from many motivations, two were central. The large scale one was to find a better module scheme for complex systems involving hiding of details, and the small scale one was to find a more flexible version of assignment ...

Но лучше самому почитать, имхо. Там не много...

Т.е. посылка сообщений.

"посылка сообщений" - aka message passing - это то, что, в конечно счете, станет решением для этой самой "small scale one". То, что он сам потом назовет "extreme late binding of all things".

Вот этот общий префикс и есть класс.

К счастью, не все языки заставляют такое упаковывать в классы. :) Для подавляющего числа случаев тут логичнее выделять функционал в МОДУЛЬ, а не чесать левое ухо правой рукой (статические классы / синглтоны).

А вот если по смыслу у нас есть реальные объекты (множественное число), тогда и классы использовать.

В большинстве случаев не нужны синглтоны и статические классы, просто не создаем второй экземпляр если он не нужен.

логичнее выделять функционал в МОДУЛЬ

И как потом его в тестах замокать? Вот есть у меня модуль для работы с файлами, и компонент, сохраняющий какие-то файлы, который импортирует этот модуль. Я хочу проверить, что мой компонент выдает ошибку, если file_write() вернула, что нет места на диске. С объектом я могу переопределить реализацию и задать в ней нужный код ошибки. А с модулем как?

Так в том и смысл, чтобы минимизировать связанность. Тестировать нужно отдельно сам модуль.

А про текст ниже -- не понял вопрос. У нас тут никаких объектов нет, есть использование функционала из внешнего модуля. Импортируем модуль, вызываем file_worker.save_all(_данные_нашего_компонента), где ошибку получаем через res/err = file_worker.save_all, либо try cach, в зависимости от языка и реализации.

Тестировать нужно отдельно сам модуль.

Я говорю не про тесты на модуль, а про тесты на мой вызывающий код, который работает с этим модулем. Как мне написать юнит-тест, что мой код работает правильно когда модуль файлов выдает ошибку "На диске нет места"? Я не хочу реально занимать все место на реальном диске ради теста, я хочу подменить функцию file_write на заглушку, которая притворяется, что на диске нет места.

У нас тут никаких объектов нет

Я знаю, что нет, поэтому и вопрос, как без них тестировать вызывающий код.

 я хочу подменить функцию file_write на заглушку, которая притворяется, что на диске нет места.

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

Инструменты - они разные бывают.

Ок, только зачем тогда вообще использовать модули, если с ними нужны такие сложности? Было утверждение "логичнее выделять функционал в МОДУЛЬ". Если получается сложнее, значит это не логичнее.

Ну это в предельном случае. Так-то это все сильно зависит от возможностей языка. Если он позволяет сделать что -то в духе

'use mock_library_module.file_write as file_write' а не прибивает все вызываемое гвоздями, то вызов file_write - отлично заменится тем, чем нам хочется прямо в терминологии модулей, а не объектов.

Правда, у меня есть стойкое подозрение, что это все - таки терминологический вопрос и модуль - можно рассматривать как объект, единственный экземпляр которого прямо стандартным рантаймом создается при упоминании, а не руками или через какой-то фреймворк DI.

что мой код работает правильно когда модуль файлов выдает ошибку "На диске нет места"

Аааа, вот ты про что. Ты про интеграционные тесты. Допустим, твой основной код выглядит как-то так:

import file_worker
some_data = ...
res = file_worker.write(some_data)
if (error == ERROR_DISK_FULL) ... обработчик_этого_сценария()

И, как я понял, тебе мало модульных тестов (тестирования конкретного вызова "обработчик_этого_сценария() ").

Тогда тут уже зависит от желаемой глубины этой интеграции. Или простая строчка для подмены функции:

import file_worker
file_worker.write = lambda some_data: ERROR_DISK_FULL

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

import file_worker
file_worker.set_debug_scenario(ERROR_DISK_FULL)

Ты про интеграционные тесты.

Нет, я про юнит-тесты своего кода. Интеграционные это если бы я на реальный диск писал.

мало модульных тестов (тестирования конкретного вызова "обработчик_этого_сценария() ")

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

Или вообще реализация отладочного функционала в модуле file_worker.

Ну и зачем нужны эти сложности, если можно просто использовать объекты?

file_worker.write = lambda

Судя по этой записи, file_worker это самый настоящий объект. Ну так о том и речь, что их удобно использовать.

Нет, я про юнит-тесты своего кода. Интеграционные это если бы я на реальный диск писал.

Ну тут вопрос в терминологии. :) Для меня юнит-тест (тест модуля) -- это чистый тест функции обработчик_сценария_когда_диск_заполнен() без "дергания" другого юнита (модуля file_worker). Если его "дергаем", то это уже интеграция двух наших модулей (главный и "файловый").

Ну да не важно.

Ну и зачем нужны эти сложности, если можно просто использовать объекты? Судя по этой записи, file_worker это самый настоящий объект. Ну так о том и речь, что их удобно использовать.

Конечно можно назвать его объектом. Только классов тут никаких нет. :) Никакого мусорного лишнего кода и ненужных абстракций. Ни в главном коде, ни в модуле file_worker.

Только классов тут никаких нет. :)

Есть. (полу) анонимный. Вот имя модуля - и есть класс. Просто мы объекты этого класса сами не создаем, да и то можно считать, что 'import <имя модуля>' - это есть создание объекта.

Та это все понятно. Мы ж про другое беседуем. Я про то, что не нужно именованные классы вводить там, где их не существует по смыслу. Но, конечно, это не касается языков, где по-другому ты просто не можешь сделать (Java/C#). :) Если язык позволяет создавать модуль -- его логичней всего и создавать в такой ситуации.

Для меня юнит-тест - это чистый тест функции обработчик_сценария_когда_диск_заполнен() без "дергания" другого юнита (модуля file_worker)

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

Только классов тут никаких нет.

Ну как это нет, очень даже есть, просто в неявном виде. file_worker это объект этого класса. И конструкторы у них есть в виде файлов __init__.py.

Вы как господин Журден из комедии Мольера "Мещанин во дворянстве", который сорок лет не знал, что говорит прозой. Строго говоря, совсем без ООП эффективно использовать С# вряд ли выйдет, потому что вся сила С# в большом количестве готовых классов на все случаи жизни, которыми мы обычно радостно пользуемся. Мне трудно представить себе сценарий, в котором было бы целесообразно использовать С# как язык, не используя при этом его классы. Собственно, в этом и фишка ООП - можно наклепать универсальные готовые классы и использовать их во множестве разных проектов, экономя таким образом время. Писать свои классы в C# действительно нужно довольно редко, но это не значит, что вы не используете ООП. Если вы в своей программе хоть как-то работаете со строками, вы используете свойства, методы и события класса String, если с числами - класса ValueType и т.д. Вряд ли вы написали хоть одну программу на С#, которая не создает объекты.

Если я напишу что не использую ассемблерные вставки в своих программах, а потом вдруг выяснится, что реализация popcnt в стандартной библиотеке написана на ассемблере, то будет ли это считать за использование? Мне кажется - нет.

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

10 лет не создал ни одного класса, то есть заворачивал ПП в разные обработчики

Уверен, что вы и ни одной библиотеки и ни одного фреймворка за 10 лет не написали. lol

не написал, а должен был?

Из того что вы не создали за 10 лет ни одной библиотеки и ни одного фреймворка, вовсе не следует, что они "бесполезны" и "ненужны".

А почему мои слова вообще должны вас навести на мысли о том, что ООП "бесполезен и не нужен"? Вы свои фантазии мне не приписывайте.

Я как раз пишу комментарий на статью показывая, что я один из тех кто не понимает ООП. Никакой другой информации там нет.

Да нет, все ты правильно понимаешь. :) Когда ты пишешь для себя, сам же и поддерживаешь, то лучше замечаешь все плюсы и минусы подходов. И тебе сложнее голову задурить, если ты сам попробовал и так, и сяк, и можешь сравнивать.

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

если ты сам попробовал и так, и сяк, и можешь сравнивать

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

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

В любом случае я если и создаю класс, то это скорее по необходимости, как альтернатива struct. Вот здесь всё идут споры про класс собаки и то как именно работать с её лаем и очень хорошо видно что даже те кто пользуется ООП (или так думает) не имеют консенсуса. И я туда влезать не хочу. Если я хочу вывести на экран "bark", то я это делаю, мне не нужен класс или метод для этого. И последнее что меня волнует - сможет ли кто-то еще понять что я написал.

Если я хочу вывести на экран "bark", то я это делаю, мне не нужен класс или метод для этого.

Вы в очередной раз подменяете понятия. Если нужно просто вывести на экран "bark", то с ООП тоже просто берут и выводят на экран "bark". А сложности начинаются, когда надо не просто вывести.
Метод это то же самое, что процедура, поэтому это вообще неверно, код на процедуры вы разделяете.

Для интереса попробуйте написать простую программу, которая выводит на консоль собаку ASCII-кодами, ее положением можно управлять стрелками с клавиатуры, а если нажат пробел, то программа показывает "bark" возле носа собаки. А потом сделайте так, чтобы выводить можно было не только на консоль, а еще и графикой в оконном режиме.
Потом еще можно сделать, чтобы при нажатии + и - собаки добавлялись и удалялись, а текущая для управления выбиралась табом. С ООП это всё сделать довольно просто.

И последнее что меня волнует - сможет ли кто-то еще понять что я написал.

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

Вы в очередной раз подменяете понятия. Если нужно просто вывести на экран "bark", то с ООП тоже просто берут и выводят на экран "bark". А сложности начинаются, когда надо не просто вывести.Метод это то же самое, что процедура, поэтому это вообще неверно, код на процедуры вы разделяете.

Мне кажется вы старательно пытаетесь сказать что никаких парадигм нет и придерживаться чего-либо не стоит и вообще всё одинаковое. Я с этим не согласен. Метод и процедура это сильно разные вещи. Метод относится к классу, а класс описывает какой-то объект со своими свойствами. А вы хотите чтобы я к этому относился как к условности ни к чему не обязывающей.

С ООП это всё сделать довольно просто.

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

Это вообще никаким образом нельзя считать аргументом в защиту вашего подхода

Я совсем ничего не защищаю, я только сказал что не понимаю как писать программы в ООП, у меня голова не мыслит таким.

А я знаю, но проблема в том, что я не умею так проектировать приложение.

Так тут дело не в этом, а в том, что для этого приложения вы не будете помещать printf на консоль в функцию bark() даже в вашем подходе. Об этом и шла речь в дискуссии про собаку.

Я совсем ничего не защищаю

Вы явно написали "даже те кто пользуется ООП не имеют консенсуса", это указание на недостаток подхода. А я указал на то, что консенсус был не найден не про то, что вы думаете, и в вашем подходе точно также надо будет его искать.

Так тут дело не в этом, а в том, что для этого приложения вы не будете помещать printf на консоль в функцию bark() даже в вашем подходе. Об этом и шла речь в дискуссии про собаку.

я не понял что вы путаетесь мне сказать, вывод в консоль я размещу там где мне захочется.

Вы явно написали "даже те кто пользуется ООП не имеют консенсуса", это указание на недостаток подхода

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

и в вашем подходе

Я сам не особо понимаю что такое "мой подход", но вы точно знаете за меня )

я не понял что вы пытаетесь мне сказать

Именно поэтому я предложил вам самому написать программу, а не обсуждать.

вывод в консоль я размещу там где мне захочется

Если вы будете реализовывать указанные требования, то поместить printf в фукнцию bark невозможно, потому что тогда она не будет работать в графическом режиме.

люди, которые могут говорить что-то про ООП сами не могут определиться с этим

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

Если вы будете реализовывать указанные требования, то поместить printf в фукнцию bark невозможно, потому что тогда она не будет работать в графическом режиме.

передавать в процедуру bool vmode о том, нужно ли выводить в графический режим или в консоль

Мне хочется поместить его в одном месте, а моему собеседнику в другом, от используемого подхода это не зависит

Это ваше мнение, но оно не обязательно верное, то что я знаю про ООП как раз не позволит это делать как-то иначе.

Это ваше мнение, но оно не обязательно верное

Мое мнение относительно причин дискуссии о собаке 100% верное, потому что я ее участник.

передавать в процедуру bool vmode о том, нужно ли выводить в графический режим или в консоль

Да. А вот если вы ее будете обсуждать с другим человеком, он вам может предложить не писать этот bool и if в десятке процедур, а вынести их куда-нибудь наружу и написать один раз, и сделать процедуры renderText() и renderGraphic(), откуда вызывать всё что нужно для конкретного режима. И у вас с ним будет такая же дискуссия.

И у вас с ним будет такая же дискуссия

не будет. если за код отвечаю я то другой человек идёт лесом, если он - пусть пишет как хочет, если я начальник - он напишет так как сказал я, если он начальник - я напишу как сказал он.

Мое мнение относительно причин дискуссии о собаке 100% верное, потому что я ее участник.

разговор не о мнении относительно дискуссии, а о том, как в ООП реализуется подход. И договоренности там нет, хотя каждый скорее всего считает что он прав.

PS: даже если взять Хабр как пример, то тут нередки холивары, хотя каждый в отдельности считает себя экспертом в обсуждении. У меня был момент в жизни когда я оказался на форуме медицинских работников и они не могли сойтись во мнении касательное лечения определенного заболевания, там уже мат был кругом и притом что люди там были не просто какие-то с улицы, а с регалиями и медалями. А если почитать про C++ или Linux, То там вообще волосы дыбом встают как сами разработчики там срутся.

не будет

Ну как это не будет, если она уже идет у нас с вами) Вы говорите, что лучше передавать bool, я говорю, что лучше вынести. Вот и в другой ветке была такая же дискуссия. Там нет никакого начальника, и программу никто фактически писать не собирался.

разговор не о мнении относительно дискуссии, а о том, как в ООП реализуется подход

Я сказал "Мне хочется поместить printf в одном месте, а моему собеседнику в другом", вы сказали, что это лишь мое мнение. Это не мое мнение, а факт, мы обсуждали, где его лучше поместить.

Сама дискуссия была не о том, насколько код соответствует каким-то правилам правильного ООП, а о конкретных проблемах, которые будут появляться при тех или иных решениях. Большинство этих аргументов применимо и к процедурам.

И договоренности там нет

Ну так и с процедурами нет, вы говорите, что надо передавать bool в bark(), а я говорю, что не надо передавать.

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

Почему не показатель? Вполне себе показатель. Не все могут реально сравнивать подходы. Делают лишь "потому что так надо". Или не умеют по-другому. Или язык "заставляет".

А ты (как заказчик и разработчик в одном лице) можешь оценить и время разработки, и ментальную нагрузку на проектирование, написание и сопровождение кода.

споры про класс собаки и то как именно работать с её лаем и очень хорошо видно что даже те кто пользуется ООП (или так думает) не имеют консенсуса.

Именно. :)

Если я хочу вывести на экран "bark", то я это делаю, мне не нужен класс или метод для этого.

Эх, а мог бы создать бригаду строителей, приставил бы к ним наблюдателя. Они бы построили абстрактную фабрику по производству различных прототипов собачек, которые умеют разное. Лаять, прыгать, кусать, писать код. Написал бы адаптер для собачек, чтобы они умели лаять на экран.

В общем, вот это вот все делать... Лишь бы не работать. :)

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

только постфактум, так что это не суперсила

Да, но с каждым проектом набираешь же опыт и становишься более эффективным, не теряя время на размышление/проектирование странных, ненужных и неуместных вещей, которые не привносят ничего полезного. Либо наоборот, заранее понимая, куда нужно подстилать соломку.

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

Посмотреть как у других сделано?

Я тоже начинал с паскаля, потом делфи, и нигде не мыслил в парадигме ООП. Стандартные объекты использовал, но свои классы не создавал. Потом на первой работе переучился на c++ builder, но первые лет 5 продолжал писать на нём в процедурном стиле.

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

Что интересно, на pl/sql я продолжаю писать в процедурном стиле, а на плюсах не могу...

Ну вот этого щелчка мне и не хватает. У меня есть пара программ которые написаны в ООП, сначала написал ПП, а потом параллельно переделал в ООП вторым проектом. Во-первых это было мучительно, во-вторых, когда потребовалось что-то поменять в ПП это пара новых массивов, переменных и пара переписанных функций, а в ООП всё, приехали, только писать заново, так как изменилось слишком много и сразу. Причем для пользователя вообще ничего не поменялось визуально.

В целом мне не хватает этого понимания как минимум для чтения чужого кода.

Посмотрите в сторону Haskell или OCaml, должно щелкнуть =)

 fac :: Integer -> Integer
 fac 0 = 1
 fac n | n > 0 = n * fac (n - 1)

я такое не переварю, для меня C# прям 90% того что мне нравится в языке и как я его интуитивно воспринимаю. Я плохо запоминаю сам язык и то как он строится, вот с C# мне очень просто - я пишу какую-то ерунду и она сразу правильная, либо исправляется легко. Причем за последние годы там столько понавводили нового что прям няшка. Но многое сильно умнее моих способностей, но всё равно приятно. Очень нравится что в стандартные библиотеки напихано всего очень много.

Ну вот вам C# :-)

int fac(int n) => n switch {
    0 => 1
    int n1 when n1>0 => n1 * fac(n1 - 1)
};

Я не зря написал "Но многое сильно умнее моих способностей", так что ваш пример это как раз то, как я никогда не пишу на C#.

Ещё в современных «корпоративных» приложениях я заметил то, что они на самом не являются объектно-ориентированными. Сущности, DTO и так далее — это записи, а не объекты. Бины, сервисы, репозитории не хранят состояния и вполне могут быть простыми функциями в модулях.

Мы используем языки, которые заставляют нас думать с точки зрения классов с архитектурами, не требующими объектов — Spring Boot вполне можно было написать на C.

Т.е. весь этот расчудесный прекрасный ООП, при столкновении с суровой практикой выродился по факту в старый, добрый Си? Какая неожиданность :)

Т.е. весь этот расчудесный прекрасный ООП, при столкновении с суровой практикой выродился по факту в старый, добрый Си? Какая неожиданность :)

да нет, вы просто не там ищете. У кого-то действительно все вырождается в старый, добрый Си, но это не значит что не существует систем в которых возможности ООП используются по полной программе.

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

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

Делать состояние в бинах - плохая идея, потом выгребешь с этого массу проблем. Делать состояние в сервисах - плохая идея, потом выгребешь с этого массу проблем. Делать состояние в котроллерах - плохая идея, потом выгребешь с этого массу проблем. Это по факту модули.

Внедрять код в DTO - плохая идея, потом выгребешь с этого массу проблем. Внедрять код в энтити - плохая идея, потом выгребешь с этого массу проблем.. По факту это все структуры.

Наследование увлекаться не стоит - получишь жесткое связывание и хрупкую архитектуру.

Вот и получаем в итоге, как-бы ООП, а по факту просто модули с процедурами и переменными с областью видимости внутри модуля. Реально то-же самое без проблем можно на С написать и оно будет не хуже работать и выглядеть в виде кода.

ЗЫ вот опять я влез в полемику на хабре.. прощай карма :)))

Да нет, почему. Нормальное мнение. Хоть я с ним и не совсем согласен.

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

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

Конечно может быть состояние, но как правило если оно есть, то это сильно повышает вероятность проблем с этим бином. Причем проблем сложновычленяемых и сложно отлаживаемых. Поэтому без крайней необходимости лучше этого не делать

Архитектурное разделение - так и модули того-же сишника прекрасно с этим справляются.

Если нужна хитрая структура с предварительно заполненными полями - сделать функцию, которая ее создает. Как по мне так куда лучше - мы видим функцию и понимаем что она создает структуру особенным образом. С объектом - мы видим объект, который выглядит как DTO, и не ожидаем что там поля как-то хитро заполняются при создании. А городить фабрики - ну так и чем это лучше функции, создающей и заполняющей структуру?

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

про солиды вот попробуйте прочитать пример практического применения, который мне очень пригодился тоже на практике, кстати. Там вроде как про интерфейсы в основном, в примере.

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

Реально то-же самое без проблем можно на С написать и оно будет не хуже работать и выглядеть в виде кода.

так раньше и писали: структура вместо класса, поля с указателями на функции вместо методов и, заодно, вместо виртуальных функций, ...

проблема только в том что в коде не видно что функция принадлежит - работает с этой структурой, ну и деструктор (если он есть и нужен) надо вручную вызывать, поля с "методами" надо вручную инициализировать

вот я вам и описал функциональность компилятора С++, которая изначально отличала его от компилятора просто-С.

Дальше как вы будете называть свой способ организации программы: процедурный, объектно ориентированный, автоматный, или еще какой, это исключительно ваши проблемы.

Но я где то слышал или читал, что в объектно ориентированном подходе объектом является что угодно, поэтому если корневым объектом выбрать процедуру, то, в принципе, и процедурный способ можно считать подвидом объектно ориентированного.

Интересно кого больше: тех кому понравится такая логика или тех кто всеми фибрами души против?

Голосуйте пожалуйста, (мой прогноз - уйду в ощутимый минус :)

Хитро - минусить могут "не только лишь все" :)

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

По факту мы имеем замаскированное под ООП обычное процедурное программирование с модулями

Ха. :) То же самое как раз выше написал. :)

А вот что реально дает другой экспириенс, так это частичное использование функционального подхода (как же приятно читается потом что-то типа res -> func1 func2 func3, если у нас есть объект (в виде данных) и серия операций над этим объектом).

А какие проблемы будут от внедрения кода и состояния в сервисы, передаваемые по сети объекты и т.д.?

Такие же как и всегда от смешения ответственности: невозможно внести небольшие изменения (потянешь за одно место - тянется вся лапша), одновременная работа двух людей над одним классом/файлом (что порождает трудноразрешимые конфликты).
А в рантайме - разрушение консистентности. Приходится нудно поддерживать одинаковый статус на нодах при разрывах соединений, например. А код в бинах, если не очень чистый (в функциональном смысле - например, берёт системную дату) может выдавать разный результат с непредсказуемыми последствиями.
Я лично на всём этом сильно обжигался, когда приходил на вакансию где требовалось "уменее разбираться в чужом коде"

При этом ядро linux, хоть и написано на C, но является объектно-ориентированным. Так и живем.

Да и любая более-менее сложная программа на С начинает обретать признаки ОО архитектуры.

Только в некоторых подсистемах. Не глобально. Называть /sys объектным - сова на глобусе.

Так никто не говорит про /sys. Я скорее про общий подход с интерфейсами и их имплементацией через коллбеки в структурах. Там даже есть некоторый вариант наследования, когда в сабклассах переопределяется только часть коллбеков, например.

По моему мнению, как раз ООП, помогает найти тот единственный язык на котором будет говорить все команда и доменные эксперты. Такой код самодокументирован и в нем абстракции лишь помогают понять о какой предметной задаче мы говорим. Если код пишут программисты для программистов, то тогда и рождаются эти ObjectFactoryFactorySingleton и прочая несуразица, которая лишь мешает. Об этом и говорят на каждой лекции по DDD уже 10 лет, а то и больше. Ubiquitous Language и Bounded Context. Тогда и программист и аналитик и тестировщик и менеджер будут знать, что это за ObjectFactoryFactorySingleton, если они об этом договорились и ВСЕМ предельно понятно, что это и зачем, что это часть языка на котором говорит и бизнес и те кто этот бизнес автоматизируют.

Да, надо было еще про strongly typed identifier вспомнить, это ж надо какое кощунство! Над обычными UID или Int64, еще и абстракции вводить.

DDD не более чем хайп - в книжке о нём всё полезное заканчивается сразу после заголовка.

UFO just landed and posted this here

Люди разные. Кто-то мыслит существительными, кто-то глаголами. Мне лично ближе объекты.

Статья несколько сумбурная но неплохая.

Основная мысль, которая почти всегда теряется при обсуждении ООП, это то, зачем он нужен и в чем его суть. Все сводится к инструментам, их правильном и неправильном использовании, практичных архитектурах и оверинженеринге.

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

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


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

ФП подходит с другой стороны - максимально старается избежать состояния и работать только с текущим контекстом. Подход не лишен логики, но любая программа - это прежде всего состояние.

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

Если не думать о программе в терминах состояний, то многие вещи становятся гораздо проще. Например, параллельное программирование.

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

Вот ещё одно определение банды четырёх: «Объектно-ориентированные программы состоят из объектов. Объект включает в себя и данные, и процедуры, которые могут обрабатывать данные. Процедуры обычно называют методами или операциями»4. Неплохо для начала, но мне кажется, что здесь не хватает важной характеристики объектов.

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

Главное в ООП - это то, что есть данные, и есть методы которые напрямую работают с этими данными. Это то, что можно засунуть в один объект. Другой объект, который хочет получить данные из первого объекта, обязан пользоваться публичными методами.

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

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

Все остальные штуки - наследование, интерфейсы и все другое - это не суть ООП, а различные варианты решения или оптимизации различных кейсов

Сущности, DTO и так далее — это записи, а не объекты.

Ну что только не придумают, чтобы запихнуть побольше логики в один объект. Уже и не все объекты являются объектами. Назвали по-другому и притворились, что все нормально. Да нет, сущности это и есть исходная причина появления классов и объектов в языках программирования. Вот те самые Cat и Dog.

Объекты — это сущности, сочетающие в себе свойства процедур и данных, потому что они выполняют вычисления и хранят локальное состояние.
Объект включает в себя и данные, и процедуры, которые могут обрабатывать данные.

Да, только дело в том, что эти процедуры могут работать только с теми данными, которые находятся внутри объекта. Поэтому помещать в сущность заказа отправку email в любом виде неправильно, там есть только поля, которые хранятся в базе.

Информация о том, что у сущности есть свойство name - это не деталь реализации. Деталь реализации, это как именно оно хранится внутри класса. Например, в PHP в ActiveRecord оно может храниться как $this->data['name']. Вот эти детали надо скрывать, а запись значения в name нет. Поэтому сеттеры у сущности это нормально, и ничем не противоречит ООП.

По какой-то причине ООП приводит к переусложнению всего.

1. Потому что считают, что информация о наличии свойства у сущности это деталь ее реализации. Половина сложного кода, который я встречал, была связана с попытками поместить логику в сущность, которая загружается из базы в рантайме, чтобы не выставлять свойства наружу.
2. Потому что считают, что делать любые изменения в классе после его появления неправильно. С этим связана вторая половина сложного кода, который я встречал. Декоратор, интерфейс для декоратора, базовый класс для декоратора, фабрика для декоратора, интерфейс для фабрики, конфиг для интерфейса, конфиг для вызывающего кода. Всё вместо того, чтобы добавить новую строку в тот же класс и пробросить его по прямому названию. Зато можно реализацию одной строчкой в конфиге поменять. Найти бы только сначала, где менять, да еще так, чтобы где не надо не поменялось.

 Потому что считают, что делать любые изменения в классе после его появления неправильно.

...

 Зато можно реализацию одной строчкой в конфиге поменять. 

Тут срабатывает боязнь (возможно, довольно обоснованная) перекомпиляции. Что-то куда-то добавишь, а расползшиеся по вселенной места использования предыдущей версии потом сломаются. Да еще новый скомпилированный артефакт тестировать, одобрять, утверждать к использованию, до прода тащить... Легче конфиг поменять, и тогда уже скомпилированный код сразу свое поведение изменит и тестировать заново его не придется. И никакой(меньше) организационно-бюрократической волокиты.

Где-то так.

Могу ошибаться, но кажется изменение настроек DI-контейнера в компилируемых языках в любом случае означает необходимость перекомпиляции. Даже если нет, я представлял контекст PHP, там это неактуально, но интерфейсы с маппингом на класс в конфиге часто используются, хотя второй реализации не планируется, и в тестах можно замокать сам класс. При этом часто используется аргумент "С классами при изменении реализации надо будет по всему коду искать и менять импорты", хотя в IDE это делается за несколько минут. Особенно интересно, когда надо в части мест поменять, а в части оставить, с конфигом это получается сложнее.

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

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

Я думаю речь о том, что существуют, к примеру, числа и строки.
И они конечно в абстрактном смысле являются объектами, но не являются полноценными ООП объектами в том смысле, что не следят за своими инвариантами и целостностью.
Точно так же можно использовать структуры DTO как "базовые переменные" и для простоты не считать их объектами, чтобы не путаться.

Но понятно это условность лишь с точки зрения определенных правил.
Например можно так охарактеризовать структуры для сериализации.

Две проблемы не раскрыто.

1. Ухудшение структуры кода при наследовании реализаций.

При неограниченном наследовании реализаций структура кода "размягчается". Возникают странные иерархии классов, не соответсвующие архитектуре, а существующие только чтобы что-нибудь "перекрыть" с не менее странными классами если взглянуть отстранённо. Лезут анипаттерны вроде "Паблик Морозов".

2. Неадекватное повышения связанности ООП кода.

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

UFO just landed and posted this here

Разве это проблема ООП, а не плохого программиста, который создает непонятные иерархии?

Именно проблема ООП, которое позволяет и подталкивает к такому. В большинстве случаев наследование реализации вообще не нужно, но у ООП-программистов нет идиосинкразии к этому, а у кого есть — значит он уже начал расти за пределы привычного ООП-подхода.

то же как раз кейс, где ошибочно использовать абстрактный класс, а нужно использовать интерфейс

Класс, интерфейс — неважно, проблема остаётся. Причём проблема элементарно решаемая средствами функционального программирования.

UFO just landed and posted this here

" Возможность именовать как вздумается переменные подталкивает программиста выбирать необдуманные названия "

может и бред, но после вашего комментария я задумался что было бы клёво иметь язык программирования, который на уровне компилятора запрещал бы криво именовать переменные. да, clang-tidy не дает делать однобуквенные идентификаторы, но было бы интересно (наверное) иметь инструмент который скажет тебе "ты тут назвал метод setGadget а в нем ничего не сетится"

Венгерская нотация, встроенная в язык? Ну, это же $ и @ в Perl, но почему-то в массах его за это как раз не любят.

А? не знаком с перлом. если сделать $ и @, то перл сможет посмотреть в содержимое метода?

я про то чтобы компилятор говорил
-setValue: error, method claims to set value while member 'm_value' is unmodifieed
-connectToDatabase: error, you nikuda ne podcepilisya debil
-sdasslsdw: variable name is incomprehensible bullshit

Класс, интерфейс — неважно, проблема остаётся. Причём проблема элементарно решаемая средствами функционального программирования.

И как же именно ФП помогает бороться со связностью там, где ООП пасует?

Очень просто. Делается алгебраический тип данных и далее можно определять сколь угодно много точек с полиморфным поведением без всякой связи с объявлением самого типа.

Если вам нужна расширяемость именно по операциям, а не по подтипам - то и в ООП есть способы реализовать то же самое. Упрощённо, вам всего-то нужно сделать интерфейс для такой операции. Это требует больше кода, чем в ФП, но работает совершенно так же: можно объявлять операции без модификации типов.

Кстати, АТД в последнее время тоже в ООП языки "подвозят". Правда, обычно с тотальностью беда - но с ней и в классическом ФП такая же беда. Кстати, в чистой ООП-версии с тотальностью всё в порядке.

Если как в Rust можно объявить интерфейс и тут же его реализовать для нужных типов — то да, вполне эквивалентно получается. К сожалению в том же C# так нельзя и реализация всех интерфейсов должна быть одном модуле с объявлением класса, что несколько ограничивает.

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

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

Концепция ООП, заключается в использовании нескольких вычурных слов при описании приемов программирования. И это не считая слова "концепция".

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

А может у тебя не было опыта работы с НОРМАЛЬНЫМ ООП? Попробуй Kotlin. Там буквально всё является объектом. Испоганить то и его можно, но исходя из моего опыта - это самый приятный и понятный язык.

Там буквально всё является объектом

Даже null?

Вообще, много где всё "объект", хотя определение объекта достаточно неоднозначное, как мне кажется. Вот в расте у всего можно вызвать какой-то метод. Там тоже всё объекты? Получается, он тоже мощный ООП язык?

Да, Rust - довольно мощный ООП язык. Будь в нём нормальное наследование - он был бы, наверное, самым мощным из ООП языков.

Я бы сказал, что проблема в определении ООП, которое обычно включает в себя православие, самодержавие, народность инкапсуляцию, полиморфизм, наследование.

Наследование реализаций уже и в ООП кругах является дискуссионной опцией, Я бы сказал что она расту не нужна.
В то же время все остальное вполне позволяет использовать раст как очень мощный язык с поддержкой ООП.

Нужна она, нужна. Без наследования или полноценной замены на Rust никогда не появится, к примеру, адекватной UI библиотеки.

Я пробовал — уже сейчас наследование можно довольно неплохо эмулировать сочетанием portrait и моего dynamic-cast. Чуть-чуть многословно и есть трудности с изменением иерархии (так как каждый наследник должен перечислять всех предков при объявлении), но жить можно.

Тем временем:

  • egui - a simple, fast, and highly portable immediate mode GUI library

  • slint - a declarative GUI toolkit to build native user interfaces for desktop and embedded applications written in Rust, C++, or JavaScript

  • iced - a cross-platform GUI library for Rust focused on simplicity and type-safety. Inspired by Elm.

  • dioxus - a Rust library for building apps that run on desktop, web, mobile, and more

  • и т.д.

Чем, например, эти библиотеки не угодили? Или к чему надо стремиться? Да и в наше время больше распространены веб-приложения, при этом почти все так же умеют компилироваться в WASM и успешно запускаются в браузере.

А для фанатов frontend разработки (где, так же, обходятся без наследования) есть даже свой аналог React SolidJS, leptos, с поддержкой гидрации, ssr, csr, server functions.

Конечно, некоторые из этих крейтов сыроваты, но начало положено, и да, это сделано без наследования.

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

Спасибо, а можно ненадо?
Вот в реакте/ангуляре я прекрасно живу без наследования и делаю интерфейсы. А вот где оно появляется, начинаются проблемы. Порой в дебаггере докопаться где какой метод вызвался в цепочке наследования очень сложно, особенно когда один ребёнок переопределяет родительский метод.

Реакты-ангуляры построены поверх DOM, где наследование как раз используется.

Ожидаемый ответ. Но ангуляры и реакты скрывают от нас браузерный dom, и могут рендерится вообще без него.

Скрывают - да, но он всё ещё остаётся частью реализации UI.

и могут рендерится вообще без него

Рендер без DOM даёт лишь текстовый файл для передачи в браузер, а не UI для пользователя. И чтобы получить этот UI, вам, скорее всего, всё равно понадобится клиентский код, работающий с DOM.

Так наследование и иерархия компонентов не обязана быть построена на наследовании самих кодовых единиц. Тут хватит трейтов.
Самое главное - иерархия интерфейсных элементов.
В расте это может быть представлено как дерево нод (нода - это один фиксированный тип), а уж внутри нод можно использовать компоненты.
Тогда будет и иерархия объектов (а она нужна конечно) и наследование функциональности, но через включение (оно тоже нужно для переиспользования)
Просто это будет работать не через иерархии наследований.

и наследование функциональности, но через включение (оно тоже нужно для переиспользования)

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

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

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

Мне кажется, если не навязано корпоративными стандартами проекта, каждый находит в ООП что-то своё и это использует. Это как с английским, кто-то просто что-то мычит, кто-то только пишет\читает, кто-то получил CAE. Плохо чтоль? Хорошо!

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

Удручает, что такое пишут еще люди с опытом

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

Что значит "большую часть проектов состовляют процессы" ? Проекты из исходных файлов состоят всегда.

Фраза в духе "Здания из бетона состоят всегда")
Речь о том, что у проектов разного назначения разная архитектура.

К примеру, веб сервисы - это зачастую роутер адресов, различные обработчики по этим адресам и БД. Большая часть обработчиков не имеет состояния (за их состояние отвечает БД), соответственно для такой архитектуры более востребована организация кода, подходящая для процессов - чистые функции, асинхронность, DCI и прочее.

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

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

и в карму лихо поднасрали конечно же, не имея ни одного аргумента :) вот такое оно ваше, айти :)

Так именно потому, что опыт имеют, и не следуют слепо ООП-религии.

UFO just landed and posted this here

Код, написанный с использованием ООП медленнее, чем ФП

Тут очень спорно. Зависит от задач и конкретного языка. Но в большинстве случаев именно функциональный подход медленнее, чем объектно-ориентированный и тем более структурный. Так как императивное программирование наиболее близко к тому, как код выполняется процессором. Функциональное программирование хорошо именно тем, что оно самое правильное с математической точки зрения и такой код очень удобно распараллеливать.

Так как императивное программирование наиболее близко к тому, как код выполняется процессором. 

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

Например, изначально в Си операции ++ и -- были реализованы по той причине, что они эффективно исполнялись на машине PDP. Но на x86 никакой пользы от таких императивных операций нет.

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

Считаю ООП сектантсвом. Не встречал задачи, которая бы решалась без этого. Возможно есть от этого какая-то практическая польза, когда с тобой над проектом работают много людей, но я никогда это не любил.

Вот, посмотрите например на эту улитку на glsl, в котором нет ООП: https://www.shadertoy.com/view/ld3Gz2

Особенно бесяче всякие решения типа PDO - у меня сложилось чувство, что это используется теми, кто не осилил SQL, или потому что просто модно. Я не нашел решения для многих сложных SQL, которые мне приходилось писать.

* для многих сложных SQL-запросов имеется ввиду

решения типа PDO - у меня сложилось чувство, что это используется теми, кто не осилил SQL

Похоже вы не понимаете, о чем говорите, PDO это и есть средство для отправки SQL-запросов в базу из PHP.

Кстати сам фреймворк (2 и 3, по крайней мере) тоже говно. То ли дело Express JS

$query = Product::find()->alias('p');
$query->joinWith('category c');

if ($filter->managerEmail) {
  $query->joinWith('manager m');
  $query->andWhere(['m.email' => $filter->managerEmail]);
}

$subQuery = ProductImage::find()->where('product_id = p.id');
if ($filter->hasImages === true) {
  $query->andWhere(['exists', $subQuery]);
} elseif ($filter->hasImages === false) {
  $query->andWhere(['not exists', $subQuery]);
}

return new ActiveDataProvider([
  'query' => $query,
  'sort' => ['attributes' =>
    ['p.created_at', 'p.name', 'c.name', 'm.email']
  ],
]);

Выводить надо все параметры Product, название категории, email менеджера. Этот код это обеспечивает. Теперь покажите пожалуйста, как это будет выглядеть с SQL.

Потом покажите, что надо поменять, если в Product добавится поле, которое надо выводить, но по которому нет фильтра. В этом коде ничего не поменяется.

Потом подумайте, как с SQL сделать сортировку по произвольному полю из списка разрешенных при клике на заголовок таблицы на веб-странице.

В SQL это будет выглядеть чуть короче:

$sql = "SELECT * FROM product p JOIN category c ON (p.category_id = c.id) JOIN manager m ON (p.manager_id = m.id)";

if ($filter->managerEmail) {
	$where[] = "m.email = " . db_esc($filter->managerEmail);
}

if (is_bool($filter->hasImages)) {
	$where[] = ($filter->hasImages ? "" : "NOT ") . "EXISTS (SELECT * FROM image WHERE product_id = p.id)";
}

if ($where) {
	$sql .= " WHERE " . implode(" AND ", $where);
}

$sql .= "ORDER BY p.created_at, p.name, c.name, m.email DESC";

return db_query($sql);

Короче? Возможно, аж на пару строчек. Зато писать сложнее, читать сложнее, и всегда есть возможность забыть об экранировании.

А чего, bind-параметры в этот ихний PHP до сих пор не завезли?

Завезли, но комментатор выше предпочёл "чистый" SQL без них...

У меня джойн делается только в if, а у вас всегда. С другой стороны, у меня это противоречит требованию "выводить email менеджера", тут я ошибся немного, но такие задачи бывают, когда надо фильтровать по связанной таблице, но не выводить.
Но допустим это мелочи, можно добавлять в запрос все возможные джойны и полагаться на то, что база сама оптимизирует ненужные.

Чуть короче оно выглядит только потому что вы заменили if для hasImages, я тоже так могу сделать, и из-за сортировки. До сортировки мой код будет короче.

Требование произвольной сортировки вы не реализовали. Надо не "всегда сортировать по этим полям", а "разрешить сортировать по любому из них" при клике пользователя на заголовок в таблице. Текущее поле приходит в GET-параметре.

Также нет пагинации, а у меня есть. ActiveDataProvider автоматически обрабатывает сортировку и пагинацию, и не надо это копипастить в каждом списке. Чтобы выделить это в абстракцию, нужно использовать query builder, который хранит в себе дерево запроса. Иначе придется делать парсинг SQL, который дает то же дерево.

PDO как раз для тех кто именно SQL и осилил, оно же буквально для переносимого между СУБД исполнения SQL запросов сделано.

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

Вот, посмотрите например на эту операционную систему, в котором нет языков высокого уровня:
https://kolibrios.org/ru/screen.htm

Если вы думаете что пролили свет и ответили на вопрос что такое ООП простым и доступным языком, то вы сильно ошибаетесь. Вы ещё больше запутали новичков и добили мотивацию своими сравнениями и сущностями. Вообще, если вас тянет к преподаванию, нести "разумное, доброе, вечное", постарайтесь забыть что вы программист и постарайтесь выражать свои мысли просто и доступно даже ребёнку. Это статья не о чём, по крайней мере для большинства читателей, а тот кто разбирается вряд ли будет читать такой огромный доклад. Скажите , что это? Крик души или какие-то комплексы? Тяжело в социуме или другие проблемы? Лично мне непонятно.

Скажите , что это?

Перевод первой попавшейся под руку статьи ради рейтинга корпоративного блока. Вы по-сути с копипастой сейчас говорите

Если вы думаете что пролили свет и ответили на вопрос что такое ООП простым и доступным языком

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

Статья не для самообразования. Это беллетристика, что-то вроде эссе.

Почитал уже кучу статей про ООП, но так и не смог найти тот, где просто на примерах сравнивают ООП с императивным подходом. Типа "у нас начинает усложняться код, но вот как его даёт упростить ООП".

ООП упрощает не код, а его использование. Вот, например, типичный си, прямо с официального сайта:

fftw_complex *in, *out;
fftw_plan p;
...
in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * N);
p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
...
fftw_execute(p); /* repeat as needed */
...
fftw_destroy_plan(p);
fftw_free(in);
fftw_free(out);

А вот то же самое в парадигме ООП:

auto fft = new Fourier::Complex(2048);
auto in = fft.AllocBuffer();
auto out = fft.AllocBuffer();
...
fft.Transform(in, out);
...
delete fft; // выделенные буфера удалит сам

Лично мне второй вариант нравится в разы больше.

fftw_complex *in = fftw_complex_alloc(N);
fftw_complex *out = fftw_complex_alloc(N);

fftw_transform(in,out);

fftw_complex_free(in);
fftw_complex_free(out);

Для того чтобы завернуть что-то в функцию никакого ООП не нужно.

Сахар, безусловно, упрощает код - но ООП это всё-таки парадигма программирования, а не просто сахар.

В том же ядре Linux ООП используется вовсю, но код куда более похож на ваш первый пример, нежели на второй.

Ну и за автоматическое удаление буферов, на которые остались ссылки - огромное вам "фи". Нормальный пример выглядел бы как-то так:

// 1. Complex тут совершенно точно часть типа, возможно это даже шаблон.
// 2. В сложных вычислениях буферы совершенно точно потребуется создавать отдельно,
// а значит сокращённый способ для создания буферов должен быть функцией
// или статическим методом
auto fft = FourierComplex::CreateWithBuffers(2048);
auto in = fft.in;
// ... заполняем входной буфер

// Раз буферы созданы автоматически - то fft о них уже знает,
// и нет смысла передавать их снова.
fft.transform(); 

auto out = fft.out;
// ... получаем результаты

// Все объекты удаляются здесь, потому что умные указатели

Или даже вот так:

// Нам вообще не нужен объект для преобразования Фурье
// Если объект создаёт буферы, значит это фабрика
auto factory = ComplexBufferFactory(2048);

auto in = factory.AllocBuffer();
// ... заполняем входной буфер

auto out = factory.AllocBuffer();
fourierTransform(in, out, FFTW_FORWARD, FFTW_ESTIMATE);
// ... получаем результаты

// Все объекты удаляются здесь, потому что умные указатели

Смысл выделять FFT в отдельный объект был в том, что предвычисленные таблицы синусов/косинусов пересекаются для разных размеров и это помогает экономить память. Смысл создавать буфера через объект FFT был в том, что а) размер уже известен и б) в них можно хранить дополнительное состояние домена - временной или частотный. Полезно для свёртки или преобразования в другие домены - Хартли или косинусное например. Буферов также может больше, чем два, их можно складывать, умножать и всё такое. Complex выделен в часть типа, потому что часть кода пересекается с другими типами.

Насчёт "фи" - справедливо в принципе, но в шарпе сборка мусора автоматическая например. Хотя там точно также отслеживаю количество созданных объектов в деструкторе, а умные указатели в плюсах появились не так уж и давно.

Такого не может быть, потому что в мейнстримном ООП внутри методов - всё та же императивщина.

После каждой статьи про ООП хочется узнать — а что же объектом, все таки, не является? :)

Большинству и не нужен ООП. Они ещё программировать толком не научились, а им суют ООП. А в итоге получаем что человек и программирования не знает и ООП не знает, а создаёт программы.

Сколько не читай про ООП, все равно ясной картины как это использовать нет. Мой собственный опыт - есть три способа использовать ООП, два из них эффективны, а третий не представляю чем полезен:

Первый повод применения ООП

Есть функция, внутри которой вызывается еще несколько функций, внутри которых еще несколько. В во внутренние функции приходится передавать много входных параметров. Вместо этой конструкции создаем объект с одним публичным методом и множеством приватных. Приватные методы получают доступ к данным объекта и теперь не нужно передавать так много параметров. Кроме того приватные методы прячутся в классе, упрощая понимание.

Наконец, интерфейсы, IoC контейнеры и фабрики позволяют очень удобно комбинировать подобные объекты-функции для юнит-тестов или разных сценариев.

Второй повод применения ООП

Для описания объектов, существующих полностью в виртуальном мире (не моделирующих реальные объекты). Лучше всего это работает в фреймворках UI - кнопки, панельки, элементы. Еще это некие классы-сервисы для обработки чего-то.

Здесь в отличии от предыдущего случая у функции больше одного публичного метода, все они работают с одними данными.

Третий способ (который так и не понял)

DDD - объекты как модели неких сущностей реального мира. Если это "записи" без поведения, вопросов нет. Если данные с поведением, не представляю как это сделать не усложнив все неимоверно. Если кто-то умеет, можете описать.

Первый повод применения ООП

Это уже модульная архитектура. Функционал сгруппирован по отдельным модулям. Не нужен никакой "один объект" какого-то там класса.

Второй повод применения ООП

Да, я бы чуть упростил формулировку до "при написании UI интерфейсов". Вот там все по полной программе раскрывается.

Третий способ (который так и не понял)

... Если это "записи" без поведения, вопросов нет.

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

Если данные с поведением, не представляю как это сделать не усложнив все неимоверно. Если кто-то умеет, можете описать.

А часто -- никак, просто потому, что это не имеет никакого смысла. :) Начинаются фантазии на тему -- это комната добавляет в себя собаку, или собака добавляется сама в комнату. И потом никогда не разберешься "кто нам ком стоял", и почему хвост виляет собакой.

Это уже модульная архитектура. Функционал сгруппирован по отдельным модулям. Не нужен никакой "один объект" какого-то там класса.

Вроде бы и да, можно называть это "модулями" вместо объеков. Но именно такой способ введения в ООП я вроде видел у Uncle Bob.

Программисты никогда не понимали ООП, программисты не понимают ООП. И программисты не будут понимать ООП. Конечно, если программист в своей жизни ничего, кроме клавиатуры и дисплея не видел. Я считаю, что программист, будучи собирающимся стать архитектором программных систем, должен для начала изучить на практическом уровне:

  1. Архитектурное проектирование и строительство.

  2. Электронику, компонентную базу, датчики, ТАУ, ТОЭ итд

  3. Механические устройства

  4. Физику: термодинамику и устройство тепловых машин, ДВС и другие типы двигателей

  5. Биологию на клеточном уровне - строение клетки, белки, синтез ДНК, РНК,

  6. Анатомию на уровне организма, например, анатомию человека (для наглядности)

  7. Экономику, хотя бы базовые понятия

  8. Еще много всего, чего я сам не знаю, но всем советую знать.

Короче, реальный мир, как он устроен. Не глядя в экран, а прикасаясь к этому миру. Тогда в голове вырисовывается его картина, а объекты и их взаимодействия становятся очевидными, и тогда абсолютно без разницы становится, на каком языке и с какими возможностями разрабатывать программы. Ведь ООП - оно не в коде или языке, оно - в голове программиста. Если там никогда не было ничего, кроме кода - то там абстрактная жижа из шаблонов, дженериков, чего-то сложного и синтаксически-сахарного.

должен для начала изучить на практическом уровне

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

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

Расскажите, как пункты 3-7 (и особенно 8) уровня дальше общеобзорного помогают в архитектуре.

«Узок круг этих революционеров. Страшно далеки они от народа...» :-)

Есть вещи, которые без ООП реализовать весьма трудно (но можно). Простой пример - генератор объекта из xsd. Элемент - объект, атрибуты - поля, "unbounded" - TObectList<TElementByName> . Мне приходится работать с этим - ФНС и СФР регулярно выдают новые версии деклараций. Альбом схем СФР кто-нибудь просматривал? Дописываем методы - например, чтение файла парсером. Кстати - Delphi XE3. Добавлю - чтение xlsx, тоже объект.

Как известно, XSLT создан Чужими для Хищников, поэтому его в пример ставить - ну такое.

На самом деле, у этого термина есть два действующих определения. Первое относится к объединению данных с поведением (= метафора объекта). Второе относится к ограничению доступа к состоянию только самим объектом. Я бы хотел подробнее остановиться на втором, потому что, как мне кажется, многие не понимают его полностью.

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

Что значит исчезнет? Это концепция реализуется кучей разных способов, не может исчезнуть из мира концепция войны или смерти мы не в мире Человека-бензопилы

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

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

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

У "правильного" ООП высокий порог входа, это его проблема. Многие, вместо того, чтобы продолжать разбираться, как делать нужно, а как делать - вредно, просто заключают, что ООП - говно, и бросают. Но большая часть кода в мире написана с использованием ООП, и это не просто так, и не потому, что некие невидимые злодеи всех обманули и задурили голову.

ООП не за что хейтить. Задумка хорошая реализация (по большей части) ни какая.

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

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

Анекдот советский вспомнился, "ну ведь работаю же".

"Средний размер" - это сколько? Вот у нас на работе биллинг опсоса, порядка 10 миллионов строк чистого Си (и миллиона на COBOL), под гигабайт исходников, более 300 человек - ничего, работают. Гораздо больше проблем доставляет отсутствие CI/CD и Git (ну вот заказчик-жмот, только сейчас на это расшевелился), нежели ООП.

Да, микроскопами можно забивать гвозди. Можно эту систему и на ассемблере написать было, правда? И тогда сказать, что эта ваша процедурная парадигма - ненужное усложнение :)

Вопрос в том, сколько времени займёт написание, и сколько потом времени будет занимать поддержка и развитие. Те, кто принимал решение писать эту систему на Си, не учитывали интересов бизнеса и руководствовались какими-то другими критериями, а не критериями эффективности разработки и поддержки.

Либо такая система образовалась исторически. Например, сначала было много Кобола, и ничего больше не было. Потом стали переписывать (и дописывать) критические участки на С, чтобы быстрее работало. Спустя N лет получаем такого монстра. Но нельзя же на этом основании серьезно говорить, что С - это хороший/оправданный инструмент для систем подобного рода?

Тщательно изучал ООП на заре его популярности - в 90-х.

Реальность показала, что в практическом программировании (не "академическом", а на результат) ООП, фактически, не используется.

На каком языке вы пишете?

Писал на многих, начиная с Turbo Pascal 5.5.

Последние 12-15 лет в основном связка ASP.NET+C#+MSSQL, хотя сейчас приходится переходить на питона с постгресом.

И как вы пишете на C# без ООП? O_O

То, что там есть class, еще не значит, что используется ООП.

Предпочитаю строго изолированные библиотеки с вызовом функций из них, а не наследование и расширение объектов.

Выше вам уже ответили https://habr.com/ru/companies/ruvds/articles/792052/#comment_26476944

Вы как господин Журден из комедии Мольера "Мещанин во дворянстве", который сорок лет не знал, что говорит прозой. Строго говоря, совсем без ООП эффективно использовать С# вряд ли выйдет, потому что вся сила С# в большом количестве готовых классов на все случаи жизни, которыми мы обычно радостно пользуемся

Ну что ж, спасибо предыдущим поколениям, что мне не приходится писать на ассемблере :)

Внутри ASP.NET так-то ООП используется в куче мест, а если взять его Core-версию, то там ООП ещё больше.

Так что даже если вы ООП не используете - в вашей программе оно всё равно используется.

Реальность показала - извините, а в какой реальности Вы находитесь, круг задач, которые Вы решаете?

Можете мне – человеку, непонимающему программирование – в рамках любознательности объснить одну вещь. Вот у нас единый процессор. И физическая память одна. Есть разные языки написания программ, но в чём же принципиальная разница между линейным кодом и ООП для адресного пространства ?

В понятности алгоритма

Сомнительно, чтобы адресное пространство обладало способностью понимать алгоритмы.

Для адресного пространства все равно, там код. Хотя отдавая дань справедливости замечу, введение классов ВСЕГДА увеличивает код.

Вы приведете еще пример х=1+1)))

Флаг в руки. Вы же утверждали, что ВСЕГДА увеличивается код. Как видно: не всегда.

Конечно же правда, причем как по измерениям еще мохнатых годов, в два раза - в первом листинге 17 строк, во втором 8. А что там в ассемблере, никого не волнует.

А зачем вообще мешать в одной куче парадигму программирования и какие-то адресные пространства процессора, если между ними 9000 слоев абстракции?

Вот у нас единый процессор. И физическая память одна. Есть разные языки написания программ, но в чём же принципиальная разница между линейным кодом и ООП для адресного пространства ?

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

Такой вопрос надо задавать создателям компиляторов. @xortatorкак раз пишет цикл статей.

Мне это прям напомнило срач времен моего детства про SQL-сервер на ассемблере и понятие Turing tarpit. А адресному пространству вообще пофиг, оно не живое.

В свое время, прочитав Г. Буча был воодушевлен ООД и соответственно ООП. Казалось что любой сложный проект можно осилить введением классов ,наследственностью ну и прочими прелестями ООП. Однако со временем становится понятным что хаос сложности , с которой борется ООП , приводит еще к хаосу семантики и к хаосу данных, и вместе все это трудно уживается. Безусловно, для сложных проектов необходим обьектно-ориентированный дизайн, но это не самоцель, и он вполне может остаться на уровне UML. К примеру, вверху в постах, один товарищ говорил об SQL запросах и его можно понять. Введение стандартной модели ООП и строгое следование во всем его правилам со временем сделает невозможным написание оптимизированных SQL запросов для больших выборок. Аналогичная картина и в сложных алгоритмах реального времени и тем более в параллельных схемах , прерываниях, или асинхронных задачах. Все мы пониманием для без классов и объектов не обойтись, но совать их всюду и везде это получить программу монстр, обречь ее на тяжеловесность, замедленность, получить прожорливость в ресурсах , памяти , процессорного времени. Особенно все становится "интересным" и принципиально не ООП-шным.при написании нейронной сети, с элементами самообучения , подстановкой соответствующей модели на этапах глубокого обучения (или даже вместо него) и меняющимися со временем функциональными проекторами на слоях сети.

Люди не понимают ООП, в том числе автор этой статьи

Так автор статьи в блоге сам оценивает это как "my ramblings somewhat". (Автор оригинала: Florian Buchberger)

Эта статья даже не объяснение что такое ООП, а размышления о том что люди, которые уже применяют приемы ООП, не всегда понимают их смысл.

А перевод качественный. Для кого-то это будет интересным.

ИМХО все это ООП это набор приемов и паттернов, где-то они помогают писать код лучше, а где-то нет. А тут очередная попытка придумать или оправдать какую-то глубинную философию, которая к разработке ПО не сильно нужна.

Как говорится code talks, bullshit walks

Ещё сложность ооп - разная реализация в разных яп. Изучая ооп в питон, а потом изучая ооп в джаве понимаешь, в питоне что-то совсем свое.

Например, вместо абстракций - сахарные конструкции. Полное отсутствие интерфейсов. Ну и своеобразное отношение к приватностям.

"вместо абстракций - сахарные конструкции" - а можно поподробнее?

Я про Abc модуль. Его нужно отдельно импортировать. А в крупных проектах практически не встречается.

Есть тот же протокол, но в нынешнем питоне, как и типизация, довольно условная штука

Например, вместо абстракций - сахарные конструкции

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

Полное отсутствие интерфейсов

Ну не совсем полное. Есть некоторое подобие - protocol

Ну и своеобразное отношение к приватностям.

Это да, но справедливости ради, в Java тоже можно вызвать приватный метод через reflection. Так что с точки зрения случайного использования private метода примерно одинаково.

ООП в питоне, действительно "свое". Множественное наследование с MRO только чего стоит )

Насколько я понимаю, в питоне ооп во многом для простого понимания разработчиков, переходящих из других яп.

Те же абстракции, и те же протоколы - крайне по желанию. Сколько видел кодов крупных проектов, ни разу не видел применение.

Но может для программиста, например, джавы будет логически понятна сама логика такого подхода.

После питона, изучая ооп джавы прям понимаешь разницу. Даже не в конкретных реализациях. А разницу в самой философии.

...и она в жабе убогая квадратно-гнездовая. Спасибо, не надо.

В Python есть структурная подтипизация, реализуемая с помощью весьма удобных протоколов. Добавьте, пожалуйста

Прочитал заголовок, сразу перешёл к чтению комментариев)

Мне кажется что 3/4 комментирующих тоже прочитало заголовок и сразу пошло в комментарии.

А что делать, если статьи с названием "<Subj> не может <Item>" в большинстве случаев или беллетристика, или нытьё?

Удивительное дело, автор! Вы явно упомянули прототипы (помимо классов) в JavaScript и TypeScript, но, умышленно или "по забывчивости", написали только классы для Java. Простите, но, если упоминаете прототипы, то не забудьте интерфейсы для Java. Интерфейс - это класс без реализации, ничем не хуже, чем прототипы. А если ещё вспомнить абстрактные классы в Java, то я бы и их добавил, как инструмент проектирования и вероятного использования ООП.

Вот как же так?

Интерфейс - вот вообще ни разу не прототип.

Прототипы - это совсем о другом, чем интерфейсы. Например, они позволяют в отличии от интерфейсов просто и прозрачно менять поведение всех объектов "класса". Что интерфейсы не могут и им на помощь спешат всякие трейты и/или чудовища "реализаций по-умолчанию".

То что в JavaScript иногда не хватает нормальных интерфейсов это понятно - но не надо сюда приплетать Яву!

Lua со своим извращением упомянут, а Perl, автор которого сказал про Lua "я думал, это у меня ООП самое не такое", нет.

Когда уже адепты секты свидетелей ООП научатся осторожности? Нельзя так сразу набрасываться на клиента со словами - "ГЕТТЕР!, СЭТТЕР!". Это очень резкие слова, они пугают клиента. Он теряет внимание, и даже может попытаться сбежать. Надо постепенно приманивать разговором о традиционных приемах программирования, и только потом мягко и вкрадчиво предлагать - "а почему бы не использовать get и set? в этом нет ничего плохого, и система это поддерживает (резервирует ключевые слова)."

Да клиент ушлый пошёл. На всякие вкрадчивые предложения сразу расчехляет бритву Оккама-Нафигама. А сразу наскочишь - и всё , шок! Про бритву и не вспоминает! Экстремальный маркетинг и всё такое.

Всегда удивляюсь, почему практически любое (читай, не академическое) обсуждение ООП всегда (и очень быстро) скатывается к обсуждению каких-либо технических деталей?

Концептуально, ООП это же очень просто: сокрытие состояния. Как говорится, какое из этих двух слов вам не понятно? :-)

Остальное всё (вот буквально - всё) - это средства, которыми это самое сокрытие достигается в том, или ином случае. Ну либо "следствия" их использования. Всё, от какого-нибудь message passing до, какого-нибудь subtyping - это всего лишь "инструменты", т.е. суть - вообще не важны для понимания.

Да даже само понятие "объект" - вторично. Внезапно, ООП замечательно обходится из без "объектов" - не говоря уже про, какие-нибудь "классы"... да и "типы" вообще :-)

В этом смысле, показательно что ООП "ломается" как раз тогда, когда нет состояния. Нечего скрывать, не с чем становится работать - и тут же "рождаются драконы" разной степени монструозности и упоротости.

Резюмируя... ООП - это не про "классы" и т.п. Когда мы говорим об ООП, то - по сути - мы говорим лишь от том, что "открытое" состояние - не допустимо. В отличие от какого-нибудь СП, которое вполне себе это допускает. Остальное уже частности, особенности платформ, конкретных языковых средств и т.п., которые - если убрать сокрытие состояния - тут же теряют всякую ценность.

Из этого же, например, следует, что "противопоставление" ООП и ФП - это немножко "такое" (tm). По отношению к наличию "открытого" состояния - они категоричны.

Да и ФП, которое, одновременно, ООП - это "вообще норм" (tm). В том смысле, что если есть (ну вот надо нам) состояние, то мы его обязательно скрываем. Например, в "процессе" - как в Erlang, или, например, в специальном "типе" - как в Haskell.

Открытость/закрытость - это слишком общие понятия.

Если говорить проще (не употребляя запретное слово на "ка..."), то любая программа - эта куча "элементарных данных" и алгоритмов. Но куча не простая, в структурированная - данные связанны "смысловыми стрелочками" друг с другом и алгоритмами и алгоритмы друг с другом. Куча, море стрелочек!

И общая задача упорядочивания сводится к сокращению стрелочек и упрощению их структуры. Максимальному "снижению энтропии".

И ООП рекомендует все эти данные, алгоритмы и стрелочки группировать в кластеры - которые сильно связанны внутри, но слабо и очень желательно - только алгоритмически снаружи.
Из этого подхода, например, сразу же мгновенно возникает вопрос об последующем иерархическом разбиении кластеров на "подкластеры" - иначе не удастся упорядочить стрелочки.

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

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

Открытость/закрытость - это слишком общие понятия.

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

Но куча не простая, в структурированная - данные связанны "смысловыми стрелочками" друг с другом и алгоритмами и алгоритмы друг с другом. Куча, море стрелочек!

"структурирование данных" и "куча стрелочек" (DF/CF и прочие F) - это СП. Оно очень даже допускает "открытое состояние". Более того - по большому счету - оно всё и построено вокруг "не локальности изменений" глобального состояния. Без этого оно просто "ниработаит" (tm).

И общая задача упорядочивания сводится к сокращению стрелочек и упрощению их структуры.

?!

Так что все принципы ООП важны.

Нет там никаких других "принципов". Точнее - они не имманентны "сокрытию состояния".

Ибо по отдельности они не достигают поставленной цели.

Цель - одна: скрыть состояние - т.е. "локализовать его изменения". Это сильно всё упрощает само по себе, знаете ли.

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

Извините, но как связаны "куча дублирующегося кода" и "наследование"? Точнее, как наличие второго исключает наличие первого? Или - по вашему - "наследование" - это про переиспользование чтоль?!

А наследование без полиморфизма - это вообще пытка над программистами из серии "протащи вызов через десять слоёв абстракции" (правда молодые программисты судя по моим наблюдениям её почему-то любят)

?! Наследование, это же один из способов получить тот самый полиморфизм. Способ. Один из. Т.е. вполне может быть полиморфизм без какого-либо наследования. Но вот "наследование без полиморфизма" я себе представить не могу. И мне вот сильно интересно, где вы видели это самое "наследование без полиморфизма"?

Точнее классы - это не ООП. Это вообще перпендикулярно. Но они очень сильно помогают дальнейшей метаструткуризации в ООП ...

Скажу так... есть гораздо более удобные способы для, чем subclassing.

Огласите запретное слово!

:-) Прям сразу "с козырей". Можно же было как-то "тихонечко"... через композицию там... подвести к теории типов... сумма типов, произведение... то сё... показать, что subclassing это такой "ugly kid Joe", в смысле суммы типов... ну и потому же - да - "шарахать" :-)

А что это за слово на ка? Вы про теорию категорий?

Натягивание совы на глобус. Сокрытие в ООП ни разу не является ключевым. Изначально оно про message passing, и никто не запрещает ввести сообщения, которые о состоянии расскажут. Или вот Perl/Python - все поля объекта общедоступны, и ничего, это не перестает быть ООП.

А если брать уж действительно глобально, то все парадигмы предназначены ровно для одной первичной цели - переиспользование кода (DRY). Достигают они этого разными способами и ставят вторичные цели, что со временем может привести к проблемам с достижением первой (о ней забыли), но изначальные причины появления, всегда - уменьшить количество работы программиста.

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

Сокрытие в ООП ни разу не является ключевым. Изначально оно про message passing, и никто не запрещает ввести сообщения, которые о состоянии расскажут.

Причем тут message passing?! Оно - не "цель" (ООП), а лишь инструмент (один из). Да и смысл "сокрытия" совсем не в том, что состояние не видно "из вне". А в том, что его (состояние) нельзя изменить "из вне".

Или вот Perl/Python - все поля объекта общедоступны, и ничего, это не перестает быть ООП.

"Открытое" состояние (и даже просто его принципиальная возможность в) - есть основная часть критики "оопешности" таких (и не только этих) языков/систем. Это, на всякий случай... вдруг вы не в курсе.

А если брать уж действительно глобально, то все парадигмы предназначены ровно для одной первичной цели - переиспользование кода (DRY).

Как-то всё с ног на голову, имхо. Они - парадигмы т.е. - действительно про "одну цель". И DRY - если попытаться рассматривать этот принцип, как парадигму - тут не исключение. Цель эта - борьба со сложностью.

Если "маненько упростить", то ОПП - как парадигма - предлагает "сокрытие" состояния, как основной способ борьбы со сложностью (реализуемых систем). Т.е. вот прям буквально: если все изменения состояния S будут локализованы в S, то это не привнесет в систему (в которую S будет включено) сложность самой S. И в этом смысл. Речь - естественно - о "сложности системы" в инженерном смысле.

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

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

Я бы сказал - если "на пальцах" - причина: это мочь реализовывать/развивать более сложные системы, чем до этого. О "работе программиста" речь если и идет, то как о "третьей с конца по важности".

если все изменения состояния S будут локализованы в S, то это не привнесет в систему сложность самой S

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

для меня оправдание небходимости/полезности ООП состоит в обязательном покрытии кода юнит-тестами и лень писать однотипный код более двух раз

Можно еще и SICP вспомнить, там тоже было много хороших и правильных идей, примерно такой-же практической ценности.

А что, у Бугаенко были хорошие и правильные идеи? На мой взгляд у него были идеи как сделать код ужасным с точки зрения архитектуры.

Ужасным - надо понимать "написан не так, как у всех остальных в настоящее время принято"?

У Егора вполне актуальная компиляция как вполне здравых идей (к слову, вовсе не авторских, в массе придуманных десятки лет до него). К примеру недопустимость NULL ссылок/указателей на составные объекты. Как и откровенных утопических ахиней: недопустимость мутаций объектов как всеобщее правило, которое приехало из мира этих ваших монад (где безусловно очень умные парни так и не смогли в итоге справиться с задачей эффективной реализации автоматического параллелизма произвольного кода, там всеобщий и тотальный запрет мутаций как источников состояний гонок хоть вполне был обоснован).

Но без целостной платформы (как минимум SDK), изначально построенной и верифицированной на всех этих отличных идеях - это все лишь идеализм, как и успешно почивший в бозе SICP (и иже). Выражаясь метафорически: если взять бочку меда и бочку навоза и смешать, то на выходе получатся лишь две бочки навоза, и стоило ли переплачивать?

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

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

Когда это SICP успел помереть?

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

Даже открывать не хочется, ведь вместо видео всегда лучше статья текстом.

Пересказали бы что ли.

ООП не любят не за vtable, и не за множественно наследуемый полиморфизм b и прочее инкапсулирование. Реальная проблема лежит совсем в другой плоскости: GOTO

Изначально GOTO был объявлен вне закона по одной простой причине - программистам было сильно неудобно "прыгать" по страницам кода, пытаясь отследить и качественно протестировать логику потока управления. Т.е. в идеале все должно было быть последовательно-линейно, с небольшими вариациями ветвления в той самой типовой самой задаче преобразования данных из одного табличного вида в другой (которая составляет 99.9% всех современных проектов - от бухгалтерий до игродела и машинного обучения).

А что в ответ предложил ООП? Да ничего нового - лишь значительно более чудовищные формы GOTO, но лишь назвав их "по новому, по-заграничному":

  • Exceptions - особо извращенная форма GOTO (суть setjmp/longjmp), которая обычно, эдак в 99% случаев - даже не покрывается автотестами. Не прошло и 40 лет как это архитектурное фиаско тихо признали в Rust и Go, и на том спасибо

  • Наследование и полиморфизм, когда вместо типовых 50-60 строк кода в одной процедуре тебе при чтении чужого проекта нужно бегать по 30-ти методам размером в 2..5 строк кода каждый, разбросанных по 29 файлам (и это было названо образцом для подражания), уже на 7 хопе в IDE напрочь забывая окуда и зачем ты сюда вообще попал.

Список фиаско ООП идей можно продолжить (пройтись и по возведенным в абсолют макросам/копипасте кода по имени С++ templates, и по free/reuse memory-models vs obstack regions, и по CPU cache friendly Entity Component System, и даже по провалу UML в т.ч.), но "хитроумная" маскировка GOTO уже превращает весь этот фарс в комедию.

goto вообще никакого отношения к этой теме не имеет.
Можно на одних goto написать ООП код. Просто неудобно будет.

Я выше указал - что принципиального отличия между GOTO и расбрасыванием кода по десяткам методов в десятках файлов нет. Какая разница, как назван переход "куда-то там", вне зоны прямой видимости текущего файла с кодом? Да хоть функтором обзови, хоть лямда промисом, суть не меняется - вместо того, чтоб просто последовательно читать код как статью-книгу, в типовом ООП проекте ты вынужден постоянно прыгать по файлам-страницам в википедия стиле, пытаясь удержать в голове контекст и путь-иерархию вызовов.

Почему подобные прыжки туда-сюда-вверх-вниз в случае GOTO это очень плохо, а в случае ООП - это прям очень хорошо? Суть и там и там одна - логика разбросана хаотическим образом, при том что никакого code-reuse обычно не происходит - в подавляющем большинстве случаев тот или иной метод имеет как правило одного, ну очень редко два-три места вызова (если речь не идет про SDK или utility классы).

GOTO тут вообще ни при чем, термин сформулирован неверно и вводит в заблуждение, как видно по соседним комментариям. Назвать это следовало бы скорее слишком большим количеством передач управления при размазывании ответственности, а не отсылаться по сути к статье Дейкстры - такую лапшу можно и с предложенными им break/next слепить так-то.

тебе при чтении чужого проекта нужно бегать по 30-ти методам размером в
2..5 строк кода каждый, разбросанных по 29 файлам (и это было названо
образцом для подражания), уже на 7 хопе в IDE напрочь забывая окуда и
зачем ты сюда вообще попал.

Мне для этого в vim приходилось открывать табы и сплиты, рекорд был - под девяносто окон в двухнедельной таске.

 Первое, что приходит в голову, когда слышишь эту трёхбуквенную аббревиатуру

Не у всех) Возможно, кто-то даже вспомнит/найдёт весь этот стих

Эх, достать-бы где РПГ,
Да принять для куражу ЛСД...
Я разнес бы все к таким ЕБН,
Что позавидует сама ООП.

И вроде трёхбуквенных аббревиатур всего-то 33³, но совпадения встречаются часто) У меня когда-то была подруга по имени Ира. Характер чётко совпадал с IRA)

Ну это я так, для разрядки холивара между поклонниками разных ЯП

То-есть вот так безапелляционно - люди не понимают. А собеседования стало быть для выявления сверхчеловеков? И как давно они профсоюз себе сковали? Эти сверхчеловеки. Как насчёт ответственности? Или снова всё просто совпало? Неудачное название публикации. :(

В моей группе в ВУЗе, 2000е годы, ИТ специальность, все сплошь золотомедалисты в прошлом - примерно 95% популяции студентов в принципе не смогли понять и использовать адресную арифметику С/С++. Даже на уровне лабораторных и курсовых работ. А ассемблер не смогли освоить 99% (вообще никто на потоке).

Популярность Javascript/Python и прочих подобных языков, где ссылочная модель памяти выломана на старте лишь подтверждает тезис, что в мире существуют области знаний, не доступные массам. Это как игра на гитаре или рисование. Или есть некие врожденные способности, или их нет, и никакой тренировкой их не достичь. ООП, как и многое другое - тут не исключение.

Или есть некие врожденные способности, или их нет, и никакой тренировкой их не достичь.

Как это вы сразу перескочили на много лет? Разве школа и детство не имеют значения, а все способности записаны в генах?

Для ряда вещей, школа и детство только позволят такие способности раскрыть, но не "инсталлировать" с нуля. Каких именно это касается - вопрос дискуссионный, но в нашем случае всё начинается не раньше вуза, так что неважно, т.к. профессор Савельев говорит, что мозг сформирован примерно к 18 годам, для цели определения способной на томографе микронного разрешения (таких аппаратов пока не существует).

А до вуза мозги всех детей развиваются одинаково?

Вы имеете в виду профессора по церебральному сортингу? Я бы постеснялся на него ссылаться.

Моя сильно подозревать, что неосвоение адресной арифметики и ассемблера никак не происходит от какого-то "непонимания", а это заклин из-за боязни выйти из уютного мирка, защищённого от проблем с памятью. Кто преодолевает легко этот заклин - идёт дальше, пусть и с разумной осторожностью.

Именно поэтому "игрушки" системы "ну зависло - передёрнул по питанию" тут очень удобны.

И почему в PC перестали делать ROM Basic? Надо вернуть...

Как видим, мейнстримное ООП как раз скорее оказывается более доступным массам, чем альтернативные подходы или даже изначальное ООП.

UFO just landed and posted this here

Ну вообще это ж не ООП впрямую. Это асинхронщина - колбэки, ивенты... а внутри них можно и процедурно, и ООП, и функционально...

​1. Объект это нечто, про что нам удобно думать, что у него есть граница, отделяющая от остального мира состояния программы, и есть более-менее чёткое определение, как он взаимодействует с остальным состоянием - и мы думаем и описываем это в коде так, что есть этот объект и есть взаимодействие с ним.

Это первично перед всем остальным, включая то, какие виды взаимодействия, явно ли присутствует слово object или class, и прочая и прочая и прочая.

​2. Если у объекта есть собственный источник движения, это стиль Кея, Simula, Smalltalk и прочих. Это стиль "рыб в аквариуме", которые плавают, пускают пузырьки воздуха и перебрасываются данными в них. Сюда же следует включить 100500 агент-ориентированных сред, как, например, Erlang, где его внутренний процесс можно приравнять к объекту.

Если нет, а источник движения - внешний, как набор нитей (тредов) процессов, то это ООП стиля C++, Java, C#, VB, Pascal/Modula/Delphi/etc., Python, Ruby, тысячи их. Вот там вызываются методы, а не посылаются сообщения.

Это основное разграничение, по сравнению с которым остальные... ну не становятся совсем деталями, но менее заметны.

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

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

Но защита инкапсуляции всеми private/protected/аналогами - вопрос не принципа, но уровня защиты от диверсий другими участниками в большом проекте, где невозможно следить за всем, что делает кодер за соседним столом. Поэтому "инкапсуляция" тут неоднозначна, роль защиты растёт со сложностью задач.

​4. Полиморфизм наследования интерфейса - следующий критичный элемент, но он относится не к ООП самому по себе, а является неизбежным следствием необходимости контроля сложности. Посылка универсального сообщения "опиши себя текстом" в SmallTalk или вызов метода интерфейса в Java тут - перенос этой сложности с управляющего на управляемого. Разумеется, это не всегда в плюс (возможность вызвать в, к примеру, Python метод, которого нет, но который может быть добавлен или не добавлен в ходе жизни объекта назначением его "поля" - хорошо для всякой гибкости и плохо для отладки).

​5. Наследование реализации - такой же метод справиться со сложностью, но там, где это полезно за счёт того, что большая часть общего кода собирается вместе. Есть задачи, где это в плюс, и есть где можно легко заабьюзить в минус. Пока что видим, что плюсов считается больше - потому что административно можно запретить наследование реализации в конкретном месте, но нельзя сделать, если язык её не поддерживает. Но есть языки, в которых её убрали намеренно.

Всё прочее - скорее местные особенности, чем принципы ООП.

у вас не раскрыт пункт про Абстрактная чушь
Как ваш совет поможет тут? Люди уходят в эту абстрактность как раз для того, что бы было менее больно менять код.
И как "планировать только то, что точно понадобится" решит проблему если это и есть проблема?