Э. Гамма, Р. Хелм
Скачать 6.37 Mb.
|
ГЛАВА 1 ВВЕДЕНИЕ В ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ Проектирование объектно-ориентированных программ — нелегкое дело, а если они предназначены для повторного использования, то все становит- ся еще сложнее. Необходимо подобрать подходящие объекты, отнести их к различным классам, соблюдая разумную степень детализации, определить интерфейсы классов и иерархию наследования и установить ключевые от- ношения между классами. Дизайн должен, с одной стороны, соответствовать решаемой задаче, с другой — быть общим, чтобы удалось учесть все требо- вания, которые могут возникнуть в будущем. Хотелось бы также избежать вовсе или, по крайней мере, свести к минимуму необходимость перепроек- тирования. Поднаторевшие в объектно-ориентированном проектировании разработчики скажут вам, что создать «правильный», то есть в достаточной мере гибкий и пригодный для повторного использования дизайн, с первой попытки очень трудно, если вообще возможно. Прежде чем считать цель достигнутой, они обычно пытаются опробовать найденное решение на не- скольких задачах, и каждый раз модифицируют его. И все же опытным проектировщикам удается создать хороший дизайн систе- мы. В то же время новички испытывают шок от количества возможных вари- антов и нередко возвращаются к привычным не объектно-ориентированным методикам. Проходит немало времени перед тем, как новички поймут, что же такое удачный объектно-ориентированный дизайн. Очевидно, опытные проектировщики знают какие-то тонкости, ускользающие от новичков. Так что же это? Прежде всего, опытный разработчик понимает, что не нужно решать каждую новую задачу с нуля. Вместо этого он старается повторно воспользоваться 16 Глава 1. Введение в паттерны проектирования теми решениями, которые оказались удачными в прошлом. Отыскав хорошее решение один раз, он будет прибегать к нему снова и снова. Именно благо- даря накопленному опыту проектировщик и становится экспертом в своей области. Во многих объектно-ориентированных системах встречаются повто- ряющиеся паттерны, состоящие из классов и взаимодействующих объектов. С их помощью решаются конкретные задачи проектирования, в результате чего объектно-ориентированная архитектура становится более гибкой, элегантной, и может использоваться повторно. Проектировщик, знакомый с паттернами, может сразу же применять их к решению новой задачи, не пытаясь каждый раз изобретать велосипед. Поясним нашу мысль через аналогию. Писатели редко выдумывают совер- шенно новые сюжеты. Вместо этого они берут за основу уже отработанные в мировой литературе схемы, жанры и образы. Например, персонаж — «тра- гический герой» (Макбет, Гамлет и т. д.), жанр — «любовный роман» (бес- численные любовные романы). Точно так же в объектно-ориентированном проектировании используются такие паттерны, как «представление состоя- ния с помощью объектов» или «декорирование объектов, чтобы было проще добавлять и удалять новую функциональность». Если вы знаете паттерн, многие проектировочные решения далее следуют автоматически. Все мы знаем о ценности опыта. Сколько раз при проектировании вы испы- тывали дежавю, чувствуя, что уже когда-то решали такую же задачу, только никак не сообразить, когда и где? Если бы удалось вспомнить детали старой задачи и ее решения, то не пришлось бы придумывать все заново. Увы, у нас нет привычки записывать свой опыт на благо другим людям да и себе тоже. Цель этой книги состоит как раз в том, чтобы документировать опыт разра- ботки объектно-ориентированных программ в виде паттернов проектирова- ния. Каждому паттерну мы присвоим имя, объясним его назначение и роль в проектировании объектно-ориентированных систем. Мы хотели отразить опыт проектирования в форме, которую другие люди могли бы использовать эффективно. Для этого некоторые из наиболее распространенных паттернов были формализованы и сведены в единый каталог. Паттерны проектирования упрощают повторное использование удачных проектных и архитектурных решений. Представление прошедших проверку временем методик в виде паттернов проектирования делает их более доступ- ными для разработчиков новых систем. Паттерны проектирования помогают выбрать альтернативные решения, упрощающие повторное использование системы, и избежать тех альтернатив, которые его затрудняют. Паттерны улучшают качество документации и сопровождения существующих систем, 1.1. Что такое паттерн проектирования 17 поскольку они позволяют явно описать взаимодействия классов и объектов, а также причины, по которым система была построена так, а не иначе. Проще говоря, паттерны проектирования дают разработчику возможность быстрее найти правильный путь. Ни один из паттернов, представленных в книге, не описывает новые или не- проверенные разработки. В книгу были включены только такие паттерны, которые неоднократно применялись в разных системах. По большей части они никогда ранее не документировались и либо хорошо известны толь- ко в объектно-ориентированном сообществе, либо были частью какой-то удачной объектно-ориентированной системы — ни один источник нельзя назвать простым для начинающих проектировщиков. Таким образом, хотя эти решения не новы, мы представили их в новом и доступном формате: в виде каталога паттернов в едином формате. Хотя книга получилась довольно объемной, паттерны проектирования — лишь малая часть того, что необходимо знать специалисту в этой области. В издание не включено описание паттернов, имеющих отношение к параллелизму, рас- пределенному программированию и программированию систем реального времени. Отсутствуют и сведения о паттернах, специфичных для конкретных предметных областей. Из этой книги вы не узнаете, как строить интерфейсы пользователя, как писать драйверы устройств и как работать с объектно- ориентированными базами данных. В каждой из этих областей есть свои собственные паттерны; возможно, в будущем кто-то систематизирует и их. 1.1. ЧТО ТАКОЕ ПАТТЕРН ПРОЕКТИРОВАНИЯ По словам Кристофера Александера (Christopher Alexander), «любой паттерн описывает задачу, которая снова и снова возникает в нашей работе, а также принцип ее решения, причем таким образом, что это решение можно потом использовать миллион раз, и при этом никакие две реализации не будут полностью одинаковыми» [AIS+77]. Хотя Александер имел в виду паттерны, возникающие при проектировании зданий и городов, но его слова верны и в отношении паттернов объектно-ориентированного проектирования. Наши решения выражаются в терминах объектов и интерфейсов, а не стен и дверей, но в обоих случаях смысл паттерна — предложить решение опре- деленной задачи в конкретном контексте. В общем случае паттерн состоит из четырех основных элементов: 1. Имя. Указывая имя, мы сразу описываем проблему проектирования, ее решения и их последствия — и все это в одном-двух словах. Присваивание 18 Глава 1. Введение в паттерны проектирования паттернам имен расширяет наш «словарный запас» проектирования и по- зволяет проектировать на более высоком уровне абстракции. Наличие словаря паттернов позволяет обсуждать их с коллегами, в документации и даже с самим собой. Имена позволяют анализировать дизайн системы, обсуждать его достоинства и недостатки с другими. Нахождение хороших имен было одной из самых трудных задач при составлении каталога. 2. Задача. Описание того, когда следует применять паттерн. Описание объ- ясняет задачу и ее контекст. Может описываться конкретная проблема проектирования, например способ представления алгоритмов в виде объектов. В нем могут быть отмечены структуры классов или объектов, типичные для негибкого дизайна. Также может включаться перечень ус- ловий, при выполнении которых имеет смысл применять данный паттерн. 3. Решение. Описание элементов дизайна, отношений между ними, их обязанностей и взаимодействий между ними. В решении не описывается конкретный дизайн или реализация, поскольку паттерн — это шаблон, при- менимый в самых разных ситуациях. Вместо этого дается абстрактное опи- сание задачи проектирования и ее возможного решения с помощью некоего обобщенного сочетания элементов (в нашем случае классов и объектов). 4. Результаты — следствия применения паттерна, его вероятные плюсы и минусы. Хотя при описании проектных решений о последствиях часто не упоминают, знать о них необходимо, чтобы можно было выбрать между различными вариантами и оценить преимущества и недостатки данного паттерна. Нередко к результатам относится баланс затрат времени и памя- ти, а также речь может идти о выборе языка и подробностях реализации. Поскольку в объектно-ориентированном проектировании повторное использование зачастую является важным фактором, то к результатам следует относить и влияние на степень гибкости, расширяемости и пере- носимости системы. Перечисление всех последствий поможет вам понять и оценить их роль. Вопрос о том, что считать паттерном, а что нет, зависит от точки зрения. То, что один воспринимает как паттерн, для другого просто примитивный стро- ительный блок. В этой книге мы рассматриваем паттерны на определенном уровне абстракции. Паттерны проектирования — это не то же самое, что свя- занные списки или хештаблицы, которые можно реализовать в виде класса и повторно использовать без каких бы то ни было модификаций. С другой стороны, это и не сложные предметно-ориентированные решения для целого приложения или подсистемы. В этой книге под паттернами проектирования понимается описание взаимодействия объектов и классов, адаптированных для решения общей задачи проектирования в конкретном контексте. 1.2. Паттерны проектирования в схеме MVC в языке Smalltalk 19 Паттерн проектирования именует, абстрагирует и идентифицирует ключе- вые аспекты структуры общего решения, которые и позволяют применить его для создания повторно используемого дизайна. Он выделяет участву- ющие классы и экземпляры, их роли и отношения, а также распределение обязанностей. При описании каждого паттерна внимание акцентируется на конкретной задаче объектно-ориентированного проектирования. В форму- лировке паттерна анализируется, когда следует применять паттерн, можно ли его использовать с учетом других проектных ограничений, каковы будут последствия применения метода. Поскольку любой проект в конечном итоге предстоит реализовывать, в состав паттерна включается пример кода на языке C++ (иногда на Smalltalk), иллюстрирующего реализацию. Хотя в паттернах описываются объектно-ориентированные архитектуры, они основаны на практических решениях, реализованных на основных язы- ках объектно-ориентированного программирования типа Smalltalk и C++, а не на процедурных (Pascal, C, Ada и т. п.) или более динамических объ- ектно-ориентированных языках (CLOS, Dylan, Self). Мы выбрали Smalltalk и C++ из прагматических соображений, поскольку наш опыт повседневного программирования связан именно с этими языками, и они завоевывают все большую популярность. Выбор языка программирования безусловно важен. В наших паттернах подразумевается использование возможностей Smalltalk и C++, и от этого зависит, что реализовать легко, а что — трудно. Если бы мы ориентировались на процедурные языки, то включили бы паттерны наследование, инкапсуляция и полиморфизм. Некоторые из наших паттернов напрямую поддерживаются менее распространенными языками. Так, в языке CLOS есть мультиметоды, которые делают ненужным паттерн посетитель (с. 379). Собственно, даже между Smalltalk и C++ есть много различий, из-за чего некоторые паттерны проще выражаются на одном языке, чем на другом (см., например, паттерн итератор — с. 302). 1.2. ПАТТЕРНЫ ПРОЕКТИРОВАНИЯ В СХЕМЕ MVC В ЯЗЫКЕ SMALLTALK В Smalltalk80 для построения интерфейсов пользователя применяется тройка классов модель/представление/контроллер (Model/View/Controller — MVC) [KP88]. Знакомство с паттернами проектирования, встречающимися в схеме MVC, поможет вам разобраться в том, что мы понимаем под словом «паттерн». 20 Глава 1. Введение в паттерны проектирования MVC состоит из объектов трех видов. Модель — это объект приложения, а представление — его внешний вид на экране. Контроллер описывает, как интерфейс реагирует на управляющие воздействия пользователя. До по- явления схемы MVC эти объекты в пользовательских интерфейсах смеши- вались. MVC отделяет их друг от друга, за счет чего повышается гибкость и улучшаются возможности повторного использования. MVC отделяет представление от модели, устанавливая между ними про- токол взаимодействия «подписка/уведомление». Представление должно гарантировать, что внешнее представление отражает состояние модели. При каждом изменении внутренних данных модель уведомляет все зависящие от нее представления, в результате чего представление обновляет себя. Такой подход позволяет присоединить к одной модели несколько представлений, обеспечив тем самым различные представления. Можно создать новое пред- ставление, не переписывая модель. На следующей схеме показана одна модель и три представления. (Для про- стоты мы опустили контроллеры.) Модель содержит некоторые данные, ко- торые могут быть представлены в форме электронной таблицы, гистограммы и круговой диаграммы. Модель сообщает своим представлениям обо всех изменениях значений данных, а представления взаимодействуют с моделью для получения новых значений. window window window x y z a b c 60 30 10 50 30 20 80 10 10 a b c a c b a=50% b=30% c=20% Модель Представления На первый взгляд, в этом примере продемонстрирован просто дизайн, от- деляющий представление от модели. Но тот же принцип применим и к более общей задаче: разделение объектов таким образом, что изменение одного 1.2. Паттерны проектирования в схеме MVC в языке Smalltalk 21 отражается сразу на нескольких других, причем изменившийся объект не имеет информации о подробностях реализации других объектов. Этот более общий подход описывается паттерном проектирования наблюдатель. Еще одна особенность MVC заключается в том, что представления могут быть вложенными. Например, панель управления, состоящую из кнопок, допустимо представить как составное представление, содержащее вложен- ные — по одной кнопке на каждое. Пользовательский интерфейс инспектора объектов может состоять из вложенных представлений, используемых также и в отладчике. MVC поддерживает вложенные представления с помощью класса CompositeView , являющегося подклассом View . Объекты класса CompositeView ведут себя так же, как объекты класса View , поэтому могут использоваться всюду, где и представления. Но еще они могут содержать вложенные представления и управлять ими. Здесь можно было бы считать, что этот дизайн позволяет обращаться с со- ставным представлением, как с любым из его компонентов. Но тот же дизайн применим и в ситуации, когда мы хотим иметь возможность группировать объекты и рассматривать группу как отдельный объект. Такой подход опи- сывается паттерном компоновщик. Он позволяет создавать иерархию классов, в которой некоторые подклассы определяют примитивные объекты (на- пример, Button — кнопка), а другие — составные объекты ( CompositeView ), группирующие примитивы в более сложные структуры. MVC позволяет также изменять реакцию представления на действия поль- зователя. При этом визуальное воплощение остается прежним. Например, можно изменить реакцию на нажатие клавиши или использовать открыва- ющиеся меню вместо командных клавиш. MVC инкапсулирует механизм определения реакции в объекте Controller . Существует иерархия классов контроллеров, и это позволяет без труда создать новый контроллер как ва- риант уже существующего. Представление пользуется экземпляром класса, производного от Controller , для реализации конкретной стратегии реагирования. Чтобы реализовать иную стратегию, нужно просто подставить другой контроллер. Можно даже заменить контроллер представления во время выполнения программы, изме- нив тем самым реакцию на действия пользователя. Например, представление можно деактивировать, так что он вообще не будет ни на что реагировать, если передать ему контроллер, игнорирующий события ввода. Отношение представление/контроллер — это пример паттерна проектирова- ния стратегия (с. 362). Стратегия — это объект, представляющий алгоритм. Он будет полезен, когда вы хотите статически или динамически подменить 22 Глава 1. Введение в паттерны проектирования один алгоритм другим, если существует много разновидностей одного алго- ритма или когда с алгоритмом связаны сложные структуры данных, которые хотелось бы инкапсулировать. В MVC используются и другие паттерны проектирования, например фабрич- ный метод (c. 135), позволяющий задать для представления класс контрол- лера по умолчанию, и декоратор (с. 209) для добавления к представлению возможности прокрутки. Тем не менее, основные отношения в схеме MVC описываются паттернами наблюдатель, компоновщик и стратегия. 1.3. ОПИСАНИЕ ПАТТЕРНОВ ПРОЕКТИРОВАНИЯ Как мы будем описывать паттерны проектирования? Графические обозна- чения важны, но их одних недостаточно. Они просто символизируют конеч- ный продукт процесса проектирования в виде отношений между классами и объектами. Чтобы повторно воспользоваться дизайном, нам необходимо документировать решения, альтернативные варианты и компромиссы, ко- торые привели к нему. Важны также конкретные примеры, поскольку они демонстрируют практическое применение паттерна. При описании паттернов проектирования мы будем придерживаться единого формата. Описание каждого паттерна разбито на разделы, перечисленные ниже. Такой подход позволяет единообразно представить информацию, облегчает изучение, сравнение и применение паттернов. Название и классификация паттерна Название паттерна должно быть компактным и четко отражающим его назначение. Выбор названия чрезвычайно важен, потому что оно станет частью вашего словаря проектирования. Классификация паттернов про- водится в соответствии со схемой, которая изложена в разделе 1.5. Назначение Краткие ответы на следующие вопросы: что делает паттерн? Почему и для чего он был создан? Какую конкретную задачу проектирования можно решить с его помощью? Другие названия Другие распространенные названия паттерна, если таковые имеются. |