Комментарии 22
Снова они, снова паттерны :) Получилось вроде хорошо и доходчиво, но неплохо было бы сначала сделать небольшое введение в ООП, чтобы понимать понятия, которыми автор оперирует.
увидев картинку к статье подумал что будет ЕЩЁ одно пояснение ООП с котиками. Но нет, пронесло в этот раз
Побуду занудой и критиком. В Java уже давно как завезли функциональные интерфейсы. И значительная часть шаблонов при помощи их можно либо просто схлопнуть в ничего или значительно сократить. Например, классы SmsNotifier и EmailNotifier, что в FactoryMethod - они же в чистом виде мусор. Можно было просто методы сделать и из chooseService нужный вернуть.
Вы правы, в Java многое можно свести к лямбдам и возвращать из фабрики просто функцию. В примере хотелось сформировать, так скажем, ментальную модель, более явную. Я пожалуй, добавлю в статью реальный пример с функциональными интерфейсами. Спасибо большое за комментарий
более явную.
Функции - и есть более явная. То что тут показано - это костыль, придуманный на замену в те времена, когда указателей на функцию не было.
костыль, придуманный на замену в те времена, когда указателей на функцию не было
Указатели были даже в си. Но нужно было явно преобразовать из указателя в функцию, зная тип функции. И это ломало всю красоту.
А так да, костыль.
костыль, придуманный на замену в те времена, когда указателей на функцию не было
В Java их и сейчас нет. "Лямбды" -- это синтаксический сахар, а под капотом всё та же эмуляция ФП через ООП с паттерном Стратегия, встроенным в JVM через механизм интерфейсов. Разве что вызов метода класса, в который вынесен код лямбды, осуществляется через объект реализуемого интерфейса, содержавший ссылку на враппер этого метода (типа как java.lang.reflect.Method) и перед первым использованием создаваемый на лету через вызов новой команды JVM invokedynamic вместо, как раньше, создания экземпляра анонимного класса через оператор new.
Указатели на функцию появились раньше лямбд. Но в других языках. В Java, насколько я помню, изначально заморочились на безопасности и решили отказаться от указателей вообще. А в С использование указателя было единственным способом передать блок кода в качестве параметра куда-либо.
Что такое порождающие паттерны в философском плане? Это создание долгоживуего уникального объекта методом более сложным чем конструктор, а ответственность создания не на объекте а вне его потому что инвариант затрагивает не только его самого? Для ФП значит не применимо совсем, там создается структура, а создающий метод с ней не связан? Кажется, природа ООП тут приводит к усложнению.
В ФП ответственность создания не на отдельном объекте, а на контексте / вызывающем сервисе. А в ООП жёстко: либо ответственность на самом объекте (в конструкторе) либо на другом объекте (чистая выдумка), который пытается вобрать в себя контекст, вариации логики создания. Это сложнее, но иногда в этом есть плюсы (логика создания сконцентрирована, а не размазана по юскейсам).
В ООП иногда лучше, чаще хуже. И хорошо, чтобы язык позволял оба способа.
Перефразируя
ООП:
Ответственность должна быть на объекте (принцип инкапсуляции)
Но инвариант контекстный, значит конструктор недостаточен. Вводим отдельный объект (Factory/Builder). Логика создания концентрирована, но искусственна (объект ради объекта)
ФП:
Ответственность на вызывающем сервисе (и контекстность логики тут выглядит естественно и хорошо вписывается в слоистую модель)
Создание = функция (может быть где угодно). Логика создания размазана по use cases.
Плюсы концентрации (ООП Factory):
-Одно место для всех вариаций создания
-Переиспользование сложной логики
-Легче найти/изменить
Минусы:
-Лишняя сложная абстракция (чистая выдумка без аналогов а реальном мире, что для ООП не естественно)
-Зависимости тянутся в Factory. Ломает слои, потому что тянет контекст в домен.
В ФП порождение мы и не пытаемся протащить в домен. А в ООП пытаемся. Интерфейсами можно абстрагироваться, но это снова добавляет сложности
Для меня минусы перевешивают
Ответственность должна быть на объекте (принцип инкапсуляции)
А если на объекте лежит и непосредственная ответственность, кроме хранения данных, для чего его класс и создавали, то... похоже, страдает принцип единственной ответственности...
В ФП ответственность создания не на отдельном объекте, а на контексте / вызывающем сервисе. А в ООП жёстко: либо ответственность на самом объекте (в конструкторе) либо на другом объекте (чистая выдумка), который пытается вобрать в себя контекст, вариации логики создания.
В ООП ответственность за создание объекта ВСЕГДА лежит вне этого объекта. Уже просто потому, что если объект нужно создать, значит этого объекта ещё нет — и как то, чего нет, может отвечать за что либо?
"Конструктор" — это костыль для недоООП-языков, где есть классы, но классы не являются объектами. Известно ровно два логически полноценных подхода к созданию объектов: объект можно создать либо с помощью уже существующего объекта — через клонирование, либо с помощью класса — через создание экземпляра.
В этом смысле порождающие паттерны являются вполне логическим продолжением: если создание объекта связано с некоторой сложностью, делегируем эту сложность отдельному объекту.
С "философской" точки зрения всё логично и самоподобно, а потому требует меньшее количество сущностей, и, следовательно, проще.
Wat?
Конструктор - это часть контракта типа. Тип существует до экземпляра. Ответственность на уровне типа, не экземпляра.
По сути: Путаешь runtime существование с design-time ответственностью. Класс отвечает за инвариант своих экземпляров - это и есть его ответственность при создании
Неожиданно, да? Но нет, уважаемый, путаете вы. Точнее, скорее всего, запутали вас.
В нормальном, чистом ООП достаточно двух базовых видов сущностей: объекты и сообщения. Ни конструкторов, ни типов, ни run-/designtime, ни даже классов и, тем более, типов не нужно — всё это, если и будет, то будет в виде "деталей реализации".
Понимаю, что вам — не лично вам; выросло уже не одно, не два, и даже не три поколения программистов, для которых ООП это три (может быть, четыре) "кита" — недосуг и (морально?) тяжело заставить себя более глубоко изучить вопрос "что такое ООП". Но если вы таки заставите себя почитать, например, The Early History of Smallltalk, или — ещё лучше — на практике попробовать Smalltalk (например, в виде Pharo) или Self, то, возможно, увидите что-то интересное для себя. А "ущербная" парадигма может вдруг заиграть неожиданными красками. И, кстати, вы можете найти интересные связи с так любимым вам ФП.
Но если вам удобнее пользоваться, пусть и не логичная, но привычной "мейнстромовой" трактовочкой ООП а-ля Страуструп, то просто игнорируйте мои сообщения.
не, я пас
если в голове у ООП последователей такое, я прохожу мимо
"ООП" в 2025 году — это зонтичный термин для очень разных подходов.
Но честно говоря smalltalk обсуждать совсем не хочется - его не используют. Предположительно из-за динамической типизации и низкой скорости. Думаете, он мог бы стать вторым питоном, если бы не ошибки его маркетинга (платный, неудобный синтаксис)?
если вам удобнее пользоваться, пусть и не логичная, но привычной "мейнстромовой" трактовочкой ООП а-ля Страуструп
я вообще стараюсь не пользоваться ООП, насколько это возможно
Не думал, что когда то пригодится, но как будто в тему.
Я когда изучал паттерны, делал самому себе шпаргалку по ним, на Java. Выложил на гитхаб супер давно.
Делалось для себя (кстати, пригождается до сих пор), поэтому там куча пояснений в комментариях, это компиляция найденных мной(вручную!) обьяснений из интернета, плюс собственные размышления.
Разбил по директориям, на структурные, порождающие, поведенческие.
Каждый паттерн - семпл кода с main методом, можно позапускать и почитать комменты.
Короче, если кому то будет полезно:
К удивлению для статьи с таким названием, обнаружил в ней нечто полезное: объяснение разницы между фабричным методом и абстрактной фабрикой с разъяснением зачем нужна фабрика фабрик.
Основным шаблоном проектирования является "Стратегия" как способ присвоить чему-то значение в виде исполняемого кода с известным контрактом вызова -- способ облечь функциональное программирование в одежды объектно-ориентиованного. В JVM-языках реализуется через понятие интерфейса.
Можно уже следующую часть? :) Хочется почитать
Паттерны ООП c примерами на Java: порождающие шаблоны