Пользователь
0,0
рейтинг
22 января 2012 в 21:18

Разработка → Паттерны ООП в метафорах

Большинство литературы посвященной паттернам в ООП (объектно-ориентированном программировании), как правило, объясняются на примерах с самим кодом. И это правильный подход, так как паттерны ООП уже по-умолчанию предназначаются для людей, которые знают что такое программирование и суть ООП. Однако порой требуется заинтересовать этой темой людей, которые в этом совершенно ничего не понимают, например «не-программистов» или же просто начинающих «компьютерщиков». Именно с этой целью и был подготовлен данный материал, который призван объяснить человеку любого уровня знаний, что такое паттерн ООП и, возможно, привлечет в ряды программистов новых «адептов», ведь программирование это на самом деле очень интересно.
Статья предназначена исключительно для новичков, так что «старожилы» ничего нового для себя не узнают. В основном статья описывает известные паттерны из книги «Приемы объектно-ориентированного программирования. Шаблоны проектирования.», но более популярным и простым языком.


Что же такое вообще паттерн в ООП?

Паттерн (от англ. Pattern) — образец, шаблон.
Представьте, что вы хотите сделать новый автомобиль, но вы никогда этим не занимались. Сколько колес и почему вы спроектируете для него? Сейчас вы уже скорее всего скажете что 4, однако почему не 3, 5, 10, 20? Потому-что практикой использования уже было выяснено, что обычные автомобили лучше всего делать на 4-х колесах — это шаблон проектирования сформированный временем. Именно такому же подходу и служат паттерны в ООП и вы не столкнетесь с ними в разработке до тех пор, пока вам не потребуется «сделать автомобиль». Однако иногда случается так, что вы создаете «трицикл», и только потом, набив несколько шишек с его устойчивость и неудачным вписыванием в колею на дороге, узнаете что существует паттерн «автомобиль», который значительно упростил бы вам жизнь, знай вы про него ранее.

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

Порождающие паттерны


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

Singleton (одиночка)

Один из самых известных и, пожалуй, самых спорных паттернов.
Представьте, что в городе требуется организовать связь между жителями. С одной стороны мы можем связать всех жителей между собой протянув между ними кабели телефонных линий, но полагаю вы понимаете насколько такая система неверна. Например, как затратно будет добавить еще одного жителя в связи (протянуть по еще одной линии к каждому жителю). Чтобы этого избежать, мы создаем телефонную станцию, которая и будет нашим «одиночкой». Она одна, всегда, и если кому-то потребуется связаться с кем-то, то он может это сделать через данную телефонную станцию, потому что все обращаются только к ней. Соответственно для добавления нового жителя нужно будет изменить только записи на самой телефонной станции. Один раз создав телефонную станцию все могут пользоваться ей и только ей одной, в свою очередь эта станция помнит всё что с ней происходило с момента ее создания и каждый может воспользоваться этой информацией, даже если он только приехал в город.
Основной смысл «одиночки» в том, чтобы когда вы говорите «Мне нужна телефонная станция», вам бы говорили «Она уже построена там-то», а не «Давай ее сделаем заново». «Одиночка» всегда один.

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

Registry (реестр, журнал записей)

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

Паттерны «одиночка» и «реестр» постоянно встречаются нам в повседневной жизни. Например бухгалтерия в фирме является «одиночкой», потому как она всегда одна и помнит что с ней происходило с момента ее начала работы. Фирма не создает каждый раз новую бухгалтерию когда ей требуется выдать зарплату. В свою очередь бухгалтерия является и «реестром», потому как в ней есть записи о каждом работнике фирмы.

Примечание:
«Реестр» нередко является «одиночкой», однако это не всегда должно быть именно так. Например мы можем заводить в бухгалтерии несколько журналов, в одном работники от «А» до «М», в другом от «Н» до «Я». Каждый такой журнал будет «реестром», но не «одиночкой», потому как журналов уже 2. Хотя нередко «реестр» служит именно для хранения «одиночек».
Сам паттерн «реестр» не являтся «порождающим паттерном» в полном смысле этого термина, однако его удобно рассматривать именно во взаимосвязи с ними.

Multiton (пул «одиночек»)

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

Object pool (пул объектов)

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

Factory (фабрика)

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

Builder (строитель)

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

Примечание:
Чтобы лучше понять разницу между фабрикой и строителем, можно использовать следующую метафору.
«Фабрика» — это автомат по продаже напитков, в нем уже есть всё готовое (или «осталось разогреть»), а вы только говорите что вам нужно (нажимаете кнопку). «Строитель» — это завод, который производит эти напитки и содержит в себе все сложные операции и может собирать сложные объекты из более простых (упаковка, этикетка, вода, ароматизаторы и т.п.) в зависимости от запроса.

Prototype (прототип)

Данный паттерн чем-то напоминает «фабрику», он также служит для создания объектов, однако с немного другим подходом. Представьте что у вас есть пустой пакет (из под сока), а вам нужен полный с апельсиновым соком. Вы «говорите» пакету «Хочу пакет апельсинового сока», он в свою очередь создает свою копию и заполняет ее соком, который вы попросили. Немного «сказочный пример», но в программировании часто так и бывает. В данном случае пустой пакет и является «прототипом», и в зависимости от того что вам требуется, он создает на своей основе требуемые вами объекты (пакеты сока).
Клонирование не обязательно должно производится на самом «пакете», это может быть и какой-то другой «объект», главное лишь что данный «прототип» позволяет получать его экземпляры.

Factory method (фабричный метод)

Данный паттерн довольно сложно объяснить в метафорах, но всё же попробую.
Ключевой сложностью объяснения данного паттерна является то, что это «метод», поэтому метафора метода будет использовано как действие, то есть например слово «Хочу!». Соответственно, паттерн описывает то, как должно выполнятся это «Хочу!».
Допустим ваша фабрика производит пакеты с разными соками. Теоретически мы можем на каждый вид сока делать свою производственную линию, но это не эффективно. Удобнее сделать одну линию по производству пакетов-основ, а разделение ввести только на этапе заливки сока, который мы можем определять просто по названию сока. Однако откуда взять название?
Для этого мы создаем основной отдел по производству пакетов-основ и предупреждаем все под-отделы, что они должны производить нужный пакет с соком про простому «Хочу!» (т.е. каждый под-отдел должен реализовать паттерн «фабричный метод»). Поэтому каждый под-отдел заведует только своим типом сока и реагирует на слово «Хочу!».
Таким образом если нам потребуется пакет апельсинового сока, то мы просто скажем отделу по производству апельсинового сока «Хочу!», а он в свою очередь скажет основному отделу по созданию пакетов сока, «Сделай ка свой обычный пакет и вот сок, который туда нужно залить».

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

Lazy initialization (отложенная инициализация)

Иногда требуется что-то иметь под рукой, на всякий случай, но не всегда хочется прилагать каждый раз усилия, чтобы это каждый раз получать/создавать. Для таких случаев используется паттерн «отложенная инициализация». Допустим вы работаете в бухгалтерии и для каждого сотрудника вы должны подготавливать «отчет о выплатах». Вы можете в начале каждого месяца делать этот отчет на всех сотрудников, но некоторые отчеты могут не понадобиться, и тогда скорее всего вы примените «отложенную инициализацию», то есть вы будете подготавливать этот отчет только тогда, когда он будет запрошен начальством (вышестоящим объектом), однако начальство по сути в каждый момент времени может сказать что у него этот отчет уже есть, однако готов он уже или нет, оно не знает и знать не должно. Как вы уже поняли, данный паттерн служит для оптимизации ресурсов.

Dependency injection (внедрение зависимости)

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

Service Locator (локатор служб)
автор: VolCh
«Локатор служб» является методом реализации «внедрения зависимости». Он возвращает разные типы объектов (компаний) в зависимости от кода инициализации. Пускай задача стоит доставить наш пакет сока, созданный строителем, фабрикой или ещё чем, куда захотел покупатель. Мы спрашиваем у локатора «дай нам службу доставки», и он нам соединяет на со службой доставки по номеру телефона, который директор ему дал (потому что получает откат они нам дают скидку как постоянным клиентам), а мы уже просим службу доставить сок по нужному адресу. Сегодня одна служба, а завтра может быть другая. Нам без разницы какая это конкретно служба, решение принимает директор и сообщает об этом локатору служб, нам важно знать лишь что они могут доставлять то, что мы им скажем туда, куда скажем, то есть службы реализуют интерфейс «Доставить <предмет> на <адрес>».

Структурирующие паттерны


Данные паттерны помогают внести порядок и научить разные объекты более правильно взаимодействовать друг с другом.

Adapter или wrapper (адаптер, обертка)

Данный паттерн полностью соответствует своему названию. Чтобы заставить работать «советскую» вилку через евро-розетку требуется переходник. Именно это и делает «адаптер», служит промежуточным объектом между двумя другими, которые не могут работать напрямую друг с другом.

Bridge (мост)

Представим ситуацию, когда вам требуется работать на разных автомобилях, однако садясь в новый автомобиль вам уже желательно знать как им управлять. Таким образом вы сталкиваетесь с паттерном «мост». С одной стороны вы имеете множество различных автомобилей (разные модели и марки), но среди все них есть общая абстракция (интерфейс) ввиде руля, педалей, коробки передач и так далее. Таким образом мы задаем как-бы правила изготовления автомобилей по которым мы можем создавать любые их виды, но за счет сохранения общих правил взаимодействия с ними, мы можем одинаково управлять каждым из них. «Мостом» в данном случае является пара двух «объектов»: конкретного автомобиля и правил взаимодействия с этим (и любым другим) автомобилем.

Composite (компоновщик)

Довольно интересный паттерн суть которого заключается в минимизации различий в управлении как группами объектов так и индивидуальными объектами. Для примера можно рассмотреть управление солдатами в строю. Существует строевой устав, который определяет как управлять строем и согласно этого устава абсолютно не важно кому отдается приказ (например «шагом марш») одному солдату или целому взводу. Соответственно в устав (если его в чистом виде считать паттерном «компоновщик») нельзя включить команду, которую может исполнить только один солдат, но не может исполнить группа, или наоборот.

Decorator (декоратор, оформитель)

Как понятно из названия, данный паттерн чаще всего используется для расширения исходного объекта до требуемого вида. Например мы условно можем считать «декоратором» человека с кистью и красной краской. Таким образом, какой бы объект (или определенный тип объектов) мы не передали в руки «декоратору», на выходе мы будем получать красные объекты.

Facade (фасад)

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

Front controller (единая точка входа)

Если проводить аналогии с реальными миром, то «единая точка входа» это то, через что вы сейчас читаете данную статью (например броузер). Она служит «единой точкой входа» для всего интернет пространства. То есть вы используете один интерфейс (броузер) для получения доступа к разным объектам большой системы (сайтам в интернете). Данный паттерн в целом сильно похож на «фасад».

Flyweight (приспособленец)

Самым лучшим примером (который я смог найти в реальной жизни) для метафорического сравнения паттерна «приспособленец» является театральная постановка. Представьте что нам требуется поставить пьесу. Однако по сценарию в этой пьесе задействованы несколько десятков людей, которые по своей сути выполняют одинаковые действия, например участвуют в массовках различных сцен в разные промежутки времени, но между ними всё же есть какие-то различия (например костюмы). Нам бы стоило огромных денег нанимать для каждой роли отдельного актера, поэтому мы используем паттерн «приспособленец». Мы создадим все нужные нам костюмы, но для каждой массовки будем переодевать небольшую группу актеров в требуемые для этой сцены костюмы. В результате мы имеем возможность ценой малых ресурсов создавать видимость управления большим количеством казалось бы разных объектов.

Proxy или surrogate (прокси, заместитель, суррогат)

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

Паттерны поведения


Эта группа паттернов позволяет структурировать подходы к обработке поведения и взаимодействия объектов. Проще говоря, как должны проходить процессы в которых существует несколько вариантов протекания событий.

Chain of responsibility (цепочка обязанностей)

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

Command или action (команда, действие)

Паттерн «команда» очень похож в реальной жизни на кнопки выключателей света в наших квартирах и домах. Каждый выключатель по своей сути делает одно простое действие — разъединяет или соединяет два провода, однако что стоит за этими проводами выключателю не известно. Что подключат, то и произойдет. Точно также действует и паттерн «команда». Он лишь определяет общие правила для объектов (устройств), в виде соединения двух проводов для выполнения команды, а что именно будет выполнено уже определяет само устройство (объект).
Таким образом мы можем включать одним типом выключателей как свет в комнате, так и пылесос.

Interpreter (интерпретатор)

Сравнить данный паттерн можно с тем, как вы закладываете часто используемые действия в сокращенный набор слов, чтобы сам «интерпретатор» потом превратил этот набор в более комплексные осмысленные действия. По сути каждый человек постоянно является «интерпретатором». Хотите провести жизненный эксперимент? Если из дома выходит кто-то из вашей семьи (муж, жена, ребенок), скажите ему простой набор слов «Литр молока, половинку белого, 200 грамм творога». По сути вы ничего особенного не сказали, лишь перечислили набор продуктов, однако велик шанс того, что «интерпретатор» транслирует это в команду «зайди по дороге в продуктовый магазин и купи следующее … и принеси это домой». Паттерн «интерпретатор» призван сократить часто исполняемые действия в более короткое их описание.

Iterator (итератор, указатель)

Все помнят школьное «на первый второй рассчитайся!»? Вот именно в этот момент шеренга вашего класса и являлась реализацией паттерна «итератор», хотя в программировании это конечно более функциональное понятие, но суть примерно та же. «Итератор» предоставляет правила доступа к списку каких-либо объектов независимо от того, что это за объекты. То есть не важно какой именно класс построен и из каких учеников, должны быть общие правила подсчета и обращения как каждому ученику по списку, вроде «13-ый, выйти из строя». Нередко паттерн «итератор» используется для доступа к «реестру». Ссылки, которые вы видите на многих сайтах для переходов по страницам, вроде «следующая», «предыдущая», «в начало» и т.п. по своей сути также являются доступом «итератору» который отвечает за страницы сайта.

Mediator (посредник)

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

Memento (хранитель)

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

Observer или Listener (наблюдатель, слушатель)

Очень распространенный паттерн в реальной жизни. Например если вы подписались на какую-либо email (или смс) рассылку, то ваш email (или номер сотового телефона) начинает реализовывать паттерн «наблюдатель». Как только вы подписываетесь на событие (например новая статья или сообщение), всем кто подписан на это событие (наблюдателям) будет выслано уведомление, а они уже в свою очередь могут выбрать как на это сообщение реагировать.

Blackboard (доска объявлений)

Данный паттерн служит для обеспечения взаимодействия между большим количеством объектов. Он является расширением паттерна «наблюдатель» и позволяет централизованно обслуживать как «наблюдателей», так и «создателей событий». В аналогии подпиской на email уведомления, это будет сам сайт подписки, который обслуживает множество подписчиков и тех, кто для них создает информацию (сообщения).

Servant (слуга)

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

State (состояние)

В реальной жизни каждый человек может прибывать в разных состояниях. Точно также порой требуется чтобы объекты в программе вели себя по разному в зависимости от каких-либо их внутренних состояний. По аналогии с реальной жизнью можно например привести следующий пример:
Если вы устали то на фразу «Сходи в магазин» вы будете выдавать «Не пойду», если вам нужно сходить в магазин (за пивом?), то на «Сходи в магазин» вы будете выдавать «Уже бегу!». Человек (объект) один и тот же, а поведение разное. Именно для этих целей и используют паттерн «состояние».

Strategy (стратегия)

Используется для выбора различных путей получения результата. Вспомним пример с получением прав. Человек, который будет реализовывать паттерн «стратегия» будет действовать следующим образом: вы говорите ему «Хочу права, денег мало» в ответ вы получите права через длительное время и с большой тратой ресурсов. Если вы скажите ему «Хочу права, денег много», то вы получите права очень быстро. Что именно делал этот человек вы понятия не имеете, но вы задаете начальные условия, а как себя вести уже решает он сам (сам выбирает стратегию).
Соответственно внутри «стратегии» хранятся различные способы поведения, и чтобы выбрать, ему нужны определенные параметры, в данном случае это объем денежных средств. Как устроена сама «стратегия» и какие алгоритмы внутри нее вам собственно знать и требуется.

Specification (спецификация, определение)

Паттерн спецификации позволяет описывать подходит ли данный объект нам на основе каких-либо критериев. Например мы имеем несколько контейнеров для погрузки на судно. Однако чтобы определить грузить контейнер или нет на определенное судно, нам нужно выбрать метод как это определять. Реализация такого метода и является паттерном «спецификация». В самом простом случае для каждого контейнера мы можем определить в паттерне «спецификация» совпадает ли страна назначения корабля со страной назначения контейнера. Соответственно мы один раз вводим правило «сравнить две страны назначения» и применяем его ко всем контейнерам для проверки.

Subsumption (категоризация)

Данный паттерн является прямым последователем паттерна «спецификация». Он позволяет распределять объекты по категориям на основе каких-либо условий. Соответственно по аналогии с примером кораблей и контейнеров, это категоризация по тому, какие контейнеры в какие страны направляются.

Visitor (посетитель)

Данный паттерн можно сравнить с прохождением обследования в больнице. Однако «посетителем» в терминах паттернов здесь будут сами врачи. Чтобы было понятнее: у нас есть больной которого требуется обследовать и полечить, но так как за разные обследования отвечают разные врачи, то мы просто присылаем к больному врачей в качестве «посетителей». Правило взаимодействия для больного очень простое «пригласите врача (посетителя) чтобы он сделал свою работу», а врач («посетитель») приходит, обследует и делает всё необходимое. Таким образом следуя простым правилам можно использовать врачей для разных больных по одним и тем же алгоритмам. Как уже было сказано, паттерном «посетитель» в данном случае является врач, который может одинаково обслуживать разные объекты (больных) если его позовут.

Single-serving visitor (одноразовый посетитель)

Является частным случаем использования паттерна «посетитель». Если в случае с обычным «посетителем» у нас есть врач которого мы можем отправить к разным больным (и при желании по несколько раз), то в данном паттерне можно привести аналогию, что мы нанимаем врача, отправляем его к одному больному и после обследования сразу увольняем.

Hierarchical visitor (иерархический посетитель)

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

Заключение


Вот собственно и все основные паттерны которые я хотел описать в данной статье. Как вы видите, все они имеют очень много общего с реальной жизнью и позволяют делать код насколько же простым для чтения и понимания, как и то, что мы видим в реальной жизни. Программирование — это не «инопланетный язык» (а сами программисты вполне себе земные существа), это просто другая форма взаимодействия и описания мира существующего.
О том же как непосредственно применять данные паттерны на практике написано большое количество статей и книг в интернете, их очень легко найти. Однако надеюсь сведения, приведенные в данной статье позволят вам быстро сориентироваться, если вдруг «на горизонте кода» появится знакомый паттерн.

Надеюсь вы нашли данный материал полезным для себя и благодарю за внимание.

UPD:
дополнительные материалы, которые также могут быть интересны по данной теме:
От пользователя Markel
www.cours.polymtl.ca/inf3700/divers/nonSoftwareExample/patexamples.html

От пользователя NikoM
Книга «Фримен, Фримен, Сьерра: Паттерны проектирования»
Евгений @evgenyl
карма
176,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (86)

  • +12
    Реквестирую такую же статью про антипаттерны.
    • +5
      Не уверен что тут много чего можно сказать помимо вот этой статьи. Если наберется достаточно материала и будет время — сделаю.
      • 0
        Там расписано про антипаттерны, но хотелось бы прочитать про антипаттерны на примерах из реального мира!
  • +2
    Прекрасный материал! Нет, на самом деле. Конечно нужна ещё оценка не программиста, уже давно использующего часть паттернов, а того, кто только этому учится, но всё же великолепно.
    Однозначно в мемориз, спасибо.
    • +3
      И оценка программиста не помешает. Некоторые описания не совсем точны, state: у каждого объекта есть состояние, из-за которого объект может менять свое поведение, суть же паттерна в выделение состояния в отдельный объект; chain of responsibility: пример не отражает суть. Stragey: «вы задаете начальные условия, а как себя вести уже решает он сам (сам выбирает стратегию).» вообще то вы сами выбираете алгоритм (стратегию). Другие слишком странные: command: " включать одним выключателем как свет в комнате, так и пылесос." это вообще как возможно в реальной жизни?.. Есть же проще примеры, тот же расчет зарплаты. Decorator: «можно считать декоратором человека с кистью и красной краской. » ?! Лучше уж использовать банальный пример с пиццей и индигриентами. Proxy: это же заместитель, можно было пример с заместителями и использовать :).
      • +1
        упс, " Есть же проще примеры, тот же расчет зарплаты" относилось к Strategy
      • +1
        1. State
        Во многих метафорах не говорится о том что выделять в отдельных объект, потому что это уже более предметная область. Так то ведь можно такой state даже в синглтон загнать. Главная цель была именно понять что паттерн делает, а именно меняет состояние объекта.
        2. Chain of responsibility
        К сожалению не смог найти метафору попроще. Возможно предложите вы — поправлю.
        3. Strategy
        Возьмите классический пример — валидатор. Вы используете только метод validate, а на основе параметров, вроде ('not_empty') паттерн сам производит действия, но метод который вы используете извне, везде только validate. Так что как и было сказано в описании — задаются лишь параметры, а стратегию (конкретный метод) реализует уже сам паттерн.
        4. Command
        Возможно некорректно выразился. У вас в пылесосе, по своей сути, точно такой же механизм как в выключателе света — соединитель/разъединитель двух проводков. Имелось ввиду именно это.
        Какой именно пример вы имеете ввиду насчет зарплаты? Опять же если есть что-то попроще и понятнее — исправить статью не сложно.
        5. Decorator.
        А чем лучше пример с пиццей? Просто не совсем могу понять ваш ход мыслей.
        6. Proxy
        Чем пример с заместителем будет лучше? Полагаю если вы попробуете описать тот же принцип просто применив слово заместитель, то ничего не изменится. Могу конечно ошибаться, жду пример если это не так.
      • +1
        Попробовал другую метафору для Chain of responsibility, возможно так будет лучше.
  • –2
    Статья хорошая но слишком много текста) Разбавили бы его иллюстрации)
    • +14
      Да-да. Вот я «Войну и мир» в свое время поэтому же не стал читать.
      • +2
        Я тоже. Я лет через 15 после прохождения её в школе прочитал и понял, что и без картинок нормально :)
        • +1
          Любая статья должна быть интересна для прочтения а не быть просто быть просто данными
    • +3
      Алиса?..
  • +1
    Hierarchical visitor — а не лучше ли сравнить его с кем-то, кто хочет получить какую-либо лицензию? ))) В современных реалиях — гораздо более гештальтный пример
    • +1
      Тогда бы получилось не совсем корректно. Hierarchical visitor должен пройти по каждому элементу один раз, а у нас сейчас с лицензиями человека могут «футболить» по отдельным кабинетам несколько раз )
      • +1
        Тогда этот пример можно в антипаттерны
  • +3
    Книга Фримена «Паттерны проектирования» раскрывает тему паттернов на наглядных метафорах, обильно проиллюстрированных.
    Советую эту книгу для плавного освоения паттернов.
    Книга занимает 11 место в недавно освещенном топе habrahabr.ru/blogs/development/135897/ оригинального голосования stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read
  • +2
    Мне не нравится аналогия про синглтон. Синглтон — это как будто вместо того, чтобы провести в каждую квартиру по телефонному проводу (не рассказывая, куда он идет — на ближайшую телефонную станцию или на персональный спутниковый передатчик) мы говорим каждому жителю: «Что-то нам лень до тебя провод тянуть, пойди сам на ул. Ленина, дом 1, и воткни свой провод там куда-нибудь». И когда, например, станция на улице Ленина перестает справляться с количеством абонентов — у всех возникают проблемы.
    • +1
      Вы ушли в пример параллельного доступа к объекту (Concurrency control). Это совершенно не связано с паттерном синглтона.
      • +1
        Суть не в параллельном доступе, а в том что когда по каким-то причинам нужно перекинуть часть абонентов на новую станцию, то возникают проблемы — от каждого нужно новый провод тянуть.
    • +1
      Я имел в виду, что клиенты синглтона, помимо знания его интерфейса, знают, что это синглтон, знают, где его взять, и сами решают, когда это сделать. Иногда бывает, что эти знания — лишние для них, и могут обернуться проблемами в будущем.
  • +5
    Даёшь «Математический анализ в картинках»!
    • +2
    • 0
      за весь анализ не скажу, но вот читаю сейчас книжку «Без паники: Цифровая обработка сигналов». там очень много матанализа преподнесено наглядными средствами.
  • +1
    en.wikipedia.org/wiki/Service_locator_pattern

    Service Locator паттерн.

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

    Вот Service Locator — это такая конструкция, которая выдает по нужному вам запросу некую сущность, необходимую для решения задачи.

    По сути дела, это один из способов инкапсулировать информацию о том, как создавать тот или иной объект, внутрь специального объекта. Очень клевая штука. :)
    • +1
      Не совсем пока понял в чем его разница с паттерном Builder?
      Объясните пожалуйста, если не сложно.
      • +1
        В моем понимании Builder есть порождающий паттерн,
        в то время как Service Locator (как я его обычно реализую) — это синглтон + реестр, который может как порождать новые объекты при обращении к нему, так и возвращать уже инстанцированные.

        псевдокод
        audioObject = ServiceLocator::instance()->getAudioObject() // тут может быть скрыта фабрика

        videoObject = ServiceLocator::instance()->getVideoObject() // а тут может быть некий объект, однажды инстанцированный и затем возвращаемый в той самой инстанции — читай синглтон

        и т.д.
        • +1
          и тут дело в глобальности. ServiceLocator это такая супер-пупер мегаштука, всеобъемлющая.

          но кто-то может понимать иначе этот паттерн, не отрицаю :)
      • +2
        Builder как бы собирает объекты из кусочков, но это объекты одного класса, просто для разных экземпляров задаются разные свойства. Всегда будут пакет, изображение, сок и т. п., просто они могут быть разными, но всё равно это будет пакет с соком.

        Service Locator же возвращает разные типы объектов в зависимости от кода инициализации. Пускай задача стоит доставить наш пакет сока, созданный строителем, фабрикой или ещё чем, куда захотел покупатель. Мы спрашиваем у локатора «дай нам службу доставки», и он нам соединяет на со службой доставки по номеру телефона, который директор ему дал (потому что получает откат они нам дают скидку как постоянным клиентам), а мы уже просим службу доставить сок по нужному адресу. Сегодня одна служба, а завтра может быть другая. Нам без разница, решает директор и сообщает об этом локатору служб, нам важно знать лишь что они могут доставлять то, что мы им скажем туда, куда скажем, то есть службы реализуют интерфейс «Доставить <предмет> на <адрес>».

        По сути локатор служб это реестр, куда заносятся конкретные экземпляры различных служб, нужных приложению. То есть реестр со специфическим назначением. Основное назначение — реализация DI/IoC. Очень близок к паттерну IoC Container, настолько, что их часто путают, по мне так разница в том, что локатор оперирует экземплярами служб, созданных не им, а контейнер сам создаёт при необходимости. Но может ошибаюсь.
        • +2
          Благодарю. Расширил статью вашим комментарием и заодно добавил и dependency injection.
          • 0
            Неточность небольшая. Service Locator не является расширением DI, это один из методов его реализации.
            • 0
              Ох уж эти термины ) Поправил.
      • +1
        Мне тоже не понравилось описание этого паттерна. По сути автор сказал — это фабрика, только где-то в недрах ее происходит собирание реальных объектов из частей.

        А «в недрах» нас и не должно интересовать. интересная ссылка

        Builder применяется для создания сложных объектов из отдельных частей (обычно родственных, но не обязательно). Фабрика — целого сразу. Пример Buildr-а:

        interface ITreeBuilder
        {
          IEntity CreateSheet();
          IEntity CreateTree(IEnumerable<IEntity> children);
          IEntity CreateRoot(IEntity part)
        }
        

        Фабрика:

        itnerface ITreeFactory
        {
          IEntity CreateTree();
        }
        
  • +7
    На мой взгляд у вас получилась не очень хорошая статья.
    Вы излишне многословны, большинство метафор витиеваты и туманы, некоторые же и вовсе не верны, при этом у вас сохраняется зависимость определения одних метафор от других.

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

    Помимо упомянутой в комментариях выше книге, есть так же статья 1997 года — www.cours.polymtl.ca/inf3700/divers/nonSoftwareExample/patexamples.html, рекомендую.
    • +1
      Я всячески приветствую критику, однако всё же она должна быть конструктивной, чтобы не быть голословной.
      Если вам есть что предложить/поправить/внести своё — предлагайте, давайте вместе делать материал более качественным и полезным для читателей. На мой взгляд это куда более грамотный подход нежели просто отписаться «у вас всё не так, как думаю я».

      Как я уже говорил, это лишь небольшие метафоры, если снабдить это иллюстрациями, добавить разбавить слегка кодом и примерами — то смысл в этой статье теряется напрочь, потому что получится книга, которую указали вот в этом комментарии. Еще раз замечу — это не статья чтобы изучать паттерны, это лишь небольшое ознакомление с тем, что они иногда описывают в реальном мире.
      • НЛО прилетело и опубликовало эту надпись здесь
        • +2
          А мой опыт говорит, что очень редко бывают методы, которые нужны всем типам объектов, ну разве что конструкторы, но они у всех разные. А если всё же нужны, то эти методы реализуются в базовом классе или определяются абстрактно/в интерфейсах, а затем реализуются в наследниках (сериализация хороший пример).

          Один из столпов ООП — инкапуляция, только методы объектов должны знать об их внутренних данных, о том, что в мешке лежит. А как вы предлагаете реализовать ту же сериализацию? Функция, которая принимает произвольный объект, определяет его тип и в куче if'ов или case'ов выбирает как именно этот объект сериализовывать?
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              >Я считаю, что достаточно иметь один тип объектов, который имеет все методы.

              God-objects? Что делать, если вызывается метод, для которого в данном «мешке» данных нет? NPE?

              >— Зачем?

              Возможность сменить реализацию, не меняя интерфейс. Невозможность рассогласовать состояние объекта незметно для самого объекта.

              >Без всякой кучи if-ов.

              Хотелось бы взглянуть. Как гуглить?

              • НЛО прилетело и опубликовало эту надпись здесь
                • +2
                  >то же, что и SQL запрос «делает», когда в базе ищут то, чего там нету.

                  Theris no table...? Понятно…

                  >Объекты как ассоциативные массивы

                  Больше вопросов нет…
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • +3
                      Не все свойства объектов надо сериализовывать, циклом по всем свойствам пройтись — перебор. Уж поверьте на слово, пока вы С++ занимались, я с ассоциативными массивами работал, эмулировал с их помощью объекты из C++. Прямо как у вас «все объекты одного типа, но с разным набором данных».

                      Я понимаю ваш восторг от возможности динамически задавать новые свойства после C++, но вот один из самых популярных языков со слабой динамической типизацией всё больше идёт к сильной статической. Не от хорошей жизни.
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • +2
                          Уже скучно…
            • +1
              «А как вы предлагаете реализовать ту же сериализацию?»
              Меня этот вопрос, как старого С++ника, то же ставил в тупик. Но пришлось переучиваться… на JS, там это элементарно. Без всякой кучи if-ов.

              элементарно? только не говорите о JSON.
              • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  ну, неужели Вам, цитата
                  как старого С++ника

                  кажется JSON чем-то магическим?
        • +3
          «любой часто используемый метод нужен всем объектам»

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

          Если таковых много, значит либо вы строите сереализатор, либо недопроектировали свое решение.
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              >Вот мяукать мячик не может.

              А что будет, если написать Мячик->Мяукни?
              • НЛО прилетело и опубликовало эту надпись здесь
                • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              «Это теорема Гёделя о неполноте.»

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

              Программа не должна 100% верно описывать реальный мир (и 1% не обязателен). Достаточно абстрактной модели, удовлетворяющей условиям, описанным в техзадании. Если в нем нет того, что кошка по вызову деструктора распадается на атомы, значит она может и не распадаться (это ее личное дело, если конечно это не вызовет утечку ресурсов).
              • НЛО прилетело и опубликовало эту надпись здесь
                • НЛО прилетело и опубликовало эту надпись здесь
              • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  Интерестно, как вы будете складывать огурцы и помидоры. А самое главное, что у вас после этого получится.

                  Все операции в математике определены на каком-то конкретном типе объектов. Сложение натуральных чисел — для яблок, или помидоров, или сущностей. Помимо натуральных есть комплексные числа, вероятностные величины, вектора и другие математические абстракции.

                  Конкретная «формальная арифметика» определяет сущности и операции над ними. Не одну и не две, а столько сколько нужно. Так для векторов определено умножение на число, скалярное умножение векторов, векторное умножение векторов. Маразм ясен.
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Вот только вы складываете всё-равно числа. Операция сложения в математике существует только для чисел. Вы ведь не говорите, что сложив один огурец и один огурец получилось что-то другое. Вы складываете один+один и приставляете справа «огурец»
                  • 0
                    Огурцы и помидоры можно складывать в свободной абелевой группе, порождённой огурцом и помидором. То, что этого сделать нельзя,- одна из самых популярных в народе вредных эвристик.
              • НЛО прилетело и опубликовало эту надпись здесь
            • +1
              Вот не рассуждая о высоком, вы говорите что все объекты содержатся в виде данных внутри памяти и потому могут быть запросто сериализованы. Во-первых что делать с большим графом объектов? Объекты могут и не в памяти быть, а в БД. Отправим один объект по сети а он всю базу утянет? А что если внутри объектов есть не то что секретная, просто лишняя информация для конкретного получателя? А получателей много? Короче не мелите чепуху, а почитайте про паттерн DTO.
              • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  И появляется куча ифов — это тут нужно сериализовывать, это тут не нужно…
                  • НЛО прилетело и опубликовало эту надпись здесь
        • +2
          Если следовать вашей логике, сохраняя поток «метафор», то в реальной жизни в принципе не нужны коробки разных размеров, можно взять одну коробку одного размера, приделать ей моторчик, колеса и ручку сверху, а потом этой коробкой заменять автомобиль, портфель, грузовик (просто побольше коробок взять) и дом. Ведь универсально же.

          Не буду втягиваться диалог корректности этой метафоры, но в данном случае я скорее буду полагаться на опыт большого числа инженеров.
          То что ваши задачи позволяли пока вам универсально решать всё при помощи «коробки», еще не значит что вам не встретятся задачи, где ее будет либо недостаточно, либо она будет избыточна.
          • 0
            «можно взять одну коробку одного размера, приделать ей моторчик ...»

            Главное не забывать при этом говорить «тр-тр-тр...»
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              Со своей стороны я могу только пожелать вам успехов в реализации подобного подхода.
              Уверен, что если вы изобрели новый прекрасный метод разработки решающий все те проблемы для борьбы с которыми и используют ООП подход, то весь цивилизованный и разумный мир последует за вами.
              • НЛО прилетело и опубликовало эту надпись здесь
            • +1
              Откуда функция сериализации знает какие свойства объекта надо сериализировать, а какие нет? Сериализация, например, соединения с БД для передачи по сети или сохранения на диск бессмысленна и, скорее всего, приведёт к ошибке при попытке сохранить десриализованный объект в базе. Он-то будет думать, что у него есть соединение с БД, а том, что он на другой хост попал или прошло полгода как соединение закрылось он и не догадается.
              • НЛО прилетело и опубликовало эту надпись здесь
              • НЛО прилетело и опубликовало эту надпись здесь
              • НЛО прилетело и опубликовало эту надпись здесь
                • +1
                  Не о вложенных полях речь идёт, а о значениях имеющих смысл только в данном контексте, после десериализации они в лучшем случае будут бесполезны, а худшем вызовут, например, access vilation и крах приложения, а то и создадут уязвимость.
                  • НЛО прилетело и опубликовало эту надпись здесь
        • +1
          А методы надо писать таким образом, что бы они работали с любым типом объектов (портфелей), SQL же работает, правда он может вернуть пустое множество, но всегда находит в портфеле не гнилые красные фасолины.
          ответить

          sql отвечает на вопрос ЧТО, а не на вопрос КАК
          • НЛО прилетело и опубликовало эту надпись здесь
            • +2
              Правильно, «что делать?», а «как делать» он сам выбирает.
            • +1
              реляционные языки — языки программирования, оперирующие с данными как со множествами, применяя к ним основные операции теории множеств.
              типичным примером является SQL. SQL основывается на теории исчисления кортежей, которое является в свою очередь направлением реляционного исчисления, а в основой оного лежит теория предикатов первого уровня.
              все-таки вы не сможете с помощью них покрывать многие аспекты алгоритмического программирования.
  • –1
    Браво!
  • +1
    Устал читать.
  • +1
    Вообще статья хорошая, но вот с метафорами беда…
  • 0
    По поводу «Стратегии» вы написали: Как устроена сама «стратегия» и какие алгоритмы внутри нее вам собственно знать и требуется.
    Вы здесь частицу «не» не пропустили? «знать и не требуется».
    Если ошибки нет, то поспорю с вами.

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