Решил сделать маленькую шпаргалку. Для тех, кто не в курсе, всё это хорошо расписано в двух прекрасных книгах:
- Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидесс "Приемы объектно ориентированного проектирования. Паттерны проектирования"
- М. Фаулер "Архитектура корпоративных программных приложений"
Эта статья будет постепенно дописываться, так что если вам нравится формат изложения, а он очень сокращенный, то смело добавляйте ссылку в закладки. Итак, начнём с Фаулеровских базовых.
Шлюз (Gateway)
Это объект, инкапсулирующий доступ к внешней схеме или источнику данных. Смысл применения шлюза - обеспечить более удобный интерфейс для доступа к внешней системе. Весь специализированный код API помещается в класс-шлюз, интерфейс которого не отличается от интерфейса обычного объекта. Интерфейс шлюза может даже представлять собой точную копию инкапсулируемого интерфейса. Шлюз разрабатывается клиентом для использования внешнего приложения в отличие от интерфейса (Facade). В отличие от адаптера (Adapter) интерфейс шлюза не нужно подстраивать под какой-либо существующий интерфейс. И в отличие от медиатора (Mediator) шлюз разделяет только два объекта, причем источник данных не знает о существовании шлюза. Одно из главных назначений шлюза - обеспечить основу для реализации фиктивных служб (Service Stub).
Преобразователь (Mapper)
Это объект, устанавливающий взаимодействие между двумя независимыми объектами. Основное назначение преобразователя - отделить друг от друга различные части программной системы. Преобразователь представляет собой некий "изоляционный" слой, проложенный между двумя подсистемами. Он управляет взаимодействием подсистем, зачастую лишь перемещая данные из одного слоя в другой. Разделяемые подсистемы не зависят друг от друга, поэтому ни одна из них не может напрямую вызвать запуск преобразователя. Этот вызова обычно возлагают на третью подсистему, либо реализуют преобразователь в виде типового решения обозреватель (Observer). В отличие от медиатора (Mediator), объекты, разделенные преобразователем, не знают о наличии последнего.
Супертип слоя (Layer Supertype)
Это тип, выполняющий роль суперкласса для всех классов своего слоя. Супертип слоя используется тогда, когда все объекты соответствующего слоя имеют некоторые общие свойства или поведение. Чтобы избежать повторения, всё общее поведение можно вынести в супертип слоя. Если в рассматриваемом приложении находятся объекты нескольких различных типов, может понадобиться создать несколько супертипов слоя.
Отдельный интерфейс (Separated Interface)
Этот шаблон предполагает размещение интерфейса и его реализации в разных пакетах. Отдельный интерфейс используется тогда, когда нужно уничтожить зависимость между двумя подсистемами, обеспечить поддержку нескольких независимых реализаций или осуществить вызовы методов, противоречащие общей структуре зависимостей. Если реализация имеет только одного клиента или все клиенты реализации находятся в одном пакете, интерфейс может быть размещен прямо в пакете клиента. Если имеется несколько клиентских пакетов или определение интерфейса не входит в обязанности разработчиков клиентского пакета, то интерфейс помещается в пакет, отдельный от клиентских.
Реестр (Registry)
Это "глобальный" (в действительности не обязательно таковой) объект, который используется другими объектами для поиска общих объектов или служб. В качестве интерфейса реестров рекомендуется применять статические методы. Обычно глобальный по отношению к процессу реестр реализуется в виде единственного элемента (Singleton). Рекомендуется использовать явные классы с явными методами, которые позволяют проследить, какие ключи применяются для поиска объекта. Явный класс позволяет сохранить типовую безопасность в статически типизированных языках, а также инкапсулировать структуру реестра, чтобы при последующем росте системы её можно было вынести в нужный класс или слой. Разные области видимости требуют различных реализаций, однако интерфейс при этом может быть общий.
Объект-значение (Value Object)
Также может иметься в виду объект переноса данных (Data Transfer Object)
Это небольшие простые объекты наподобие денежных значений или диапазонов дан, равенство которых не основано на равенстве идентификаторов. Применяется для моделирования тех сущностей, равенство которых определяется по значениям полей. Эти объекты делаются неизменяемыми, т.е. чтобы после создания объекта значения его полей не изменялись. Такие объекты можно передавать в виде значения, а не в виде ссылки на объект. Два объекта считаются равными если равны значения их полей. Обычно изменение полей объекта подразумевает полную замену старого объекта новым. Как правило для хранения объектов-значений применяют внедренные значения (Embedded Value) или крупные сериализованные объекты (Serialized LOB).
Деньги (Money)
Этот шаблон представляет денежное значение. Денежные величины представляют собой объекты-значения (Value Object), поэтому и хранить их желательно в виде внедренного значения (Embedded Value). Величина денежной суммы обычно представляется значением целочисленного типа или же действительным значением с фиксированным количеством десятичных знаков. Это препятствует созданию некорректных значений, обусловленных работой с числами с плавающей запятой. Таким образом инкапсулируется обработка округления, что позволяет избежать большинства ошибок округления до фиксированного количества десятичных знаков. Еще одним преимуществом является возможность одновременного выполнения вычислений в нескольких единицах измерения (валютах).
Частный случай (Special Case)
Производный класс, описывающий поведение объекта в особых ситуациях. Суть заключается в возврате специального объекта с тем интерфейсом, который ожидает увидеть вызывающий метод. Следует применять для описания повторяющегося поведения, связанного с обработкой значений NULL или других особых ситуаций после выполнения соответствующих проверок на их наличие. Обычно, но не всегда, для реализации можно воспользоваться типовым решением приспособленец (Flyweight). Нередки ситуации, когда при переопределении методов в частном случае последний возвращает еще один частный случай.
Дополнительный модуль (Plugin)
Связывает классы во время настройки, а не во время компиляции приложения. Это типовое решение применяется тогда, когда одно и то же поведение может иметь несколько реализаций в зависимости от выбранной исполняющей среды. Также дополнительный модуль обеспечивает централизованную настройку приложения во время выполнения. Обычно определяется Отдельный интерфейс (Separated Interface), а затем пишется объект-фабрика (Factory Method) дополнительного модуля, который будет выполнять отображение интерфейса на необходимые реализации. Также дополнительный модуль комбинируется с типовым решением единственный элемент (Singleton). Применение дополнительного модуля наиболее предпочтительно в языках, поддерживающих отражение, поскольку объект-фабрика может создавать объекты реализаций без необходимости устанавливать зависимости во время компиляции.
Фиктивная служба (Service Stub)
Синонимы: объект-имитатор (Mock Object)
Устраняет зависимости приложения от труднодоступных или проблемных служб на время тестирования. Используется тогда, когда зависимость приложения от конкретной внешней службы значительно затрудняет процесс разработки и тестирования. Обычно устанавливают доступ к внешней службе с помощью шлюза (Gateway). Последний следует реализовать не как класс, а в виде отдельного интерфейса (Separated Interface), имеющего одну реализацию для обращения к реальной службе и, как минимум, еще одну реализацию, являющуюся фиктивной службой. Нужная реализация шлюза должна загружаться с помощью дополнительного модуля (Plugin). Сама реализация фиктивной службы должна быть достаточно простой, чтобы ускорить процесс разработки.
Множество записей (Record Set)
Представление табличных данных в оперативной памяти. Структура множества записей в точности копирует результат выполнения запроса к базе данных, однако может быть сгенерирована и использована другими частями системы. Обычно множество записей можно легко отсоединить от источника данных, что позволяет передавать множество записей по сети, не беспокоясь о наличии соединения с базой данных. Если множество записей легко поддается сериализации, оно может выступать в качестве объекта переноса данных (Data Transfer Object). Зачастую множество записей реализуют в виде единицы работы (Unit of Work), благодаря чему все изменения множества записей могут быть внесены в отсоединенном режиме и затем зафиксированы в базе данных. Источник данных может воспользоваться оптимистической автономной блокировкой (Optimistic Offline Lock), чтобы проверить параллельные транзакции на наличие конфликтов и в случае отсутствия последних записать все внесенные изменения в базу данных. Большинство реализаций множества записей используют неявный интерфейс (implicit interface), хотя он и обладает такими существенными недостатками как рассредоточение кода поиска и утрата компилятором информации о типах. Настоящая ценность множества записей раскрывается в тех средах, которые могут применять его в качестве стандартного способа манипулирования данными. При наличии подобных сред для организации логики домена следует применять модуль таблицы (Table Module).
Фабричный метод (Factory Method)
Синонимы: Виртуальный конструктор (Virtual Constructor)
Определяет интерфейс для создания объектов, при этом выбранный класс инстанцируется подклассами, то есть оставляет подклассам решение о том, какой класс инстанцировать. Фабричный метод позволяет классу делегировать инстанцирование подклассам. Фабричные методы избавляют проектировщика встраивать в код зависящие от приложения классы.
Абстрактная фабрика (Abstract Factory)
Синонимы: Инструментарий (Kit)
Предоставляет интерфейс для создания семейств, связанных между собой, или независимых объектов, конкретные классы которых неизвестны.
Одиночка (Singleton)
Гарантирует, что некоторый класс может иметь только один экземпляр, и предоставляет глобальную точку доступа к нему.
Прототип (Prototype)
Описывает виды создаваемых объектов с помощью прототипа и создаёт новые объекты путём его копирования. У прототипа те же самые результаты, что у абстрактной фабрики (Abstract Factory) и строителя (Builder): он скрывает от клиента конкретные классы продуктов, уменьшая тем самым число известных клиенту имён. Кроме того все эти паттерны позволяют клиенту работать со специфичными для приложения классами без модификаций.
Строитель (Builder)
Отделяет конструирование сложного объекта от его представления, позволяя использовать один и тот же процесс конструирования для создания различных представлений.
Адаптер (Adapter)
Синонимы: Обертка (Wrapper).
Преобразует интерфейс класса в некоторый другой интерфейс, ожидаемый клиентами. Обеспечивает совместную работу классов, которая была бы невозможна без данного паттерна из-за несовместимости интерфейсов.
Декоратор (Decorator)
Также может иметься в виду обертка (Wrapper).
Динамически возлагает на объект новые функции. Декораторы применяются для расширения имеющейся функциональности и являются гибкой альтернативой порождению подклассов.
Заместитель (Proxy)
Синонимы: суррогат (Surrogate)
Подменяет другой объект для контроля доступа к нему, то есть является суррогатом другого объекта. Заместитель применим во всех случаях, когда возникает необходимость сослаться на объект более изощренно, чем это возможно, если использовать простой указатель. С помощью этого паттерна при доступе к объекту вводится дополнительный уровень косвенности.
Компоновщик (Composite)
Группирует объекты в древовидные структуры для представления иерархии типа "часть-целое". Позволяет клиентам работать с единичными объектами так же, как с группами объектов.
Мост (Bridge)
Синонимы: Описатель Тело (Handle Body)
Отделяет абстракцию от реализации, благодаря чему появляется возможность независимо изменять то и другое.
Приспособленец (Flyweight)
Использует разделение для эффективной поддержки большого числа мелких объектов. При использовании приспособленцев не исключены затраты на передачу, поиск или вычисление внутреннего состояния, особенно если раньше оно хранилось как внутреннее. Однако такие расходы с лихвой компенсируются экономией памяти за счет разделения объектов-приспособленцев.
Фасад (Facade)
Предоставляет унифицированный интерфейс к множеству интерфейсов в некоторой подсистеме. Определяет интерфейс более высокого уровня, облегчающий работу с подсистемой. Клиенты общаются с подсистемой, посылая запросы фасаду. Он переадресует их подходящим объектам внутри подсистемы. Хотя основную работу выполняют именно объекты подсистемы, фасаду, возможно, придется преобразовать свой интерфейс в интерфейсы подсистемы. Клиенты, пользующиеся фасадом, не имеют прямого доступа к объектам подсистемы.
Интерпретатор (Interpreter)
Для заданного языка определяет представление его грамматики, а также интерпретатор предложений языка, использующий это представление.
Шаблонный метод (Template Method)
Определяет скелет алгоритма, перекладывая ответственность за некоторые его шаги на подклассы. Позволяет подклассам переопределять шаги алгоритма, не меняя его общей структуры. Шаблонные методы - один из фундаментальных приемов повторного использования кода. Они особенно важны в библиотеках классов, поскольку предоставляют возможность вынести общее поведение в библиотечные классы. Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой кино-империи фразу "Не звоните нам, мы сами позвоним". В данном случае это означает, что родительский класс вызывает операции подкласса, а не наоборот.
Итератор (Iterator)
Синонимы: Курсор (Cursor)
Дает возможность последовательно обойти все элементы составного объекта, не раскрывая его внутреннего представления.
Команда (Command)
Синонимы: Действие (Action), Сценарий транзакции (Transaction Script)
Инкапсулирует запрос в виде объекта, позволяя тем самым параметризовывать клиентов типом запроса, устанавливать очередность запросов, протоколировать их и поддерживать отмену выполнения операций.
Наблюдатель (Observer)
Синонимы: Подчиненные (Dependents), Издатель Подписчик (Publish Subscribe)
Определяет между объектами зависимость типа один-ко-многим, так что при изменении состояния одного объекта все зависящие от него получают извещение и автоматически обновляются. Шаблон позволяет изменять субъекты и наблюдатели независимо друг от друга. Субъекты разрешается повторно использовать без участия наблюдателей и наоборот. Это дает возможность добавлять новых наблюдателей без модификации субъекта или других наблюдателей.
Посетитель (Visitor)
Представляет операцию, которую надо выполнить над элементами объекта. Позволяет определить новую операцию, не меняя классы элементов, к которым он применяется.
Посредник (Mediator)
Определяет объект, в котором инкапсулировано знание о том, как взаимодействуют объекты из некоторого множества. Способствует уменьшению числа связей между объектами, позволяя им работать без явных ссылок друг на друга. Это, в свою очередь, дает возможность независимо изменять схему взаимодействия. Коллеги посылают запросы посреднику и получают запросы от него. Посредник реализует кооперативное поведение путем переадресации каждого запроса подходящему коллеге (или нескольким коллегам).
Состояние (State)
Позволяет объекту варьировать свое поведение при изменении внутреннего состояния. При этом создается впечатление, что поменялся класс объекта.
Стратегия (Strategy)
Синонимы: Политика (Policy)
Определяет семейство алгоритмов, инкапсулируя их все и позволяя подставлять один вместо другого. Можно менять алгоритм независимо от клиента, который им пользуется.
Хранитель (Memento)
Синонимы: Лексема (Token)
Позволяет, не нарушая инкапсуляции, получить и сохранить во внешней памяти внутреннее состояние объекта, чтобы позже объект можно было восстановить точно в таком же состоянии.
Цепочка обязанностей (Chain of Responsibility)
Можно избежать жесткой зависимости отправителя запроса от его получателя, при этом запросом начинает обрабатываться один из нескольких объектов. Объекты-получатели связываются в цепочку, и запрос передается по цепочке, пока кокой-то объекта его не обработает.
Представление бизнес-логики
Сценарий транзакции (Transaction Script)
Способ организации бизнес-логики по процедурам, каждая из которых обслуживает один запрос, инициализируемый слоем представления. Главным достоинством сценария транзакции является простота решения. Вам не приходится беспокоиться о наличии и вариантов функционирования других параллельных транзакций. Обычно сценарий транзакции размещается в классах, отличных от тех, которые относятся к слоям представления и источника данных. Может использоваться один класс для реализации нескольких сценариев транзакции или вариант, следующий схеме типового решения команда (Command) - собственный класс для каждого сценария транзакции. В последнем возможность манипулировать экземплярами сценариев как объектами в период выполнения помогает преодолевать проблемы потоков вычислений и облегчает изоляцию данных.
Модель предметной области (Domain Model)
Это объектная модель домена, охватывающая поведение (функции) и свойства (данные). Предусматривает создание сети взаимосвязанных объектов, каждый из которых представляет некую осмысленную сущность. Реализация модели предметной области означает пополнение приложения целым слоем объектов, описывающих различные стороны определенной области бизнеса. С моделью предметной области связано большое количество различных контекстов. Модели предметной области особенно хороши, когда существует множество схожих условий протекания процесса, которые могут быть отображены в структуре объектов как таковых. В этом случае сложность алгоритмов вычислений перемещается на уровень связей между объектами. Чем более близка логика, тем с большей вероятностью различные части кода системы должны пользоваться одними и теми же связями. Любому алгоритму вычислений соответствует определенная сеть объектов. Также назначение модели предметной области во многом состоит в сокрытии базы данных от верхних слоев системы.
Модуль таблицы (Table Module)
Объект, охватывающий логику обработки всех записей хранимой или виртуальной таблицы базы данных. Предусматривает создание по одному классу на каждую таблицу базы данных, и единственный экземпляр класса содержит всю логику обработки данных таблицы. Основное отличие модуля таблицы от модели предметной области (Domain Model) состоит в том, что если, например, приложение обслуживает множество заказов, в соответствии с моделью предметной области придется сконструировать по одному объекту на каждый заказ, а при использовании модуля таблицы понадобится всего один объект, представляющий одновременно все заказы. Сильная сторона решения модуль таблицы заключается в том, что оно позволяет сочетать данные и функции для их обработки и в то же время эффективно использовать ресурсы реляционной базы данных. На первый взгляд модуль таблицы во многом напоминает обычный объект, но отличается тем, что не содержит какого бы то ни было упоминания об идентификационном признаке объекта. Типовое решение модуль таблицы во многом основывается на табличной структуре данных и потому допускает очевидное применение в ситуациях, где доступ к информации обеспечивается при посредничестве множеств записей (Record Set). Структуре данных отводится центральная роль, так что методы обращения к данным в структуре должны быть прямолинейными и эффективными.
Слой служб (Service Layer)
Устанавливает множество доступных действий и координирует отклик приложения на каждое действие. Слой служб определяет границы приложения и множество операций, предоставляемых им для интерфейсных клиентских слоев кода. Он инкапсулирует бизнес-логику приложения, управляет транзакциями и координирует реакции на действия. Подобно сценарию транзакции (Transaction Script) и модели предметной области (Domain Model), слой служб представляет собой типовое решение по организации бизнес-логики. Слой служб предусматривает распределение "разной" логики по отдельным слоям, что обеспечивает традиционные преимущества расслоения, а также большую степень свободы применения классов домена в разных приложениях. Двумя базовыми вариантами реализации слоя служб являются создание интерфейса доступа к домену (Domain Facade) и конструирование сценария операции (Operation Script). Преимуществом использования слоя служб является возможность определения набора общих операций, доступных для применения многими категориями клиентов, и координация откликов приложения на выполнение каждой операции.
Архитектурные типовые решения источника данных
Шлюз таблицы данных (Table Data Gateway)
Объект, выполняющий роль шлюза (Gateway) к базе данных. Он содержит в себе все команды SQL, необходимые для извлечения, вставки, обновления и удаления данных из таблицы или представления. Методы шлюза таблицы данных используются другими объектами для взаимодействия с базой данных. Как правило, для каждой таблицы базы данных создается собственный шлюз таблицы данных. Это наиболее простое типовое решение интерфейса базы данных, поскольку оно замечательно отображает таблицы или записи баз данных на объекты. Одно из преимуществ использования шлюза таблицы данных для инкапсуляции доступа к базе данных состоит в том, что этот интерфейс может применяться и для обращения к базе данных с помощью средств языка SQL, и для работы с хранимыми процедурами. Данное решение подходит практически для любой платформы, так как представляет собой не более чем оболочку для операторов SQL.
Шлюз записи данных (Row Data Gateway)
Объект, выполняющий роль шлюза (Gateway) к отдельной записи источника данных. Каждой строке таблицы базы данных соответствует свой экземпляр шлюза записи данных. Шлюз выступает в роли интерфейса к строке данных и прекрасно подходит для применения в сценариях транзакции (Transaction Script). Реализация шлюза записи данных включает в себя только логику доступа к базе данных и никакой логики домена. Добавление логики домена в шлюз записи данных превращает его в активную запись (Active Record).
Активная запись (Active Record)
Объект, выполняющий роль оболочки для строки таблицы или представления базы данных. Он инкапсулирует доступ к базе данных и добавляет к данным логику домена. В основе типового решения активная запись лежит модель предметной области (Domain Model). Каждая активная запись отвечает за сохранение и загрузку информации в базу данных, а также за логику домена, применяемую к данным. Это может быть вся бизнес-логика приложения. Впрочем, иногда некоторые фрагменты логики домена содержатся в сценариях транзакции (Transaction Script), а общие элементы кода, ориентированные на работу с данными, - в активной записи. Активная запись очень похожа на шлюз записи данных (Row Data Gateway). Принципиальное отличие между ними в том, что шлюз записи данных содержит только логику доступа к базе данных, в то время как активная запись содержит и логику доступа к данным, и логику домена. Активная запись хорошо подходит для реализации не слишком сложной логики домена, в частности операций создания, считывания, обновления и удаления. Кроме того, она прекрасно справляется с извлечением и проверкой на правильность отдельной записи.
Преобразователь данных (Data Mapper)
Это слой преобразователей (Mapper), который осуществляет передачу данных между объектами и базой данных, сохраняя последние независимыми друг от друга и от самого преобразователя. То есть это слой программного обеспечения, которое отделяет объекты, расположенные в оперативной памяти, от базы данных. В функции преобразователя данных входит передача данных между объектами и базой данных и изоляция их друг от друга. В результате этого объекты, расположенные в оперативной памяти, могут даже не "подозревать" о самом факте присутствия базы данных. Им не нужен SQL-интерфейс и тем более схема базы данных. (В свою очередь, схема базы данных никогда не "знает" об объектах, которые её используют.) Поскольку преобразователь данных является разновидностью преобразователя (Mapper), он полностью скрыт от уровня домена. В большинстве случаев преобразователь данных применяется для того, чтобы схема базы данных и объектная модель могли изменяться независимо друг от друга. Как правило, подобная необходимость возникает при использовании модели предметной области (Domain Model). Основным преимуществом преобразователя данных является возможность работы с моделью предметной области без учета структуры базы данных как в процессе проектирования, так и во время сборки и тестирования проекта. В этом случае объектам домена ничего не известно о структуре базы данных, поскольку все отображения выполняются преобразователями.
Объектно-реляционные типовые решения, предназначенные для моделирования поведения
Единица работы (Unit of Work)
Содержит список объектов, охватываемых бизнес-транзакцией, координирует запись изменений в базу данных и разрешает проблемы параллелизма. Типовое решение единица работы позволяет контролировать все действия, выполняемые в рамках бизнес-транзакции, которые так или иначе связаны с базой данных. По завершении всех действий единица работы определяет окончательные результаты работы, которые и будут внесены в базу данных. Таким образом не приходится отслеживать, что было изменено, или беспокоиться о том, в каком порядке необходимо выполнить нужные действия, чтобы не нарушить целостность на уровне ссылок - это все сделает единица работы. Основным назначением единицы работы является отслеживание действий, выполняемых над объектами домена, для дальнейшей синхронизации данных, хранящихся в оперативной памяти, с содержимым базы данных. Эта синхронизация выполняется единицей работы в конце бизнес-транзакции, для избежания большого количества мелких обращений к базе данных.
Коллекция объектов (Identity Map)
Гарантирует, что каждый объект будет загружен из базы данных только один раз, сохраняя загруженный объект в специальной коллекции. При получении запроса просматривает коллекцию в поисках нужного объекта. Коллекция объектов может быть явной (explicit), кода доступ осуществляется посредством специализированных методов, соответствующих конкретному типу искомого объекта, или универсальной (generic), когда для доступа ко всем типам объектов используется общий метод. Как правило, коллекция объектов применяется для управления любыми объектами, которые были загружены из базы данных и затем подверглись изменениям. Основное назначение коллекции объектов - не допустить возникновения ситуации, когда два разных объекта приложения будут соответствовать одной и той же записи базы данных, поскольку изменение этих объектов может происходить несогласованно и, следовательно, вызывать трудности с отображением на базу данных. Еще одним преимуществом коллекции объектов является возможность использования ее в качестве кэша записей, считываемых из базы данных. Это избавляет от необходимости повторного обращения к базе данных, если снова понадобиться какой-нибудь объект.
Загрузка по требованию (Lazy Load)
Объект, который не содержит все требующиеся данные, однако может загрузить их в случае необходимости. Существует четыре основных способа реализации загрузки по требованию. Самый простой - инициализация по требованию (Lazy Initialization). Основная идея данного подхода заключается в том, что при каждой попытке доступа к полю выполняется проверка, не содержит ли оно значение NULL. Если поле содержит NULL, метод доступа загружает значение поля и лишь затем его возвращает. Если используется преобразователь данных (Data Mapper), то может понадобиться реализовать виртуальлный прокси-объект (Virtual Proxy). Виртуальный прокси-объект имитирует объект, являющийся значением поля, однако в действительности ничего в себе не содержит. В этом случае загрузка реального объекта будет выполнена только тогда, когда будет вызван один из методов виртуального прокси-объекта. Диспетчер значений (Value Holder) - еще один способ реализации - это объект, который выполняет роль оболочки для какого-нибудь другого объекта. Чтобы добраться к значению базового объекта, необходимо обратиться за ним к диспетчеру значения. При первом обращении диспетчер значения извлекает необходимую информацию из базы данных. И наконец, фиктивный объект (Ghost) - это реальный объект с неполным состоянием. Когда подобный объект загружается из базы, он содержит только свой идентификатор. При первой же попытке доступа к одному из его полей объект загружает значения всех остальных полей. Помимо всего прочего, использование загрузки по требованию может повлечь за собой чрезмерное количество обращений к базе данных. Бессмысленно использовать загрузку по требованию, чтобы извлечь значения поля, хранящегося в той же строке, что и остальное содержимое объекта, даже если речь идет о полях большого размера, таких как крупный сериализованный объект (Serialized LOB) (тут я не совсем согласен с автором). Применение загрузки по требованию существенно усложняет приложение, поэтому прибегать к ней нужно только тогда, когда без нее действительно не обойтись.
Объектно-реляционные типовые решения, предназначенные для моделирования структуры
Поле идентификации (Identity Field)
Сохраняет идентификатор записи базы данных для поддержки соответствия между объектом приложения и строкой базы данных. Суть поля идентификации до смешного проста: требуется лишь сохранить первичный ключ таблицы реляционной базы данных в полях объекта, однако в реализации этого шаблона существует множество спорных моментов. Поле идентификации используется тогда, когда необходимо построить отображение между объектами, расположенными в оперативной памяти, и строками таблицы базы данных. Как правило, такая необходимость возникает при использовании модели предметной области (Domain Model) или шлюза записи данных (Row Data Gateway). Подобное отображение не нужно, если вы используете сценарий транзакции (Transaction Script), модуль таблицы (Table Module) или шлюз таблицы данных (Table Data Gateway).
Отображение внешних ключей (Foreign Key Mapping)
Отображает ассоциации между объектами на ссылки внешнего ключа между таблицами базы данных. Обычно реализуется посредством добавления обратного указателя на "объект-хозяин", что приводит к появлению ссылки, аналогичной создаваемой внешним ключом. Это изменяет объектную модель, однако позволяет значительно упростить обновление данных путем простого обновления однозначных полей "с другой стороны" ссылки. Отображение внешних ключей может применяться для моделирования практически всех связей между классами. Наиболее распространенный случай, когда отображение внешних ключей применить нельзя, - это связи "многие ко многим". Внешние ключи являются одномерными значениями, а из определения первой нормальной формы следует, что в одном поле нельзя хранить множественные значения внешних ключей. В этом случае вместо отображения внешних ключей необходимо воспользоваться отображением с помощью таблицы ассоциаций (Association Table Mapping).
Отображение с помощью таблицы ассоциаций (Association Table Mapping)
Сохраняет множество ассоциаций в виде таблицы, содержащей внешние ключи таблицы, связанных ассоциациями. В основе отображения с помощью таблицы ассоциаций лежит хранение ассоциаций в дополнительной таблицы отношений. Последняя содержит только значения внешних ключей двух таблиц, связанных отношением. Таким образом, каждой паре взаимосвязанных объектов соответствует одна строчка таблицы отношений.
Отображение зависимых объектов (Dependent Mapping)
Передает некоторому классу полномочия по выполнению отображения для дочернего класса. Один класс (зависимый объект) передает другому классу (владельцу) все свои полномочия по взаимодействию с базой данных. При этом у каждого зависимого объекта должен быть один и только один владелец. Данный принцип проявляет себя в терминах классов, выполняющих отображение. В случае с активной записью (Active Record) и шлюзом записи данных (Row Data Gateway) зависимый класс не будет содержать никакого кода, касающегося выполнения отображения на базу данных; этот код будет реализован в классе владельце. В случае с преобразователем данных (Data Mapper) у зависимого класса не будет своего преобразователя; всё необходимое отображение будет выполняться преобразователем класса-владельца. И наконец, в случае шлюза таблицы данных (Table Data Gateway) зависимого класса не будет вообще; всю обработку зависимых данных будет осуществлять класс-владелец. Зависимый объект не имеет поля идентификации (Identity Field) и, следовательно, не заноситься в коллекцию объектов (Identity Map). Для зависимого объекта вообще не предусмотрено отдельных методов поиска, так как весь поиск выполняется объектом-владельцем. Отображение зависимых объектов может применяться только при соблюдении следующих условий: у каждого зависимого объекта должен быть строго один владелец; на зависимый объект может ссылаться только его владелец.
Внедренное значение (Embedded Value)
Отображает объект на несколько полей таблицы, соответствующей другому объекту. Обычно происходит отображение значения полей объекта на поля записи его владельца. Или другими словами, поля таблицы некоего объекта, как бы группируются в одном поле этого объекта, представляя собой подчиненный объект, содержащий значения полей объекта-владельца. Он может представлять собой, например, объект-значение (Value Object). Когда-объект владелец загружается из базы данных или сохраняется в ней, вместе с ним загружаются или сохраняются и зависимые объекты. Зависимые классы не имею собственных методов загрузки и сохранения, поскольку все эти операции выполняются их владельцем. Для большей наглядности внедренное значение можно рассматривать как частный случай отображения зависимых объектов (Dependent Mapping), где значения поля таблицы является отдельным зависимым объектом. Огромным преимуществом внедренного значения является возможность постановки SQL-запросов к полям зависимого объекта.
Сериализованный крупный объект (Serialized LOB)
Сохраняет граф объектов путем их сериализации в единый крупный объект и помещает его в поле базы данных. Это разновидность типового решения хранитель (Memento). Может представлять собой как крупный двоичный объект (BLOB), так и крупный символьный объект (CLOB). Первый сохранятся в виде двоичного формата данных, а второй представляет собой текстовую строку, например, в виде XML.
Наследование с одной таблицей (Single Table Inheritence)
Представляет иерархию наследования классов в виде одной таблицы, столбцы которой соответствуют всем полям классов, входящих в иерархию. Наследование с одной таблицей отображает все поля всех классов структуры на столбцы одной и той же таблицы. То есть структура наследования отображается на одну таблицу, которая содержит в себе все данные всех классов, входящих в иерархию наследования. Каждому классу (а точнее, его экземпляру) соответствует одна строка таблицы; при этом поля таблицы, которых нет в данном классе, остаются пустыми. Основное представление объектов, выполняющих отображение, соответствует общей схеме преобразователей наследования (Inheritance Mappers).
Наследование с таблицами для каждого класса (Class Table Inheritence)
Синонимы: отображение "корень-лист" (Root-Leaf Mapping)
Представляет иерархию наследования классов, используя по одной таблице для каждого класса. Идея наследование с таблицами для каждого класса проста и понятна: каждому классу модели предметной области соответствует своя таблица базы данных. Поля класса домена отображаются непосредственно на столбцы соответствующей таблицы. Как и в других схемах отображения иерархии наследования, в данном типовом решении применяется фундаментальный принцип преобразователей наследования (Inheritance Mappers).
Наследование с таблицами для каждого конкретного класса (Concrete Table Inheritence)
Представляет иерархию наследования классов, используя по одной таблице для каждого конкретного класса. При этом каждая таблица содержит столбцы, соответствующие полям конкретного класса и всех его "предков", а потому поля суперкласса дублируются во всех таблицах его производных классов. Как и остальные схемы отображения иерархии наследования, данное типовое решение основано на фундаментальном принципе преобразователей наследования (Inheritance Mappers). Наследование с таблицами для каждого конкретного класса часто рассматривают как разновидность наследования с таблицами для каждого листа (Leaf Table Inheritance), когда создается по одной таблице для каждого листа иерархии наследования, а не для каждого конкретного класса.
Преобразователи наследования (Inheritance Mappers)
Структура, предназначенная для организации преобразователей, которые работают с иерархиями наследования. Минимизирует количество кода, необходимого для загрузки и сохранения содержимого базы данных, при отображении объектно-ориентированной иерархии наследования, благодаря точному распределению обязанностей по структуре преобразователей, реализующих как абстрактное так и конкретное поведение. Хотя детали поведения могут различаться в зависимости от выбранной схемы отображения (наследование с одной таблицей (Single Table Inheritence), наследование с таблицами для каждого класса (Class Table Inheritence), наследование с таблицами для каждого конкретного класса (Concrete Table Inheritence)), общая структура остается одной и той же.
Шаблоны, предназначенные для представления данных в Web
Модель-представление-контроллер (Model View Controller)
Распределяет обработку взаимодействия с пользовательским интерфейсом между тремя участниками. Модель - это объект, предоставляющий некоторую информацию о домене. Содержит в себе все данные и поведение и не связана с пользовательским интерфейсом. В наиболее "чистой" форме представляет собой объект модели предметной области (Domain Model). Представление отображает содержимое модели средствами графического интерфейса. Все изменения информации обрабатываются контроллером. Получая входные данные от пользователя, он выполняет операции над моделью и указывает представлению на необходимость соответствующего обновления. Данное типовое решение реализует два принципиальных типа разделения: отделение представления от модели, являющееся одним из фундаментальных принципов проектирования программного обеспечения, и отделение контроллера от представления. Последний тип разделения практически не играет такой важной роли в системах с толстым клиентом, однако в Web-интерфейсах отделение весьма полезно.
Контроллер страниц (Page Controller)
Объект, который обрабатывает запрос к Web-странице или выполнение конкретного действия на Web-сайте. Предполагает наличие отдельного контроллера для каждой логической страницы Web-сайта. Этим контроллером может быть сама страница или отдельный объект, соответствующий данной странице. То есть контроллер страниц может быть реализован в виде сценария (сценария CGI, сервлета и т.п.) или страницы сервера (ASP, PHP, JSP и т.п.). Контроллер страниц не обязательно должен представлять собой единственный класс, зато все классы контроллеров могут использовать одни и те же вспомогательные объекты. Контроллер страниц более прост в работе чем контроллер запросов (Front Controller) и представляет собой естественный механизм структуризации, при котором конкретные действия обрабатываются соответствующими страницами сервера или классами сценариев. Поэтому контроллер страниц хорошо применять для сайтов с достаточно простой логикой контроллера.
Контроллер запросов (Front Controller)
Контроллер, который обрабатывает все запросы к Web-сайту. Он объединяет все действия по обработке запросов в одном месте, распределяя их выполнение посредством единственного объекта обработчика. Как правило этот объект реализует общее поведение, которое может быть изменено во время выполнения с помощью декораторов (Decorator). Для выполнения конкретного запроса обработчик вызывает соответствующий объект команды (Command). Выбор команды может происходить статически или динамически.
Контроллер приложения (Application Controller)
Точка централизованного управления порядком отображения интерфейсных экранов и потоком функций приложения. Входные контроллеры обращаются к контроллеру приложения за командами, которые следует применить к модели, и за представлениями, которые необходимо использовать в определенном контексте приложения. Контроллер приложения выполняет две основные функции: выбор логики домена, которую нужно применить в конкретной ситуации, и выбор представления, которое следует отобразить в ответ на запрос. Для осуществления этих функций контроллер приложения поддерживает две коллекции ссылок на классы - одну для команд, выполняющихся в слое домена, и одну для представлений. Приложение может иметь несколько контроллеров приложений, управляющих его составными частями. Благодаря этому сложная логика выбора интерфейсных экранов может быть распределена по нескольким классам. Более простому приложению достаточно и одного контроллера. Если логика Web-приложения, описывающая порядок отображения интерфейсных экранов, довольно проста (другими словами, если пользователь может открывать экраны приложения практически в любом порядке), применять контроллер приложения не имеет смысла. Основное преимущество данного типового решения состоит именно в определении порядка отображения страниц и выборе тех или иных представлений в зависимости от состояний объектов. Необходимость использования контроллера приложения очевидна, если различные изменения хода приложения требуют применения схожей логики, касающиеся выбора команд и представлений особенно если такие изменения возникают во многих местах приложения.
Представление по шаблону (Template View)
Преобразует результат выполнения запрос в формат HTML путём внедрения маркеров в HTML-страницу. В основном выполняется вставка маркеров в текст готовой статической HTML-страницы, при вызове которой для обслуживания запроса эти маркеры будут преобразованы в вызовы функций, предоставляющих динамическую информацию. Подобная схема позволяет создавать статическую часть страницы с помощью обычных средств, например WYSIWYG-редакторов. Для избежания внедрения в страницу большого количества программной логики применяют вспомогательный объект (Helper Object). Он будет содержать в себе всю фактическую логику домена, а сама страница - только вызовы вспомогательного объекта. Это значительно упростит структуру страницы и максимально приблизит её к "чистой" форме представления по шаблону. Преимущество представления по шаблону перед представлением с преобразованием (Transform View) в том, что оно позволяет оформлять представление в соответствии со структурой страницы, а также воплотить в жизнь идею, когда дизайнер занимается проектированием страницы, а программист работает над вспомогательным объектом. Однако, реализуя представление в виде страницы сервера, последнюю весьма легко переполнить логикой. Также представление по шаблону сложнее тестировать, чем представлением с преобразованием (Transform View), так как большинство реализаций представления по шаблону ориентированы на использование в рамках Web-сервера.
Представление с преобразованием (Transform View)
Представление, которое поочередно обрабатывает элементы данных домена и преобразует их в код HTML. На вход подаются данные из модели, а на выходе принимается код HTML. При этом программа последовательно проходит по структуре данных домена и, обнаруживая новый фрагмент данных создает их описание в терминах HTML. Отличие представления с преобразованием от представления по шаблону (Template View) заключается в способе организации представления. Представление по шаблону организовано с учетом размещения на экране выходных данных. Представление с преобразованием ориентировано на использование отдельных преобразований для каждого вида входных данных. Преобразованиями управляет нечто наподобие простого цикла, который поочередно просматривает каждый входной элемент, подбирает для него подходящее преобразование и применяет это преобразование. Таким образом, правила представления с преобразованием могут быть организованы в любом порядке - на результат это не повлияет. Преобразования изначально направлены на визуализацию данных в формат HTML, что позволяет избежать внедрения в представление слишком большого количества логики. Представление с преобразованием легче тестировать, поскольку, в основном, его реализация не требует наличия функционирующего Web-сервера. Представления по шаблону (Template View) не выдерживает последних двух достоинств представления с преобразованием.
Двухэтапное представление (Two Step View)
Выполняет визуализацию данных домена в два этапа: вначале формирует некое подобие логической страницы, после чего преобразует логическую страницу в формат HTML. На первом этапе информация, полученная от модели, организуется в некую логическую структуру, которая описывает визуальные элементы будущего отображения, однако еще не содержит кода HTML. На втором этапе полученная логическая структура преобразуется в код HTML. Методы второго этапа "знают", какие элементы есть в логической структуре и как визуализировать каждый из этих элементов. Таким образом, система с множеством экранов может быть трансформирована в код HTML путём единственного прохождения второго этапа, благодаря чему решение о варианте преобразования в HTML принимается в одном месте. Двухэтапное преобразование может быть построено как на основе представления с преобразованием (Transform View) так и на основе представления по шаблону (Template View). Главным преимуществом двухэтапного представления является возможность разбить преобразование данных на два этапа, что облегчает проведение глобальных изменений. Одноэтапный вариант представления, будь то представление по шаблону или представление с преобразованием, предусматривает по одному компоненту представления для каждой Web-страницы приложения. В двухэтапном представлении визуализация данных выполняется в два этапа, что требует по одному представлению первого этапа для каждой страницы приложения и единственное представление второго этапа для всего приложения. Последняя схема значительно облегчает изменение внешнего вида сайта на втором этапе, поскольку каждое такое изменение распространяется сразу на весь сайт. Таким образом, для приложений с несколькими вариантами внешнего вида используется меньше элементов регулировки отображения и чем больше интерфейсных экранов и вариантов внешнего вида есть у приложения, тем большим будет выигрыш.
Продолжение следует....
Не мешало бы в общих чертах объяснить что такое паттерны и для чего они нужны, какой-нибудь крохотный пример из практики...