Обновить

Паттерны ООП c примерами на Java: порождающие шаблоны

Уровень сложностиСредний
Время на прочтение14 мин
Охват и читатели17K
Всего голосов 43: ↑41 и ↓2+53
Комментарии22

Комментарии 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 методом, можно позапускать и почитать комменты.

Короче, если кому то будет полезно:

https://github.com/youngmyn/samples-patterns-gof

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

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

Можно уже следующую часть? :) Хочется почитать

Завтра выйдет)

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds