Э. Гамма, Р. Хелм
Скачать 6.37 Mb.
|
ПАТТЕРНЫ В ПРОГРАММНОМ ОБЕСПЕЧЕНИИ Нашим первым коллективным опытом изучения архитектуры программного обеспечения было участие в семинаре OOPSLA ’91, который проводил Брюс Андерсон (Bruce Anderson). Семинар был посвящен составлению справочни- ка для архитекторов программных систем. Данное событие положило начало целой серии встреч, последняя из которых состоялась в августе 1994 г. на первой конференции по языкам паттернов программ. В результате сформи- ровалось сообщество людей, заинтересованных в документировании опыта разработки программного обеспечения. Разумеется, ту же цель видели перед собой и другие исследователи. Книга Дональда Кнута «Искусство программирования для ЭВМ» [Knu73] была одной из первых попыток систематизировать знания, накопленные при раз- работке программ, хотя акцент в ней был сделан на описании алгоритмов. Но даже эта задача оказалась слишком трудной, так что работа осталась не- законченной. Серия книг Graphics Gems [Gla90, Arv91, Kir92] — еще один каталог, посвященный проектированию, хотя и он в основном посвящен алгоритмам. Программа Domain Specific Software Architecture (Архитек- тура проблемно-ориентированного программного обеспечения), которую спонсирует Министерство обороны США [GM92], направлена на подбор информации архитектурного плана. Исследователи, занятые разработкой баз знаний, стремятся отразить накопленный опыт разработок. Есть и много других групп, задачи которых в той или иной мере сходны с нашими. Большое влияние на нас также оказала книга Джеймса Коплиена «Advanced C++: Programming Styles and Idioms» [Cop92]. Описанные в ней паттерны в большей степени, чем наши, ориентированы на C++. Кроме того, в книге 6.4. Приглашение 411 приводится много низкоуровневых паттернов. Коплиен всегда был активным членом сообщества проектировщиков, заинтересованных в паттернах. Сей- час он работает над паттернами, описывающими роли людей в организациях, занятых разработкой программ. Одним из первых, кто стал популяризировать работы Кристофера Алек- сандра среди программистов, был Кент Бек (Kent Beck). В 1993 г. он начал вести колонку в журнале The Smalltalk Report, посвященную паттернам в языке Smalltalk. Некоторое время паттерны собирал Питер Коад (Peter Coad). В основном в его работе [Coa92] представлены паттерны анализа. Мы не видели последние паттерны, разработанные им, хотя и слышали, что он над ними работает. Также ходят слухи о нескольких книгах на тему паттернов, над которыми сейчас работают авторы, но не видели ни одной из них. Все, что мы можем — сказать, что их появление ожидается в будущем. 6.4. ПРИГЛАШЕНИЕ Что можете сделать лично вы, если вас интересуют паттерны? Прежде всего применяйте их и ищите другие паттерны, которые лучше отражают ваш подход к проектированию. Разработайте свой словарь паттернов и исполь- зуйте его, в частности, в беседах с коллегами о своих проектах. Размышляя о проектах и описывая их, не забывайте о словаре. Во-вторых, относитесь к материалу критично! Каталог паттернов — это плод напряженной работы, не только нашей, но и десятков рецензентов, который делились своими замечаниями. Если вы наткнулись на какую-то проблему или полагаете, что объяснения должны быть более подробными, пишите нам. То же самое относится к любому каталогу паттернов: авторам нужна обратная связь с читателями! Одна из самых полезных особенностей паттер- нов состоит в том, что они выводят процесс принятия проектных решений из туманной области интуиции. С помощью паттернов авторы могут явно сформулировать, на какие компромиссы им пришлось идти. А это, в свою очередь, помогает разглядеть, каковы же минусы их паттернов, и вступить в осмысленную дискуссию. Не упускайте такой возможности. В-третьих, выявляйте паттерны, которыми пользуетесь, и фиксируйте их. Включите их в состав документации по своей программе. Вовсе не обяза- тельно работать в научно-исследовательской лаборатории, чтобы находить паттерны. Не стесняйтесь составить свой собственный каталог паттернов, но... пусть кто-нибудь поможет вам облечь его в достойную форму! 412 Глава 6. Заключение 6.5. НА ПРОЩАНИЕ В лучших проектах используется много паттернов проектирования. Единое целое образуется в результате их согласованных взаимных действий. Вот что говорит об этом Кристофер Александр: «Можно строить здания, на- низывая паттерны в достаточно произвольном порядке. Такое здание будет просто собранием паттернов. В нем нет плотности. Нет основательности. Но можно объединять паттерны и так, что в одном и том же физическом объеме они будут перекрывать друг друга. Тогда здание получается очень плотным, в небольшом пространстве сосредоточивается много функций. За счет такой плотности здание приобретает основательность» (A Pattern Language [AIS+77, стр. xli]). ПРИЛОЖЕНИЕ A ГЛОССАРИЙ Абстрактная операция — операция, которая объявляет сигнатуру, но не ре- ализует ее. В C++ абстрактные операции соответствуют чисто виртуальным функциям классов. Абстрактная связанность — говорят, что класс A абстрактно связан с аб- страктным классом B, если в A есть ссылка на B. Такое отношение мы на- зываем абстрактной связанностью, поскольку A ссылается на тип объекта, а не на конкретный объект. Абстрактный класс — класс, единственным назначением которого является определение интерфейса. Абстрактный класс полностью или частично деле- гирует свою реализацию подклассам. Создавать экземпляры абстрактного класса нельзя. Агрегированный объект — объект, составленный из подобъектов. Под- объекты называются частями агрегата, и агрегат отвечает за них. Делегирование — механизм реализации, при котором объект перенаправляет или делегирует запрос другому объекту (уполномоченному). Уполномочен- ный выполняет запрос от имени исходного объекта. Деструктор — в C++ это операция, которая автоматически вызывается для очистки объекта непосредственно перед его удалением. Динамическое связывание — ассоциация между запросом к объекту и одной из его операций, устанавливаемая во время выполнения. В C++ динамически связываться могут только виртуальные функции. Дружественный класс — в C++: класс, обладающий теми же правами доступа к операциям и данным некоторого класса, что и сам этот класс. 414 Приложение A. Глоссарий Закрытое наследование — в C++: класс, наследуемый только ради реали- зации. Замещение — переопределение операции, унаследованной от родительского класса, в подклассе. Инкапсуляция — результат сокрытия представления и реализации в объ- екте. Представление невидимо и недоступно извне. Получить доступ к представлению объекта и модифицировать его можно только с помощью операций. Инструментальная библиотека (toolkit) — набор классов, обеспечивающих полезную функциональность, но не определяющих дизайн приложения. Интерфейс — набор всех сигнатур, определенных операциями объекта. Ин- терфейс описывает множество запросов, на которые может отвечать объект. Каркас — набор взаимодействующих классов, описывающих повторно применимый дизайн некоторой категории программ. Каркас задает архи- тектуру приложения, разбивая его на отдельные классы с четко определен- ными функциями и взаимодействиями. Разработчик настраивает каркас под конкретное приложение путем порождения подклассов и составления композиций из объектов, принадлежащих классам каркаса. Класс — определяет интерфейс и реализацию объекта. Описывает внутрен- нее представление и операции, которые объект может выполнять. Композиция объектов — объединение нескольких объектов для получения более сложного поведения. Конкретный класс — класс, в котором нет абстрактных операций. Может иметь экземпляры. Конструктор — в С++: операция, автоматически вызывающаяся для ини- циализации новых экземпляров. Метакласс — в Smalltalk классы являются объектами. Метакласс — это класс объекта-класса. Наследование — отношение, которое определяет одну сущность в тер- минах другой. В случае наследования класса новый класс определяется в терминах одного или нескольких родительских классов. Новый класс наследует интерфейс и реализацию от своих родителей. Новый класс на- зывается подклассом или производным классом (в C++). Наследование класса объединяет наследование интерфейса и наследование реализации. Глоссарий 415 В случае наследования интерфейса новый интерфейс определяется в тер- минах одного или нескольких существующих. При наследовании реали- зации новая реализация определяется в терминах одной или нескольких существующих. Объект — имеющаяся во время выполнения сущность, в которой хранятся данные и процедуры для работы с ними. Операция — на данные объекта можно воздействовать только с помощью его операций. Объект выполняет операцию, когда получает запрос. В C++ операции называются функциями класса, в Smalltalk — методами. Операция класса — операция, определенная для класса в целом, а не для индивидуального объекта. В C++ операции класса называются статиче- скими функциями. Отношение агрегирования — отношение агрегата и его частей. Класс опре- деляет такое отношение для своих экземпляров, то есть агрегированных объектов. Отношение осведомленности — говорят, что одному классу известно о дру- гом, если первый ссылается на второй. Параметризованный тип — тип, где некоторые составляющие типы остав- лены неопределенными. Они передаются как параметры в точке использо- вания. В C++ параметризованные типы называются шаблонами. Паттерн проектирования — паттерн проектирования именует, мотивирует и объясняет конкретный прием проектирования, который относится к задаче, часто возникающей при работе над объектно-ориентированными системами. Паттерн описывает задачу, ее решение, область применимости этого решения и его результаты. Он также содержит рекомендации по реализации и при- меры. Под решением понимается схема организации объектов и классов, позволяющая справиться с проблемой. Паттерн адаптируется для работы в конкретных условиях и реализуется в заданном контексте. Переменная экземпляра — элемент данных, определяющий часть представ- ления объекта. В C++ используется термин переменная класса. Подкласс — класс, наследующий другому классу. В C++ подкласс называ- ется производным классом. Подсистема — независимая группа классов, функционирующих совместно для выполнения набора обязанностей. 416 Приложение A. Глоссарий Подтип — один тип называется подтипом другого, если интерфейс первого содержит интерфейс второго. Полиморфизм — способность подставлять во время выполнения вместо одного объекта другой с совместимым интерфейсом. Получатель — объект, которому направлен запрос. Примесь — класс, спроектированный так, чтобы сочетаться с другими клас- сами путем наследования. Примеси-классы обычно абстрактны. Прозрачный ящик как способ повторного использования — стиль по- вторного использования, основанный на наследовании классов. Подкласс повторно использует интерфейс и реализацию родительского класса, но может также иметь доступ к закрытым для других аспектам своего родителя. Протокол — расширяет концепцию интерфейса за счет включения допусти- мой последовательности запросов. Родительский класс — класс, которому наследует другой класс. Синонимы: суперкласс (Smalltalk), базовый класс (C++) и класс-предок. Связанность — степень зависимости компонентов программы друг от друга. Сигнатура — под сигнатурой операции понимается сочетание ее имени, параметров и возвращаемого значения. Ссылка на объект — значение, которое идентифицирует другой объект. Супертип — тип родителя, которому наследует данный тип. Схема взаимодействий — схема, на которой показан поток запросов между объектами. Схема классов — схема, на которой изображены классы, их внут ренняя структура и операции, а также статические связи между ними. Схема объекта — схема, на которой изображена структура конкретного объекта во время выполнения. Тип — имя конкретного интерфейса. Черный ящик как способ повторного использования — стиль повторного использования, основанный на композиции объектов. Объекты-компоненты не раскрывают друг другу деталей своего внутреннего устройства и потому могут быть уподоблены черным ящикам. ПРИЛОЖЕНИЕ Б ОБЪЯСНЕНИЕ НОТАЦИИ На протяжении всей книги мы пользуемся схемами для иллюстрации важных идей. Некоторые схемы нестандартны (например, снимок экрана, где изображено диалоговое окно, или схематичное изображение дерева объектов). Но при описании паттернов проектирования для обозначения отношений и взаимодействий между классами и объектами применяется более формальная нотация. В настоящем приложении эта нотация рассма- тривается подробно. Мы пользуемся тремя видами схем: на схеме классов отображены классы, их структура и статические отно- шения между ними; на схеме объектов показана структура объектов во время выполнения; на схеме взаимодействий изображен поток запросов между объектами. В описании каждого паттерна проектирования есть хотя бы одна схема классов. Остальные используются, если в них возникает необходимость. Схема классов и объектов основаны на методологии OMT (Object Modeling Technique — методика моделирования объектов) [RBP+91, Rum94] 1 . Схема взаимодействий заимствованы из методологии Objectory [JCJO92] и метода Буча [Boo94]. 1 В OMT для обозначения схем классов используется термин «схема объектов». Мы же зарезервировали термин «схема объекта» исключительно для описания структуры объекта. 418 Приложение Б. Объяснение нотации Б.1. СХЕМА КЛАССОВ На рис. B.1a представлена нотация OMT для абстрактных и конкретных классов. Класс обозначается прямоугольником, в верхней части которого жирным шрифтом напечатано имя класса. Основные операции класса пере- числяются под именем класса. Все переменные экземпляра располагаются ниже операций. Информация о типе необязательна; мы пользуемся синтак- сисом С++, ставя имя типа перед именем операции (для обозначения типа возвращаемого значения), переменной экземпляра или фактического пара- метра. Курсив служит указанием на то, что класс или операция абстрактны. AbstractClassName AbstractOperation1() Type AbstractOperation2() ConcreteClassName Operation1() Type Operation2() instanceVariable1 Type instanceVariable2 Клиент Клиент Drawing Shape CreationTool LineShape Color Фигуры Drawing Draw() Для каждой фигуры { shape>Draw() } Рис. Б.1. Нотация схем классов: a) абстрактные и конкретные классы; б) класс клиента участника (слева) и класс неявного клиента (справа); в) отношения между классами; г) аннотация на псевдокоде Б.1. Схема классов 419 При использовании некоторых паттернов проектирования полезно видеть, где классы клиентов ссылаются на классы-участники. Если паттерн вклю- чает класс клиента в качестве одного из участников (это означает, что на клиента возлагаются определенные функции), то клиент изображается как обычный класс. Так, например, обстоит дело в паттерне приспособленец (231). Если же клиент не входит в состав участников паттерна (то есть не несет никаких обязанностей), то его изображение все равно полезно, по- скольку проясняет способ взаимодействия участников с клиентами. В этом случае классы клиентов изображаются бледным шрифтом, как показано на рис. Б.1б. Примером может служить паттерн заместитель (246). Бледный шрифт клиента напоминает также о том, что мы специально не включили клиента в состав участников. На рис. Б.1в показаны отношения между классами. В нотации OMT для обо- значения наследования классов используется треугольник, направленный от подкласса (на рисунке — LineShape ) к родительскому классу ( Shape ). Ссылка на объект, представляющая отношение агрегирования «является частью», обозначается линией со стрелкой с ромбиком на конце. Стрелка указывает на агрегируемый класс (например, Shape ). Линия со стрелкой без ромбика обозначает отношение осведомленности (так, LineShape содержит ссылку на объект Color , который может использоваться также и другими фигурами). Рядом с началом стрелки может находиться еще и имя ссылки, позволяющее отличить ее от других ссылок 1 Еще одно полезное свойство, которое следует визуализировать, — то, какие классы создают экземпляры других классов. Для этого используется пун- ктирная линия, поскольку OMT такого отношения не поддерживает. Мы называем их отношениями «создает». Стрелка направлена в сторону класса, экземпляр которого создается. На рис. Б.1в класс CreationTool создает объ- екты класса LineShape 1 В OMT определены также ассоциации между классами, изображаемые простыми ли- ниями, соединяющими прямоугольники классов. Хотя на стадии анализа ассоциации полезны, нам кажется, что их уровень слишком высок для выражения отношений в пат- тернах проектирования, просто потому, что на стадии проектирования ассоциациям следует сопоставить ссылки на объекты или указатели. Ссылки на объекты по сути своей являются направленными и потому лучше подходят для визуализации интересу- ющих нас отношений. Например, классу Drawing (рисунок) известно о классах Shape (фигура), но сами фигуры ничего не «знают» о рисунке, в который они погружены. Выразить такое отношение только лишь с помощью ассоциаций невозможно. 420 Приложение Б. Объяснение нотации В OMT также определено условное обозначение заполненного круга, обо- значающее «более одного». Если такой кружок появляется рядом со стрел- кой, то он говорит о том, что она ссылается на несколько объектов или что несколько объектов агрегируются. Рис. Б.1в показывает, что класс Drawing агрегирует несколько объектов типа Shape Наконец, мы дополнили OMT аннотациями на псевдокоде, которые позво- ляют коротко описать реализацию операций. На рис. Б.1г приведена такая аннотация для операции Draw в классе Drawing Б.2. СХЕМА ОБЪЕКТОВ На схеме объектов представлены только экземпляры. На ней показан мгно- венный снимок объектов в паттерне проектирования. Объектам присваи- ваются имена вида « aSomething », где Something — это класс объекта. Для обозначения объекта используется прямоугольник с закругленными углами (что несколько отличается от стандарта OMT), в котором имя объекта от- делено от ссылок на другие объекты горизонтальной линией. Стрелки ведут к объектам, на которые ссылается данный объект. На рис. Б.2 изображен соответствующий пример. |