Pull to refresh

Comments 124

зачем нужны классы в принципе, а именно в чём их преимущество перед функциями

Это сравнение кислого с мягким

Если в 2023 году нужно объяснять СОТРУДНИКУ (т.е. человеку, получающему деньги за написание кода), зачем нужны классы… то это какой-то очень странный сотрудник. Звучит примерно как «пришлось объяснять студенту, что 2+2=4».

Ну или, либо, куда только катится наша индустрия…

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

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

Не знаю насчёт классов, а вот что такое юнит-тесты нужно объяснять сотруднику в 2023?

Я как микроначальник, объяснял это коллегам вплоть до 2021-го — они их совсем не применяли (и не уверен, что хоть что-то автоматически тестируют сейчас). А до этого — системы контроля версий.

Зазубрить арифметику и понимать ее это немного разные вещи. Теорию чисел, абелевы группы и все вот это.

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

во-первых, php -- не лучший пример для демонстрации читаемости.

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

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

Не увидел в примерах пять поколений и перегрузки интерфейсами. И вообще не видел в реальности таких примеров, кроме специально надуманных fuzzBuzz-ов, которыми детей пугают.
Что не так с читаемостью? Думаю, важно что php тут не выбран, а является данным контекстом. + php гораздо больше ориентирован на ООП чем на функциональщину, вот и всё

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

Функциональное программирование - это совсем другое.

во-первых, php -- не лучший пример для демонстрации читаемости.

а что не так с читаемостью в php?

Мне вот тоже интересно, что же имел ввиду сей комментатор. Так и потянулась рука в карму, но стало интересно, действительно, что же не так с читаемостью в PHP?

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

я уже 15 лет управляю самолётом, понятия не имею, что тут может быть сложного

Шаблоны проектирования <...> Без классов(и ООП) они просто не получатся. Конечно, можно сделать их на функциях... Хотя нет, нельзя.

Берём классическую книгу по паттернам проектирования "Приемы объектно-ориентированного проектирования", и на 18 странице читаем

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

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

Другое дело, что сложно на pascal представить builder

Если речь про C++ Builder, то он возник, как перенос Delphi с Паскаля на C++.

А классическая Mac OS вся была написана на Object Pascal.

Речь про паттерн builder, а не среду разработки.

Так это ж просто паттерн. Хоть на ассемблере можно.

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

Да точно так же, руками VMT сделать в виде коллбеков. Хотя в паскале 30 лет как есть объекты.

В принципе, форт-машина - это и есть процедурная реализация паттерна билдер.

честно говоря, не понимаю о чем вы, даже гуглинг не нашел ничего по поводу форт-машины.

А Delphi все-же не Pascal, как c++ не c

Так объекты в Паскале появились задолго до Дельфи, ещё под DOS (в Borland Pascal или даже ещё раньше, когда он назывался Turbo Pascal). И фреймворки, использующие объекты вовсю, были (Turbo Vision, я когда после него мелкомягкий MFC увидел – впал в тягостное недоумение).

Тут все же есть нюансы.

С одной стороны, из "того самого" Паскаля образовалось много диалектов с классами, вроде даже местами не до конца совместимых между собой: Turbo Pascal, Object Pascal, Delphi, Free Pascal (детали могу путать, поскольку давно это было).

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

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

Объекты, кстати, появились в Турбо Паскале с версии 5.5.

С версии 6.0 в поставке появилась библиотека, использующая объектное расширение языка по полной - Turbo Vision.

Borland Pascal 7.0 включал в поставку Turbo Pascal 7.0 (урезанная версия, которая поставлась и отдельно).

мне всегда казалось что Delphi и Object Pascal тесно связаны

и гугл про это тоже говорит

Это другой object pascal. И вроде он у Борланда назывался не так, а Pascal with Objects.

Object Pascal - разработка Apple.

Все они похожи, так как происходят от UCSD Pascal. Но не одинаковы.

Delphi - это изначально всего лишь название IDE для языка Object Pascal, и лет через семь - новое маркетинговое название для всего языка в целом.

Причём борландовский Object Pascal, это хитрый Object Pascal, который включал в себя две объектные модели, нативную Object Pascal, и классическую из Turbo Pascal with Objects, для совместимости с существующим кодом.

ух-ты, не знал, благодарю за просвещение

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

Ну так и Windows когда-то на паскале писали — нам 25 лет назад в универе рассказывали про паскальное наследие в WinAPI.

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

Тип вызова функций windows api всегда был stdcall. Тип вызова pascal был добавлен в некоторых компиляторах c/c++ для савместимости с типом вызова в паскалевских программах, чтобы можно было использовать объектные модули, созданные из исходников на Паскале и с в одном проекте. В Паскале долго различные типы вызова не поддерживались, был свой, исключительный (возможно все же, что это верно не для всех компиляторов паскаля, и где-то использовался таки обычный stdcall).

Собственно типы вызова определяют не только кто стек вычищает, но и порядок аргументов функции в стеке, всякие выверты с передачей через регистры (типы register и fastcall, например). И тап pascal вроде отличался от stdcall именно порядком аргументов на стеке (но точно не помню, могу ошибаться).

Да, всё верно. Тип вызова pascal отличается от stdcall противоположным порядком проталкивания аргументов. Я не хотел вдаваться в дебри различных типов вызовов, тем более, что сейчас в x64 многие из них уже не актуальны и унифицированы. Я лишь хотел этим проиллюстрировать откуда могли взяться слухи, что Windows писался на Pascal-е и что разработчикам на конференции задавали уже этот вопрос. Ответ был - Windows писался на С.

Тип вызова функций windows api всегда был stdcall.

Откопал код из 1992 года:

int PASCAL WinMain(hInstance,hPrevInstance,lpCmdLine,nCmdShow) 
HANDLE hInstance; 
HANDLE hPrevInstance; 
LPSTR lpCmdLine; 
int nCmdShow; 
{ 
   HWND hWnd; 
   MSG msg; 
...

long FAR PASCAL WndProc(hWnd,message,wParam,lParam)
HWND     hWnd;
unsigned message;
WORD     wParam;
LONG     lParam;
{
   HDC          hDC;
   TEXTMETRIC   tm;
   PAINTSTRUCT  ps;
...

Ну я как-раз про это и говорю, откуда слухи - от не разобравшихся преподов. Увидели слово pascal - значит паскаль, однозначно!
PASCAL тут - это define __stdcall

PASCAL тут - это define __stdcall

Похоже что нет, для WIN16 это ключевое слово, как и FAR:

windef.h
windef.h

А при переходе на WIN32 его уже не использовали, и переопределили на __stdcall чтобы сохранить возможность компиляции исходников программ для WIN16 в WIN32 API.

afxver_.h:

// PASCAL is used for static member functions
#ifndef PASCAL
	#define PASCAL  __stdcall
#endif

Так ведь _pascal calling conversion давно уже устарело и не рекомендуется к использованию. Это смотря где определение.
https://learn.microsoft.com/en-us/cpp/cpp/obsolete-calling-conventions?view=msvc-170

У меня в 2022 Студии, например, вот такое определение.
У меня в 2022 Студии, например, вот такое определение.

В общем это всё дела уже минувших дней и неактуально сегодня.

Для WIN16 calling conventions _pascal был актуален, т.к. использовался до появления WIN32 API, в котором от него отказались -- быстродействия процессоров уже хватало. Но даже современный тулчейн MS может собрать чистый код для WIN16 API из 1992 года, не меняя ни строчки(проверил -- код Win16 собирается и работает под Visual Studio 2022 для платформы Win32).

Тип вызова функций windows api всегда был stdcall.

Pascal у них использовался изначально, на stdcall они в 90-е перебрались.

builder (строитель) - один из паттернов проектирования

>существуют везде

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

Все авторы, использующие систему дизайн паттернов в области программирования, отмечают что ее придумал архитектор Кристофер Александер. В своей книге A Pattern Language, изданной в 1977 году, он впервые предложил способ описания знаний в некоторой области с помощью паттернов, каждый из которых описывает проблему-конфликт и способы ее разрешения.


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

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

Функциональные языки невероятно выразительны. В функциональном языке вам не понадобятся паттерны проектирования, потому что язык настолько высокоуровневый, что вы легко начнёте программировать в концепциях, которые исключают все известные паттерны программирования. Одним из таких паттернов является Адаптер (чем он отличается от Фасада? Похоже, что кому-то понадобилось наштамповать побольше страниц, чтобы выполнить условия контракта). Этот паттерн оказывается ненужным если в языке есть поддержка каррирования." (Из статьи на Хабре)

Прошу судить строго, так как это помогает двигаться вперёд

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

Есть случаи (контекст) когда нужно-удобнее в каком то смысле использовать просто функции,

Есть случаи (контекст) когда нужно-удобнее в каком то смысле прятать функции в классы,

Есть случаи (контекст) когда сделать то или другое просто невозможно,

...

Мне кажется лучше чем аналогия с плоскогубцами и болгаркой подходит аналогия с

плоскогубцами и металлом из которых они сделаны. Давайте объясним чем плоскогубцы лучше металла (в чурках например), кто возьмется?

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

Если бы передо мной стояла задача объяснить сотруднику, чем классы лучше функций, я бы сказал:

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

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

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

offtop: 1.4k комментариев, 102 голоса за карму, и карма в нуле) Вы господин батька прям балансируете между белым и черным, между добром и злом, огнём и водой, между любителями ООП и функциональщины)

Мне карму сильно слили во время бурного и офтопного обсуждения одного судебного процесса в США. Там вопрос вообще не касался IT. Просто на эмоциях все были.

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

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

Процедурщина != фукциональщина

Да ничем. Просто так исторически сложилось. 

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

ООП удобно описывать gui

Тоже спорно - GUI удобнее описывать наверно на языках разметки, вроде HTML, XML, XAML и пр. Логику конечно писать на них не получится, поэтому и выдумывают всякие симбиозы языков. На ООП языках достаточно удобно моделировать ту же бизнес логику.

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

Полностью согласен

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

тут несогласен - костыли будут или нет, зависит от того, кто это создавал, от его знаний и опыта.

Рискую высказать непопулярную точку зрения. Шум вокруг ФП стоит последние лет 40. Какие-то отдельные элементы вошли в массовую культуру, но в целом парадигма так и не стала мейнстримом. Императивный код в итоге проще писать, поддерживать, отлаживать по шагам и расширять.

Разбивка кода на функции ещё не подразумевает функциональное программирование, и не факт, что автор имел в виду ФП как альтернативу ООП. Может, он тёплое ламповое ПП подразумевал. :)

Если бы передо мной стояла задача объяснить сотруднику, чем классы лучше функций, я бы сказал:

Да ничем. Просто так исторически сложилось.

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

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

Вы путаете функции и функциональный подход

Можно пример, как вы собираетесь писать код без абстракций? Разница классов и функций только в этом по сути.

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

interface Library {
  getArticles(authorID int64) []Article
}

Можно ли заменить это на сигнатуру функции, к примеру: func (authorID int64) []Article? Конечно. Но функция сама по себе не чистая, ей для работы нужен какой-то коннекшен к базе. И где его взять?

Если прокидывать коннекшен прямо в аргументы вот так:

getArticles(conn redis.Client, authorID int64) []Article
                 ^

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

В классах мы можем без проблем разделить передачу аргументов на 2 разных штуки:

  • конструкторы (скрытую явно от остального кода)

  • функции (открытую, публичную часть)

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

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

Но функция сама по себе не чистая

Именно эта проблема в коде и решается в данном случае хаком в виде применения поля класса.

Является ли коннекшен к базе атрибутом класса библиотеки, как абстракции проектирования? Вовсе нет.

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

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

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

это не правда. Переменная класса может быть абстрактная (к примеру интерфейс). Вот тогда и будет у нас абстрактное программирование. Без всяких хаков можно писать сносный solid код на классах и ООП. Даже в том же go, где вообще нет классов, но есть некое подобие ОПП всё с той же целью - иметь возможность абстрактного программирования, внедрения зависимостей, интерфейсы и т.п.

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

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

Но позвольте! - ваш interfacе Library где-то то должен кто-то создать. Создать объект, который реализует этот интерфейс и только тогда предоставить его "бизнес логике".

В ФП стиле, аналогично вы можете вызвать функцию, передать ей conn, и эта функция возвратит вам другую функцию: getArticles(authorID int64), с одним аргументом, которую вы и будете использовать в своей "бизнес логике".

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

Это напоминает создание объекта, передача ему параметров через конструктор или установка параметров через методы SET и передача в "бизнес логику" интерфейса, состоящего из вызова ОДНОГО метода этого объекта.

У меня ощущение, что хотелось подсветить некоторые моменты из DDD. Т.е. спор был не про "классы vs функции", а про "архитектура и декомпозиция vs код-лапша".

Вообще-то, класс - это расширение понятия структура.

Если структура - это набор данных, связанных определённым образом,

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

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

----------------------------------

Про лучше или хуже...

Отвечу вопросом на вопрос.

А что лучше C или C++?

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

Перефразируем анекдот.

  • ну чем, чем классы лучше?!

  • чем функции!

Это код, который работает с минимальным количеством ошибок

С минимальным? А три - это куча? (с)

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.

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

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

Когда я вижу просто одну строчку:

article_set_meta($articleId, '');

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

Но вместо этого приводится пример:

$Article = new Article($articleId);
$Article->setMeta('');

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

$Article->save();

какой-нибудь дописать?

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

С функцией такого разночтения не возникает.

Оно уже записалось по месту хранения статьи

Разве в случае article_set_meta ответ известен заранее? Вы же не будете ожидать, что каждая такая функция будет лезть в базу, это как минимум неэффективно.

В случае отправки id сохранение в базе подразумевается в примерно 98% случаев ) Хотя конечно могут быть варианты. Но и с объектом тоже не так сложно - достаточно один раз ознакомиться с шаблоном проектирования

article_set_meta - сохраняем?

article_set_title - сохраняем?

article_set_body - ещё раз сохраняем?

Крайне неэффективный подход.

article_set($id_article, ["meta"=>'', "title"=>'', "body"=>'']);

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

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

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

Типы вы тоже не использовали 15 лет? :)

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

Ок, уволите меня, если вдруг станете моим начальником :)

Подобный довод в технической дискуссии на хабре не уместен.

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

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

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

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

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

А все потому, что на каждых первых курсах их учат, что процедуры это прошлый век, что ООП это самый смузи-современный подход, умалчивая, что он старше этих самых джунов раза в 2,а то и в 3, что ООП для пацанов, остальное для лохов и тд. ООП ИЛИ СМЕРТЬ! То же самое про монолиты vs микросервисы, то же самое про язык1 vs язык2. То же самое про фреймворки. То же самое про стандарты типа datetime vs timestamp. И тд и тп.

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

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

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

Инкапсуляция - это не про классы,

Наследование переоценено и заменяется интерфейсами. Да и реализация интерфейсов куда понятнее, чем куб, отнаследованный от квадрата, потому что неофит решил, что раз есть x и y, то и z не помешает.

Полиморфизм... ну интерфейсы решают.

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

Классы это объединение данных и кода, который с ними работает в одной сущности.

Инкапсуляция - это не про классы,

Как раз про них - Слово «инкапсуляция» происходит от латинского in capsula — «размещение в оболочке».

Полиморфизм... ну интерфейсы решают.

А чем абстрактный класс не угодил? в пределе, абстрактный класс в котором все члены абстрактны == интерфейс.

Классы это объединение данных и кода, который с ними работает в одной сущности.

Сигнатура функции делает то же самое.

Инкапсуляция - это про сокрытие внутренностей, которые не нужны потребителю. Это про области видимости, а не ООП и классы.

А чем абстрактный класс не угодил? в пределе, абстрактный класс в котором все члены абстрактны == интерфейс.

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

Сигнатура функции делает то же самое.

Окей, можете сделать функцию, аналогичную, скажем контейнеру List<T> (линейный список) или HashSet<T> (вырожденная хэш таблица, просто для поиска ключа), где есть общие данные, с которыми можно работать используя разные методы, Add, Get, Delete, Enumerate и т.д.? Думаю что нет, с классом это сильно удобнее т.к. вы его воспринимаете как черный ящик, который делает нужные вам вещи, в описанных примерах это абстракции контейнеров с которыми удобно работать. В случае с функциями - вы будете писать пачку функций, где вам нужно будет либо таскать эти общие данные через параметры, либо держать их где-то в глобальных или неких статических переменных, видимых только в нужной вам области (если говорить про Си - в единице компиляции, т.е. конкретном файле). Это как минимум неудобно

Инкапсуляция - это про сокрытие внутренностей, которые не нужны потребителю. Это про области видимости,

Нет, сокрытие данных это сокрытие данных и это просто механизм, помогающий инкапсуляции, инкапсуляция это именно то о чем я сказал. Вон даже википедия согласна со мной https://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)

В сообществе C++ или Java принято рассматривать инкапсуляцию без сокрытия как неполноценную. Однако, некоторые языки (например, SmalltalkPython) реализуют инкапсуляцию, но не предусматривают возможности сокрытия в принципе. Другие (Standard ML , OCaml) жёстко разделяют эти понятия как ортогональные и предоставляют их в семантически различном виде (см. сокрытие в языке модулей ML).

Ну, учитывая, что не во всех ОО-языках есть обращение к полям/функциям объекта без явного указания на объект (типа this->my_function) – пачка функций, принимающих объект первым аргументом, не очень-то отличается от функций объекта.

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

Откуда три или более параметра? Всегда ровно один дополнительный параметр – this. Пока у вас нет виртуальных функций – всё вообще прозрачно.

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

Откуда три или более параметра?

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

Собрать все методы для работы с объектом в одном месте – хорошая идея, да и права на доступ к внутренностям объекта удобнее ограничивать

О чем и речь - это сильно больше чем синтаксический сахар, хотя конечно можно все высокоуровневые языки называть синтаксическим сахаром по сравнению с ассемблером :)

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

Вы почему-то меня поместили на чашу "ооп не надо"

Простите, но так показалось сначала :)

А я на чаше, "и без ооп все можно сделать"

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

Окей, можете сделать функцию, аналогичную, скажем контейнеру List<T> (линейный список) или HashSet<T> (вырожденная хэш таблица, просто для поиска ключа), где есть общие данные, с которыми можно работать используя разные методы, Add, Get, Delete, Enumerate и т.д.?

Легко:

function ArrayList() {
  const data = [];
  return {
    size: () => data.length,
    append: item => data.push(item),
    get: i => data[i]
  };
}

const list1 = new ArrayList();
list1.append(1);
list1.append(2);
list1.append(3);
for (let i = 0; i < list1.size(); i++) {
  console.log(list1.get(i));
}

Примерно то же самое будет в каком-нибудь Lisp. Классы - просто синтаксический сахар.

Отлично! но это Java(Type)script, а скажем если Java, C#, C++?

Это конечно такой себе пример:

class ArrayListAsFunctionTest {

    interface IList<T> {
        int size();
        void append(T item);
        T get(int i);
    }

    <T> IList<T> newArrayList(int capacity) {
        var data = new Object[capacity];
        var size = new int[1];
        return new IList<>() {
            public int size() { return size[0]; }
            public void append(T item) { data[size[0]++] = item; }
            public T get(int i) { return (T) data[i]; }
        };
    }

    @Test
    void test1() {
        IList<Integer> list1 = newArrayList(100);
        list1.append(1);
        list1.append(2);
        list1.append(3);
        for (int i = 0; i < list1.size(); i++) {
            System.out.println(list1.get(i));
        }
    }

}

В Java мы вынуждены объявить интерфейс. Плюс в newArrayList() создаётся анонимный класс. Ну, это язык такой, в нём даже Hello World без классов не напишешь.

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

А в Java/C# классы - это неотъемлемая часть языка, без них ничего не написать. Причём часто это приносит больше проблем, чем пользы. Люди хранят временное состояние в полях объекта: в одном методе что-то пишется в поле, потом вызывается другой метод, в котором это поле читается и т.д. Т.е. поля используются не для хранения долговременного состояния объекта, а просто для обмена данными между методами, типа глобальных переменных в Си, но ограниченных классом - понять что делает такой код очень сложно, плюс гарантированы проблемы с многопоточностью. Или делают какие-то безумные иерархии классов.

Основа ООП - это инкапсуляция, полиморфизм и наследование.

Инкапсуляция может достигаться разными способами, для этого не обязательно наличие в языке ключевого слова class. Можно это делать и через замыкания как JavaScript, Lisp и т.д.

Полиморфизм в принципе тоже работает и без классов.

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

Это конечно такой себе пример:

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

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

Имеем класс ArrayList (даром что синтаксически он не класс) с методами append, size и get и полем data, которое само объект класса массив. Создается экземпляр класса list1, и пошло-поехало.

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

Объясните дураку, чем это лучше чем рабоче-крестьянские (классические) классы?

Ну и такие фокусы невозможны в тех языках что я выше упомянул.

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

Вот! Наверно это самый точный ответ.

Не с классами. Больше похоже на prototype-based, как в JavaScript (разница вылезет, когда решите отнаследоваться от объекта, перекрывая его методы).

Чтобы "с классами" – надо ещё один уровень косвенности добавить.

ООП - это одна парадигма программирования из множества. Есть клевая книга "Concepts, Techniques, and Models of Computer Programming" by Peter Van Roy and Seif Haridi, в которой это очень хорошо разложено по полочкам.

В каждом языке программирования акцент сделан на определенных парадигмах. В Java/C# - это изначально ООП, плюс немного параллельных вычислений, немного ФП, немного метапрограммирования (рефлексия) и т.д. В Prolog - основной акцент на логическом программировании. В Lisp - на метапрограммировании, языково-ориентированном программировании (макросы).

практически чистое ООП с классами

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

Мне лично вообще чужд этот подход смотреть на всё через призму объектов. Когда-то давно прочитал статью Пола Грэма "Programming Bottom-Up". И мне ближе всего такой подход, при котором приложение по сути строится из множества микрофреймвоков. Например, я делал инструмент моделирования, в котором были такие микрофреймвоки: 1) для работы с коллекциями, всякие специфические итераторы, чтобы перебирать элементы в обратном порядке и т.д. 2) для работы с геометрическими фигурами 3) библиотечка с разными алгоритмами типа A* 4) для рендеринга UI 5) для парсинга строк 6) для стека команд и т.д.

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

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

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

Те же List<T>, HashSet<T> и т.д., которые упоминались выше - это скорее про алгебраические типы данных, чем про ООП.

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

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

Вы посмотрите с чего я начал:

Классы - это синтаксический сахар для фунций и структур

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

Если не брать во внимание все что связано с наследованием - да, не добавляют.

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

Все так, только trait-ы могут наследовать друг от друга :) но это совсем не то же самое нежели чем в том же C++

Не могу чего-то ещё добавить, но хочу отметить, что дядя Боб с вами согласен (немного апелляции к авторитету):

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

– Роберт Мартин, "Чистая архитектура"

можете сделать функцию, аналогичную, скажем контейнеру <...> где есть общие данные, с которыми можно работать <...> Думаю что нет ...

Это самое простое. На С это реализуется проще чем кажется.
Гораздо сложнее реализовать на С наследование, перегрузку и виртуальные ф-ии, и невозможно реализовать перегрузку операторов и деструктор.

Перегрузка операторов вообще никак не связана с ООП.

На Си её сделать нельзя, но на кондовейшем процедурном Фортране, например, можно.

Ну и не во всех языках вообще есть операторы (operators).

А чем абстрактный класс не угодил? в пределе, абстрактный класс в котором все члены абстрактны == интерфейс.

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

Наследование переоценено и заменяется интерфейсами

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

Можно сделать класс A с одним методом A1 и функцией A2 как параметр в конструкторе.

Можно. Сделать вообще всё можно и на чём угодно. Но я то написал:

без применения костылей

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

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

Статья, где комментарии лучше содержимого. Особенно понравились комментарии от:@GospodinKolhoznik, @aamonster, @ivankudryavtsev . В статье только один плюсик использования классов - инкапсуляция, остальные даже близко не рассматриваются.

Какие-то рассуждения сорта "Почему молоток лучше чем отвёртка"

Как-то однажды знаменитый учитель Кх Ан вышел на прогулку с учеником Антоном. Надеясь разговорить учителя, Антон спросил: "Учитель, слыхал я, что объекты - очень хорошая штука - правда ли это?" Кх Ан посмотрел на ученика с жалостью в глазах и ответил: "Глупый ученик! Объекты - всего лишь замыкания для бедных."

Пристыженный Антон простился с учителем и вернулся в свою комнату, горя желанием как можно скорее изучить замыкания. Он внимательно прочитал все статьи из серии "Lambda: The Ultimate", и родственные им статьи, и написал небольшой интерпретатор Scheme с объектно-ориентированной системой, основанной на замыканиях. Он многому научился, и с нетерпением ждал случая сообщить учителю о своих успехах.

Во время следующей прогулки с Кх Аном, Антон, пытаясь произвести хорошее впечатление, сказал: "Учитель, я прилежно изучил этот вопрос, и понимаю теперь, что объекты - воистину замыкания для бедных." Кх Ан в ответ ударил Антона палкой и воскликнул: "Когда же ты чему-то научишься? Замыкания - это объекты для бедных!" В эту секунду Антон обрел просветление.

count, substr, strpos, is_null - вот это функции.

У функции есть одна задача - взять аргументы и на из основе вернуть ответ.

article_set_meta, article_add_image, article_render - это процедуры. Могут и в базу данных залезть, и глобальные переменные менять, и что угодно.

И да, процедурное программирование тяжелее масштабируется, поэтому за пределами определенных ниш мало где применяется.

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

Ну давайте ещё поспорим, что лучше Windows или Linux без указания контекста применения.

Стабильностью? Простотой использования? Наличием большинства игр?

Есть же где-то такие сотрудники... Сейчас работаю в кровавом энтерпрайзе. Таких сотрудников не встречал сто лет...

Sign up to leave a comment.

Articles