Мне кажется, весь хейт в том, что ООП на фронте с JS просто не удобен.
Использовать его по-настоящему не получается из-за JS. У него многого не хватает, банально — нет интерфейсов.
Но есть TypeScript 🙂
Он всё ещё страдает от JS-райнтайма, но у него уже достаточное API, чтобы писать нормальный™ код.
Мне вот сильно не хватало нормального DI (без декораторов!), пока я не наткнулся на:
- github.com/wessberg/di
Другое дело, что это опять-таки не всем проектам нужно 🙂
Парадигма программирования, как и архитектура, — это инструмент для укрощения сложности. И, как с архитектурой, нам стоит исследовать выгоды и издержки перед применением.
Я вообще считаю, что ООП — это больше не про классы, а про отношения между сущностями.
Но классы — это всё ещё наиболее удобный инструмент для написания трушного™ ООП.
В простейших случаях можно обойтись и объектом с парой функций.
Но вот то, как эти объекты будут друг с другом взаимодействовать, в каких отношениях они будут находиться, _проще спроектировать_ в терминах ООП ¯\_(ツ)_/¯
Я причём не говорю, что интерфейсы и реализации — это строго лишь ООП, нет.
Просто как их использовать для проектирования и написания кода, описано обширнее всего в книгах, так или иначе связанных с ООП.
— Ладно, вот простой вопрос: можно ли вообще писать в ООП-стиле фронтовый код? Не бекенд, а фронт?
Да. (Но нужен TypeScript 😃)
Есть ошибочное мнение, что фронт — типа ненастоящее программирование.
Но современный фронт сложный ¯\_(ツ)_/¯
А сложное надо проектировать ¯\_(ツ)_/¯
Кому-то это может не нравиться, кто-то хочет, чтобы вся сложность снова ушла на сервера, но сейчас мы имеем что имеем.
Со сложностью надо как-то справляться.
У нас есть фреймворки и библиотеки, это хорошая помощь, но они решают только часть проблем. Проектированием системы всё равно надо заниматься нам самим.
И вот тут ООП может помочь:
- Как разделить обязанности между модулями,
- как использовать композицию,
- что должно быть полиморфным,
- где и какие ставить предусловия,
- что от чего должно зависеть.
Из всего, что я пробовал у ООП наиболее богатый инструментарий и словарь для проектирования систем ¯\_(ツ)_/¯
Круто, если есть или будет что-то ещё более удобное, но я не видел.
Кстати, ООП не запрещает использовать преимущества ФП! 🙂
Мы можем продолжать использовать чистые функции, иммутабельные структуры и вот это всё, даже пиша в ООП-стиле. (Такое слово есть, я проверил 😃)
Но опять же, тащить огромную инфраструктуру в небольшой проект я не стану — научен горьким опытом 🙂
Сейчас я стараюсь приносить в проект инструменты по мере их необходимости.
Обычно всё начинается с домена, внутри которого лежит пара функций да типы. Применяю S, O, I, а L и D на полшишечки. Чем сложнее становится управлять, тем больше инструментов буду использовать.
Именно поэтому мы вчера столько времени уделяли проектированию:
Чем меньше разные модули знают об устройстве друг друга — тем лучше.
Затем я в DI-контейнере указываю, какой именно класс реализует интерфейс `Logger`: github.com/bespoyasov/di-…
Таким образом я сбрасываю с себя ответственность за выбор нужной сущности на контейнер — соблюдаю принцип инверсии зависимостей: ota-solid.vercel.app/dip
Сейчас уйду ещё поработать, а потом обсудим, почему строить грамотную архитектуру проще, но не обязательно с ООП.
ООП в своих принципах подразумевает деление сложного на части.
Инкапсуляция, например — размещение данных и методов для работы с ними в одном месте. Она избавляет от необходимости знать, как устроены детали модуля.
Полиморфизм тоже абстрагирует от деталей, позволяя использовать один механизм для работы с разными сущностями.
В принципах SOLID также заложено разделение сложного на части, а ещё — барьеры на распространение изменений: ota-solid.vercel.app
Например, SRP и ISP требуют, чтобы модуль занимался только одной задачей.
OCP и LSP ограничивает изменения «коробочкой» модуля, а ещё заранее заставляют думать о том, как код будет меняться.
LSP и DIP обращают внимание на зависимости модулей и их направление.
Всё это — какие-то части проектирования.
У ООП и проектирования похожи терминология и инструментарий, поэтому, мне кажется, принципы проще применять во время рисования квадратиков на бумажке.
А теперь — о том, почему наследование как концепт должно умереть 😃
Наследование классов — это прямой путь к антипаттерну God-Object.
Проблема наследования в том, что будущее нельзя предсказать, и мы не можем заранее спроектировать такую _иерархию_, которая бы отвечала всем требования.
Нам стоит, наоборот, собирать сложное из простого — использовать композицию.
Давайте на примере воспользуемся наследованием и композицией и посмотрим, в чём разница.
Допустим, мы пишем приложение, в котором описываем живые организмы.
Что нам нужно, чтобы описать человека, используя композицию? Надо выстроить иерархию сущностей со своими свойствами:
Животные →
Млекопитающие →
Приматы →
Человек.
Что нам нужно, чтобы описать человека, используя композицию интерфейсов?
Собрать те свойства и методы, которые нам потребуются:
Человек =
Скелет +
Нервная система +
Имунная система +
Сердечно-сосудистая система + ...
Ну окей, пока выглядит одинаково.
...До тех пор пока не приходит задача научить человека летать.
Пусть в нашем приложении появляется Супермен. Он умеет стрелять лазерами из глаз и летать.
Как это впихнуть в иерархию сущностей? 😃
Животные →
Летающие животные?
Летающие приматы?
Суперчеловек?
Человек.
Нипанятна. Нам в какой-то момент придётся создать такой объект, который умеет всё, знает всё, делает слишком много.
Я, кстати, сейчас не говорю об абстрактных классах. Там несколько другое поведение, и используются они иначе. Сейчас речь именно о построении иерархии типов сущностей.
Как защититься от наследования? 😅
- Забыть о слове `extends` (или что там в вашем языке используется 🙂)
- Если есть возможность, использовать `sealed`-классы.
Если приходится писать тесты для махрового легаси, которое писали до вас, то советую посмотреть на книжку Физерса «Эффективная работа с легаси»: bespoyasov.ru/blog/working-e…
Дублирование кода _на ранних этапах_ может показать, как всё на самом деле работает, и какие паттерны мы ещё не увидели.
Удаление дублирования — это прогнозирование будущего. Чем больше исходных данных удастся собрать, тем точнее будет прогноз.
Чтобы не потерять места с копипастой, помечаю их коммент-флагом `DUPLICATE`.
После самого флага пишу, какую функциональность он дублирует. Это даёт флагу осмысленное и уникальное имя, по которому потом проще искать места для рефакторинга.