Книга «Паттерны объектно-ориентированного проектирования»
Привет, Хаброжители! Больше 25 лет прошло с момента выхода первого тиража книги Design Patterns. За это время книга из популярной превратилась в культовую. Во всем мире ее рекомендуют прочитать каждому, кто хочет связать жизнь с информационными технологиями и программированием. «Русский» язык, на котором разговаривают айтишники, поменялся, многие англоязычные термины стали привычными, паттерны вошли в нашу жизнь.
Перед вами юбилейное издание с обновленным переводом книги, ставшей must-read для каждого программиста. «Паттерны объектно-ориентированного проектирования» пришли на смену «Приемам объектно-ориентированного проектирования».
Четыре первоклассных разработчика — Банда четырех — представляют вашему вниманию опыт ООП в виде двадцати трех паттернов. Паттерны появились потому, что разработчики искали пути повышения гибкости и степени повторного использования своих программ. Авторы не только дают принципы использования шаблонов проектирования, но и систематизируют информацию. Вы узнаете о роли паттернов в архитектуре сложных систем и сможете быстро и эффективно создавать собственные приложения с учетом всех ограничений, возникающих при разработке больших проектов. Все шаблоны взяты из реальных систем и основаны на реальной практике. Для каждого паттерна приведен код на C++ или Smalltalk, демонстрирующий его возможности.
Предисловие
В книге описываются простые и изящные решения типичных задач, возникающих в объектно-ориентированном проектировании.
Паттерны появились потому, что многие разработчики искали пути повышения гибкости и степени повторного использования своих программ. Найденные решения воплощены в краткой и легко применимой на практике форме. «Банда Четырех» объясняет каждый паттерн на простом примере четким и понятным языком. Использование паттернов при разработке программных систем позволяет проектировщику перейти на более высокий уровень разработки проекта. Теперь архитектор и программист могут оперировать образными названиями паттернов и общаться на одном языке.
Таким образом, книга решает две задачи.
Во-первых, знакомит с ролью паттернов в создании архитектуры сложных систем.
Во-вторых, позволяет проектировщикам с легкостью разрабатывать собственные приложения, применяя содержащиеся в справочнике паттерны.
Что изменилось в издании 2020 года?
- Актуализирована терминология (например, для «реорганизации» кода уже вполне прижился термин «рефакторинг», для share — «совместное использование» вместо «разделения», а для mixin — «примесь»);
- обновлен стиль;
- устранены излишне громоздкие слова (например, «специфицирование» или «инстанцирование». Первое можно вполне адекватно заменить «определением», второе — «созданием экземпляра»);
- книга наконец-то называется «Паттерны объектно-ориентированного проектирования».
ПАТТЕРН TEMPLATE METHOD (ШАБЛОННЫЙ МЕТОД)
Название и классификация паттерна
Шаблонный метод — паттерн поведения классов.
Назначение
Шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые шаги алгоритма, не изменяя его структуру в целом.
Мотивация
Рассмотрим каркас приложения, в котором имеются классы Application и Document. Класс Application отвечает за открытие существующих документов, хранящихся во внешнем формате (например, в файле). Объект класса Document представляет информацию документа после его прочтения из файла.
Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов Application и Document, отвечающие конкретным потребностям.
Например, графический редактор определит подклассы DrawApplication и DrawDocument, а электронная таблица — подклассы SpreadsheetApplication и SpreadsheetDocument.
В абстрактном классе Application определен алгоритм открытия и чтения документа в операции OpenDocument:
void Application::OpenDocument (const char* name) {
if (!CanOpenDocument(name)) {
// Обработать документ невозможно
return;
}
Document* doc = DoCreateDocument();
if (doc) {
_docs->AddDocument(doc);
AboutToOpenDocument(doc);
doc->Open();
doc->DoRead();
}
}
Операция OpenDocument определяет все шаги открытия документа. Она проверяет, возможно ли открыть документ, создает объект класса Document, добавляет его к набору документов и читает документ из файла.
Операцию вида OpenDocument мы будем называть шаблонным методом, описывающим алгоритм в категориях абстрактных операций, которые замещены в подклассах для получения нужного поведения. Подклассы класса Application проверяют возможность открытия (CanOpenDocument) и создания документа (DoCreateDocument). Подклассы класса Document считывают документ (DoRead). Шаблонный метод определяет также операцию, которая позволяет подклассам Application получить информацию о том, что документ вот-вот будет открыт (AboutToOpenDocument).
Определяя некоторые шаги алгоритма с помощью абстрактных операций, шаблонный метод фиксирует их последовательность, но позволяет реализовать их в подклассах классов Application и Document.
Применимость
Основные условия для применения паттерна шаблонный метод:
- однократное использование инвариантных частей алгоритма, при этом реализация изменяющегося поведения остается на усмотрение подклассов;
- необходимость вычленить и локализовать в одном классе поведение, общее для всех подклассов, чтобы избежать дублирования кода. Это хороший пример техники «вынесения за скобки с целью обобщения», описанной в работе Уильяма Опдайка (William Opdyke) и Ральфа Джонсона (Ralph Johnson) [OJ93]. Сначала выявляются различия в существующем коде, которые затем выносятся в отдельные операции. В конечном итоге различающиеся фрагменты кода заменяются шаблонным методом, из которого вызываются новые операции;
- управление расширениями подклассов. Шаблонный метод можно определить так, что он будет вызывать операции-зацепки (hooks) — см. раздел «Результаты» — в определенных точках, разрешив тем самым расширение только в этих точках.
Структура
Участники
AbstractClass (Application) — абстрактный класс:
- определяет абстрактные примитивные операции, замещаемые в конкретных подклассах для реализации шагов алгоритма;
- реализует шаблонный метод, определяющий скелет алгоритма. Шаблонный метод вызывает примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах;
ConcreteClass (MyApplication) — конкретный класс:
- реализует примитивные операции, выполняющие шаги алгоритма способом, который зависит от подкласса.
Отношения
ConcreteClass предполагает, что инвариантные шаги алгоритма будут выполняться в AbstractClass.
Результаты
Шаблонные методы — один из фундаментальных приемов повторного использования кода. Они играют особенно важную роль в библиотеках классов, поскольку предоставляют возможность вынести общее поведение в библиотечные классы.
Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой киноимперии фразу «Не звоните нам, мы сами вам позвоним» [Swe85]. В данном случае это означает, что родительский класс вызывает операции подкласса, а не наоборот.
Шаблонные методы вызывают операции следующих видов:
- конкретные операции (либо из класса ConcreteClass, либо из классов клиента);
- конкретные операции из класса AbstractClass (то есть операции, полезные всем подклассам);
- примитивные операции (то есть абстрактные операции);
- фабричные методы (см. паттерн фабричный метод (135));
- операции-зацепки (hook operations), реализующие поведение по умолчанию, которое может быть расширено в подклассах. Часто такая операция по умолчанию не делает ничего.
Важно, чтобы в шаблонном методе четко различались операции-зацепки (которые можно замещать) и абстрактные операции (которые нужно замещать). Чтобы повторно использовать абстрактный класс с максимальной эффективностью, авторы подклассов должны понимать, какие операции предназначены для замещения.
Подкласс может расширить поведение некоторой операции, заместив ее и явно вызвав эту операцию из родительского класса:
void DerivedClass::Operation () {
// Расширенное поведение DerivedClass
ParentClass::Operation();
}
К сожалению, очень легко забыть о необходимости вызывать унаследованную операцию. Такую операцию можно трансформировать в шаблонный метод, чтобы предоставить родителю контроль над тем, как подклассы расширяют его. Идея в том, чтобы вызывать операцию-зацепку из шаблонного метода в родительском классе. Тогда подклассы смогут переопределить именно эту операцию:
void ParentClass::Operation () {
// Поведение ParentClass
HookOperation();
}
В родительском классе ParentClass операция HookOperation не делает ничего:
void ParentClass::HookOperation () { }
Подклассы переопределяют HookOperation, чтобы расширить свое поведение:
void DerivedClass::HookOperation () {
// Расширение в производном классе
}
Реализация
При реализации паттерна шаблонный метод следует обратить внимание на следующие аспекты:
- использование контроля доступа в C++. В этом языке примитивные операции, которые вызывает шаблонный метод, можно объявить защищенными членами. Тогда гарантируется, что вызывать их сможет только сам шаблонный метод. Примитивные операции, которые обязательно нужно замещать, объявляются как чисто виртуальные функции. Сам шаблонный метод замещать не надо, так что его можно сделать невиртуальной функцией-членом;
- сокращение числа примитивных операций. Важной целью при проектировании шаблонных методов является всемерное сокращение числа примитивных операций, которые должны быть замещены в подклассах. Чем больше операций нужно замещать, тем утомительнее становится программирование клиента;
- соглашение об именах. Выделить операции, которые необходимо заместить, можно путем добавления к их именам некоторого префикса. Например, в каркасе MacApp для приложений на платформе Macintosh [App89] имена шаблонных методов начинаются с префикса Do: DoCreateDocument, DoRead и т. д.
Пример кода
Следующий пример на языке C++ показывает, как родительский класс может навязать своим подклассам некоторый инвариант. Пример взят из библиотеки NeXT AppKit [Add94]. Рассмотрим класс View, поддерживающий рисование на экране, — своего рода инвариант, который заключается в том, что подклассы могут изменять вид только тогда, когда он находится в фокусе. Для этого необходимо, чтобы был установлен определенный контекст рисования (например, цвета и шрифты).
Для установки состояния можно воспользоваться шаблонным методом Display. В классе View определены две конкретные операции (SetFocus и ResetFocus), которые соответственно устанавливают и сбрасывают контекст рисования. Операция-зацепка DoDisplay класса View занимается собственно рисованием. Display вызывает SetFocus перед DoDisplay, чтобы подготовить контекст, и ResetFocus после DoDisplay — чтобы его сбросить:
void View::Display () {
SetFocus();
DoDisplay();
ResetFocus();
}
С целью поддержки инварианта клиенты класса View всегда вызывают Display и подклассы View всегда замещают DoDisplay.
В классе View операция DoDisplay не делает ничего:
void View::DoDisplay () { }
Подклассы переопределяют ее, чтобы добавить свое конкретное поведение рисования:
void MyView::DoDisplay () {
// изобразить содержимое вида
}
Известные применения
Шаблонные методы настолько фундаментальны, что встречаются почти в каждом абстрактном классе. В работах Ребекки Вирфс-Брок и др. [WBWW90, WBJ90] подробно обсуждаются шаблонные методы.
Родственные паттерны
Фабричные методы (135) часто вызываются из шаблонных. В примере из раздела «Мотивация» шаблонный метод OpenDocument вызывал фабричный метод DoCreateDocument.
Стратегия (362): шаблонные методы применяют наследование для модификации части алгоритма. Стратегии используют делегирование для модификации алгоритма в целом.
» Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 25% по купону — ООП
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.