Comments 40
Какой смысл статьи? Вроде всех тут учили, что основой ООП являются обьекты состоящие из хранилища данных и методов работы с ними.
Когда ты понимаешь, что обычный банкомат это и есть классический "Объект" (внутри лежат деньги, и у него есть некий набор правил, что с деньгами можно делать) то дальше все понятно и просто....
Ты привел редчайший случай когда моделируется реальный объект, а не бизнес процесс, который не ООП ни разу
Ну здрасьте:
Бизнес-процесс: сделка.
Создание "сделки" (предварительные условия), оформление сделки, предоплата, реализация, оплата, акт.
Наследование: тип "оферта" (условия задает продавец), "оплата" фиксирует оформление и предоплату, реализация - передача товара/услуги, акт - чек.
Наследование: тип "договор ГПХ" (условия согласуются в процессе), оформление договора, предоплата может быть или не быть, реализация, оплата, акт приема-передачи.
Всё что угодно можно подвести под ООП, вопрос в том, будет ли от этого выгода и польза...
Проблема ООП не в ООП, а в том, что 99% тех, кто сегодня использует ООП-фреймворки, вообще не владеет методами ООП и не понимает за декомпозицию. Они просто знают, что "есть функции, при оформлении которых нужно первым параметром писать self/this, а обращаться к ним через точку после переменной".
Ещё лет 15 назад с пониманием было получше. Но не от того, что люди были умнее, а от того, что фреймворков было меньше и они требовали больше понимания от программиста. Накопипастить вообще не включая голову было затруднительно.
В целом, сегодня именно ООП достигло той радостной черты, когда чтобы с его помощью сделать что-то полезное, можно его вообще не знать. Главное не делать из этого полезного библиотеку, но кого ж остановишь?
Существуют разные классы задач. Причём, не просто разные, а разные по степени сложности. У меня в блоге висит есдинственная статья, там, как раз об этом. Соответственно, чистый ФП, вроде ЛИСПа, относится к первому уровню сложности, ООП - к третьему. Если вам в языке приходится сочинять "шаблоны на шаблоны", или изобретать 25 видов полиморфизма - это верный признак того, что выбранный инструмент не соответствует предметной области. Хотя, есть ещё вариант, что кто-то, не понимающий ООП всё-таки создал мега-популярную библиотеку и теперь приходится его творчество компенсировать технологическим костылём.
Например, если Вы попытаетесь сделать Vue.js на ФП, то число сущностей и суперструктур, которые придётся при этом ввести, превысит все разумные пределы. В конечном итоге, вы переизобретёте ООП, только на функциональном слэнге. И это будет больно. И у вас будет целых 3 почитателя. Потому что когда вы манипулируете объектами - вам нужен ООП. А подход ФП предназначен, в первую очередь, для манипулирования состояниями.
Vue.js может использовать как ООП так и ФП (функции для обработки запросов, а классы для бизнес-логики или моделей), но ФП рекомендуется, особенно для обработки данных.
React :рекомендуется функциональный подход (функции и хуки).
Кстати, react более популярен. Так что мимо.
Проблема ООП не в ООП, а в том, что 99% тех, кто сегодня использует ООП-фреймворки, вообще не владеет методами ООП и не понимает за декомпозицию
И как следствие, ООП хорош в основном для простой логики и минимальной композиции. А как начнёшь лезть глубже, начинаются проблемы. Что я и показал в статье.
И как следствие, ООП хорош в основном для простой логики и минимальной композиции.
все наоборот, ООП хорош для больших проектов, когда реально провести и описать программные интерфейсы (и разделить их между разными исполнителями).
а для маленьких проектов (на одного программиста) хорошо или процедурное или вообще функциональное программирование...
Функциональное это примерно как процедурное? А, ну всё ясно. )
ООП хорош для больших проектов
В средних и больших проектах с бизнес логикой появляется функциональное ядро, 10% ответственного кода, который пишет лид. 90% остального кода связано с вариантами использования, репозиториями, перекидыванием JSON, его действительно обычно лучше писать на ООП и с этим справится джун/мидл.
Рассуждений на тему что такое ООП и ФП уже довольно много. А вот чего не хватает, так это описания какая парадигма к каким задавал лучше подходит. Всё же язык и парадигма это инструмент. А всякий инструмент хорошо справляется со своими задачами и не очень хорошо с другими.
Вот например ООП - для каких задач оно хорошо?
GUI - просто идеально! Если надо реализовывать GUI, то лучше ООП наверное ничего не существует. Как будто этот инструмент создан именно под эту задачу.
Игры - тоже очень хорошо. Игровой мир хорошо описывается с помощью ООП. Возможно это так, потому что в момент бурного развития игровой индустрии игры делали такими, чтобы их легко было реализовывать с помощью ООП. Есть такое ощущение, что в данном случае не инструмент подгоняли под задачу, а задачу под инструмент. Но как бы то ни было для описания игровых миров ООП удобно.
А вот все остальные задачи как то не очень. Для бэкэнда ООП паршиво - куча костыльных шаблонов проектирования тому подтверждение. Для фронта лучше, т.к. фронт это во многом GUI, там где начинается логика все уже не так хорошо.
Наверное многие со мной не согласятся. Многие скажут - а вот я пишу бэк на ООП и у меня все отлично, что я делаю не так? И разумеется если у человека все отлично, то он все делает так. Я всего лишь высказал свое мнение, как я вижу, для каких задач ООП отлично подходит, а для каких не очень.
Рассуждений на тему что такое ООП и ФП уже довольно много
Те, что я видел это рассуждения практиков. И это не математически точные определения. Ну вот так, а это вот так. Определения не точные и опираются на "очевидности".
Я в сущности не критикую ООП, просто к него нет дна основы. И когда нужно что-то особенное или масштабное, начинаются проблемы.
ООП - для каких задач оно хорошо?
Да, GUI, ORM, CRUD, игры. И примерно на этом всё заканчивается.
Многие скажут - а вот я пишу бэк на ООП и у меня все отлично, что я делаю не так?
Потом его, конечно, в дурку забрали. Некоторые и на пхп 7 пишут, который не ООП даже а структурное. Просто глубина проблем не так велика чтобы использовать что-то более тяжёлое
Однажды один программист открыл для себя ООП.
И ему настолько понравилась логика - что он начал писать классы на всё, а потом оптимизировать и структурировать, и дооптимизировался до единого Класса описывающего всё и сразу, от которого затем наследовалось то, другое, третье, и совсем одно на другое не похожее.
И понял он, что это не хорошо, и не стал больше писать классами без крайней на то не обходимости.
Ясно. Тут намекают что ООП не «плохое» и не «устаревшее». Оно просто оптимизировано под определённый участок сложности – тот, где ~80 % рынка действительно живёт. Там достаточно «джун + рецепты», и бизнесу это выгодно. А скорость входа в язык или проблему это скорость найма, дело даже не в фонде оплаты труда.
Когда конкретная задача выходит за пределы сложности нужно точечно вводить ФП приёмы. Если такого много, то было лучше взять более выразительный язык.
Практическая эвристика для лида (как пример)
1) Число слоёв абстракции > 3? (Controller → Service → Manager → *?)
2) Кол-во «if» в одном методе > 7?
3) Нужно 10+ файлов, чтобы добавить один новый «вариант»?
Если «да» более чем на два вопроса — ввести:
• неизменяемые структуры данных,
• функции высшего порядка,
• алгебраические типы (enum + payload) или Result/Option
В целом – да, идея именно такая: бизнес-критичную часть («functional core») делает опытный разработчик, а остальную «обвязку» (транспорт, БД, мэппинг DTO ↔ JSON, orchestration use-case’ов) могут тянуть мидлы/джуны классическими ООП-инструментами
Статья немного пустовала, но посыл хороший и я согласен со всеми пунктами.
ООП зачастую не раскрывается, вместо сущности парадигмы раскрываются отдельные механизмы, которые ее реализуют.
Была некая эволюция от идей Алана Кея и до практического выражения в с++ и современных яп, поэтому есть несколько трактовок
Основная идея довольно проста - организация программы в виде набора объектов с данными и методов их обработки. Я думаю что главное здесь это изоляция состояния и обеспечение его валидности, а так же хорошая близость логически связанных данных и методов их обработки.
Идея довольно хороша, и понятна для людей, которые работали с ранними яп и структурным программированием, но сейчас, когда все яп так или иначе поддерживают методы и инструменты ООП, это не столь очевидно.
Из этой простой идеи на самом деле не следует, как правильно ее готовить, хорошая декомпозиция и организация - все ещё очень большой вопрос
Все остальное, что перечисляют в комплекте с ООП - механизмы обеспечения, паттерны, это как раз более конкретные приемы для работы с ним, но никак не его суть и описание. Наследование реализаций видимо нам всем тут подгадило.
Да, Господи!
Хоть кому-то понравилась углубленная теория.
И да, я уже понял про другим комментариям что мелкое погружение устраивает 95% программистов и экономически оправдано. А в сложных случаях они плачут и зовут маму.
Я думаю что главное здесь это изоляция состояния и обеспечение его валидности, а так же хорошая близость логически связанных данных и методов их обработки
Да. Но если этого не хватает, то лучше совсем запретить изменение данных, тогда и защита /обеспечение не понадобится, не потребуется такая странная штука как "ответственность" за данные ака single responsibility и ещё много других вкусняшек.
паттерны, это как раз более конкретные приемы для работы с ним, но никак не его суть и описание
Как только появляется необходимость в паттернах значит вы уже упёрлись в пределы ООП, нужны костыли и дальше будет только хуже.
Наследование использовали для DRY, но недостатков больше чем достоинств.
И полиморфизм тоже важен чтобы не было стрельбы дробью при изменении кода.
Ну Я бы сказал, что полиморфизм вообще универсальная штука и нужен много где.
Вообще с абстрактной точки зрения там вообще все хорошо выглядит - если объект поддерживает некий интерфейс (операцию), то можно на нем его вызвать, каким бы он ни был.
Наследование на первый взгляд кажется хорошей идеей, видимо из этого и исходили. Это потом уже начали вылезать проблемы, хотя многие иерархии в существующих библиотеках есть и даже работают.
Собственно в этом смысле идеи Алана Кея - это не слой непосредственно объектов, это архитектурная идея ближе к идее микросервисов. То, насколько они хороши как раз видно, если делать объекты покрупнее на определенном слое архитектуры, а внутри использовать более простые структуры, DTO и объектики (написанные классами, но без предьявления строгих требований "настоящих" объектов).
Но в целом в любом случае приходит все к тому, что ООП сам по себе архитектуру не заменяет, а просто дает к ней язык и инструменты.
А хороший код получается хорошим именно в терминах архитектуры - то есть в терминах конкретных механизмов, которые ее обеспечивают. Условно, если у вас бэкэнд, пользователи и заказы - то они ваша архитектура. Если окошки, состояния и отрисовка графичков - то они.
архитектурная идея ближе к идее микросервисов
Или акторная модель. Желательно асинхронная и с очередями.
Да, именно
Кстати в этом смысле современные игровые движки - это как раз чистое actor based ООП, причем с композицией как основным инструментом.
Но это эдакое ООП на определенном уровне - графа сцены.
И все механизмы настроены на работу на этом уровне - взаимодействие с другими объектами, сообщения, какие то стандартные компоненты.
Иронично, что это же считается худшей частью, а так же то, что новички любят это использовать - концепция проста и понятна, но можно легко сделать месиво.
А опытные разработчики наоборот, пытаются изолироваться от этого (зачастую даже слишком сильно)
Идея с запретом изменения данных растет, как мне кажется, из 2-х факторов - инвалидация и многопоточность.
Неизменяемые данные очень удобны в сообщениях (просто с практической стороны) и в многопоточных окружениях (потому что очевидно точно не изменятся и не вызовут никаких гонок и прочего).
А второй фактор - для них (как и в ООП) можно контролировать валидность и консистентность через функции преобразования.
То есть в ООП идея в том, что данные внутри объекта меняются только методами внутри объекта. Соответственно все операции, которые могут изменить объект - они рядом, близко к данным, с ними работаешь в контексте объекта. Только они меняют объект, соответственно только они могут его сломать - только их и нужно проверять.
Для неизменяемых DTO - только определенные функции могут их преобразовывать и конструировать. Соответственно тоже можно обеспечить консистентность, проверив ограниченное количество функций.
Если же можно просто менять поля из разных мест - это и есть путь к тому, чтобы случайно инвалидировать объект.
Да, с появлением многопоточности и необходимости её утилизировать популярность ФП возросла.
в ООП идея в том, что данные внутри объекта меняются только методами внутри объекта
Идея: где-то (в самом объекте) сконцентрировать ответственность за изменение данных (самого объекта) неявно разделяя на до изменения и после. Но получается плохо. Лучше не менять, а порождать новые, то есть явно разделять до и после. И в этом месте нужно переключить мышление с привычного человеку на непривычное.
обеспечить консистентность
Консистентность / непротиворечивость данных двух объектов можно (строго по концепции) только введя третий объект. И это проблема, порождающая сложные паттерны банды 4х.
Да, это вообще тема сложная везде, думаю для ФП существенно решения не меняет.
Есть даже правило "минимальной достаточной информации" - операцию должен производить тот, кто обладает всей нужной информацией, но при этом минимальной.
То есть если объект А владеет Б, а Б владеет В и Г, то про В и Г знают и А и Б, но лишь последний обладает минимумом данных для этого.
Простое следствие из этого - если у вас есть список сущностей, то отдельная сущность не может им управлять. Управлять им должен владелец списка
То есть мы не можем на самом деле сделать
window.Destroy()
Мы должны делать
ui.Destroy(window)
Первая реализация требует костылей, например needToDestroy флага.
По поводу неизменяемых объектов - к сожалению это роскошь, доступная не везде.
Некоторые решения могут быть достаточно производительны, если делать преобразования в функциональном стиле, но вообще обычно работаешь с состоянием.
операцию должен производить тот, кто обладает всей нужной информацией, но при этом минимальной.То есть если объект А владеет Б, а Б владеет В и Г, то про В и Г знают и А и Б, но лишь последний обладает минимумом данных для этого.
Контролировать должен владелец. А производить операцию может кто угодно, кому владелец делегировал эту обязанность. Самому ему не обязательно, а порой даже вредно, производить операции. В частности, владелец списка может делегировать возможность удаления элемента самому этому элементу или, например, отдельному окну удаления элементов, которым тоже владеет. А может и не владеет, но так проконтролировал их общий владелец.
Прочитал вашу статью
Классическое ООП - злейший враг локальности кеша
Кстати, да. В копилку.
Очень хорошо структурированная статья. Плюсую.
Но автор ничего не сказал про "грязные" языки объектно-ориентированного программирования в статье. А именно, из-за "грязности", и пошло много такого странного понимания ООП, я считаю.
В смысле грязные?
Not pure?
Хорошо, щас добавлю приколов про мутирующие геттеры
Добавил про скрытые side effects
Насколько я знаю Smalltalk является наиболее чистый объектно-ориентированный язык программирования. Кроме того, что там есть только 3 оператора, так и действительно всё есть объект.
Лично моё мнение: если в объектно-ориентированном языке программирования появляются не объектные типы и операторы, для работы с ними (например: +, -, if-else, while, for), то язык начинает становиться "загрязнённым". Сразу скажу, что я не считаю, что это плохо.
Наиболее "грязный" объектно ориентированный язык программирования, по моему мнению, - это PHP. А наиболее чистыe, не считая Smalltalk, - это Java, C# и JavaScript. Есть и другие объектно-ориентированные языки программирования с которыми я мало работал, по этому не берусь утверждать что-либо про них.
Есть ещё глава про "Чисто объектно-ориентированные и гибридные языки" на викибукс - https://ru.wikibooks.org/wiki/Объектно-ориентированное_программирование
"pattern matching" - ничем не отличается от того, что мы в каждом классе должны добавить реализацию нового метода.
Как pattern matching будет тебе гарантировать, что ты нашел все функции, где добавил новый паттерн?
В ООП мы можем добавить метод только в базовый класс и только если нужно, переопределить для дочерних и, что не менее важно, если мы хотим, чтобы реализация метода была у всех классов, то компилятор подскажет нам, у каких классов мы этот метод не реализовали.
Пример с "выстрелом в ногу" вообще полная глупость. Ты сам написал так код. Для чего ты добавил генерацию нового токена в геттер? И что тебе мешает точно так же в ФП добавить (выстрелить себе в ногу) генерацию нового токена в функцию get_token()?
что тебе мешает точно так же в ФП добавить (выстрелить себе в ногу) генерацию нового токена в функцию get_token()?
А вы точно гинеколог статью прочитали?
Иммутабельность мешает. Надо ну много выпить специально очень постараться чтобы случайно сделать мутабельность.
Вот так должно быть. И это видно по возвращаемому типу
from dataclasses import dataclass, replace
from secrets import token_hex
from typing import Tuple
@dataclass(frozen=True)
class EmailConf:
token: str
# создание
def new_confirmation() -> EmailConf:
return EmailConf(token_hex(8))
# «отдать токен» и вернуть НОВЫЙ объект
def next_token(conf: EmailConf) -> Tuple[str, EmailConf]:
new_tok = token_hex(8)
return new_tok, replace(conf, token=new_tok)
# просто прочитать
def peek_token(conf: EmailConf) -> str:
return conf.token
Как pattern matching будет тебе гарантировать, что ты нашел все функции, где добавил новый паттерн?
Никак. Он будет гарантировать, что добавлены все типы.
Гарантию даёт не сам «pattern matching», а тройка условий, которые за него обычно выполняют в функциональных (и некоторых ‑- «функционально-окрашенных») языках:
1. Варианты типа замкнуты (closed) — их полный перечень известен компилятору.
2. Проверка exhaustiveness включена и превращена в ошибку компиляции.
3. Вы не пользуетесь «catch-all» («_» / «default») там, где нужна исчерпывающая декларативность.
Если все три пункта выполняются, то после добавления нового варианта язык сам сообщает вам «вот в этих N местах матчи больше не полные» — ровно как ОО-компилятор потребует реализовать новый абстрактный метод во всех подклассах.
Проблема мутирует его get-тера, должна решаться нормальным конструктором: object creation= resource acquisition
Если генерацию токена вынести в конструктор то все в принципе работа не как ожидалось… пока программисты конвенциям следуют. :)
ООП. Да что же ты такое?