Можно сказать, что эта книга не очень полезна. Ведь в ней не описаны никакие ранее неизвестные алгоритмы или приемы программирования. Здесь не предлагается строгой методологии проектирования систем, не делается попытки разработать новую теорию проектирования, а всего лишь документируются существующие приемы. Поэтому допустимо заключить, что данная книга, быть может, и является неплохим руководством начального уровня, но уж опытному специалисту в области объектно-ориентированного проектирования она ни к чему.
Надеемся, вы думаете по-другому. Каталогизация паттернов проектирования важна сама по себе. Она ассоциирует стандартные названия и определения с теми приемами, которыми мы постоянно пользуемся. Если не изучать паттерны проектирования программ, их нельзя будет и усовершенствовать, а придумывать новые будет сложнее.
Данная книга – только начало большой работы. В ней собраны некоторые из наиболее распространенных паттернов проектирования, но узнать о них можно только из устной молвы или путем изучения существующих систем. После прочтения ранних вариантов этой книги многие проектировщики стали записывать, какими паттернами они пользуются. Очевидно, что существующий вариант книги побудит к аналогичной работе еще более широкую аудиторию. Все это, мы надеемся, положит начало движению за документирование опыта практикующих программистов.
В данной главе обсуждается, какое влияние окажут паттерны на развитие проектирования, как они связаны с другими его сторонами и как можно самостоятельно включиться в работу по нахождению и каталогизации паттернов.
Вот несколько вариантов того, как паттерны, описанные в книге, могут повлиять на ваш подход к проектированию объектно-ориентированных программ. Приведенные соображения основаны на нашем опыте повседневной работы с ними.
Изучение работы высококвалифицированных программистов, пишущих на традиционных языках, показало, что нужно уметь пользоваться не только синтаксисом языка, но и такими более крупными концептуальными структурами, как алгоритмы, структуры данных и идиомы [AS85, Cop92, Cur89, SS86], а также планировать шаги по достижению поставленных целей [SE84]. Возможно, проектировщики не задумываются о нотации, используемой для записи проекта. Вероятно, они прилагают основные усилия к тому, чтобы найти аналогии текущей задаче
в тех планах, алгоритмах, структурах данных и идиомах, которыми пользовались раньше или о которых было известно.
Специалисты в области информатики именуют и каталогизируют алгоритмы и структуры данных, но зачастую мы не заботимся о том, чтобы как-то назвать другие виды паттернов. Паттерны дают проектировщикам единую терминологию, которой можно пользоваться для общения, документирования и изучения возможных альтернатив. Система начинает выглядеть менее сложной, поскольку
о ней становится возможным говорить на более высоком уровне абстракции, чем нотация языка проектирования или программирования.
Знание описанных в книге паттернов проектирования помогает понимать существующие объектно-ориентированные системы, в большинстве которых паттерны применяются. Частенько слышатся сетования на то, что наследование
в системах используется запутанно, а проследить поток управления очень трудно. По большей части такие жалобы связаны с непониманием использованных в системе паттернов. С другой стороны, если вы достаточно давно работаете с объектно-ориентированными системами, то, наверное, освоили описываемые здесь паттерны на собственном опыте. Отметим также, что знание паттернов поможет начинающему проектировщику работать так, как работает эксперт.
Понять систему, которая описана в терминах применяемых в ней паттернов проектирования, намного проще. В противном случае для выявления паттернов пришлось бы восстанавливать дизайн по исходным текстам. Наличие единого словаря означает, что вам нет нужды описывать паттерн целиком; достаточно просто назвать его, а читатель поймет, о чем идет речь. Проектировщик, незнакомый с паттернами, должен будет сначала справиться о них, но это все равно проще, чем обратное конструирование.
Мы постоянно применяем паттерны в своих проектах: используем их при выборе имен для классов, как основу для размышлений и обучения хорошему проектированию, а также для описания проектов [BJ94]. Все это достаточно простые случаи работы. Но легко представить себе и более изощренные способы применения паттернов, например основанные на них CASE-средства или гипертекстовые документы.
С помощью объектно-ориентированного проектирования можно создать хороший дизайн, обучить начинающих проектировщиков правильным приемам работы и стандартизовать методики разработки хороших проектов. Обычно метод проектирования определяет символы (как правило, графические) для моделирования различных аспектов проекта, а также набор правил, диктующих, как и когда применять каждый символ; зачастую удается описать проблемы, с которыми пришлось столкнуться в ходе работы над проектом, способы их разрешения и способы оценки полученного результата. Но опыт квалифицированных разработчиков не исчерпывается одними методами проектирования, которые они используют.
Думается, что важным дополнением к методам объектно-ориентированного проектирования как раз и являются наши паттерны. Они показывают, как применять такие базовые приемы, как объекты, наследование и полиморфизм, демонстрируют, как можно параметризовать систему алгоритмом, поведением, состоянием или видом объектов, которые предполагается создавать. Паттерны проектирования позволяют не просто зафиксировать результаты решений, а ответить на многочисленные «почему», возникающие в ходе проектирования. Разделы «Применимость», «Результаты» и «Реализация» в описаниях паттернов помогут вам сориентироваться при принятии решения.
Паттерны проектирования особенно полезны тогда, когда нужно преобразовать аналитическую модель в модель реализации. Вопреки многочисленным заверениям о беспрепятственном переходе от объектно-ориентированного анализа к проектированию, на практике этот процесс никогда не происходит гладко. В гибком проекте, ориентированном на повторное использование, имеются объекты, которых нет в аналитической модели. На проект оказывают влияние выбранные язык программирования и библиотеки классов. Аналитические модели часто приходится пересматривать, чтобы обеспечить повторное использование. Многие паттерны, включенные в каталог, непосредственно связаны с такого рода вопросами, почему мы и называем их паттернами проектирования.
Полноценная методика проектирования основывается не только на паттернах проектирования. Тут могут быть паттерны и анализа, и пользовательского интерфейса, и оптимизации производительности. Но паттерны – очень важная составная часть методики проектирования, которая до сих пор отсутствовала.
Повторно используемое программное обеспечение часто приходится реорганизовывать [OJ90]. Паттерны проектирования помогают вам решить, как именно реорганизовать проект, и могут уменьшить вероятность реорганизации в будущем.
По Брайану Футу (Brian Foote), жизненный цикл объектно-ориентированной программы состоит из трех фаз: прототипирования, экстенсивного роста и консолидации [Foo92].
Фаза прототипирования характеризуется высокой активностью проектировщиков, направленной на то, чтобы программа начала работать. При этом быстро создается прототип, который последовательно изменяется до тех пор, пока не будут решены первоначальные задачи. Обычно на данном этапе программа представляет собой набор иерархий классов, отражающих сущности предметной области. Основной вид повторного использования – это прозрачный ящик и наследование.
После ввода в эксплуатацию развитие программы определяется двумя противоречивыми факторами: программе необходимо отвечать все новым требованиям и одновременно должна повышаться степень ее повторной используемости. Новые требования диктуют необходимость добавления новых классов и операций, быть может, даже целых иерархий классов. Эти требования могут удовлетворяться, когда начинается фаза экстенсивного роста программы. Но данный период не может продолжаться долго. В конце концов, программа становится слишком негибкой
и не поддается дальнейшим изменениям. Иерархии классов более не соответствуют одной предметной области. Напротив, они отражают множество разных предметных областей, а классы определяют совершенно разнородные операции и переменные экземпляров.
Чтобы программа могла развиваться и дальше, ее необходимо пересмотреть
и реорганизовать. Именно в этот период часто создаются каркасы. При реорганизации классы, описывающие специализированные и универсальные компоненты, отделяются друг от друга, операции перемещаются вверх и вниз по иерархии,
а интерфейсы классов становятся более рациональными. В фазе консолидации появляется много новых объектов, часто в результате декомпозиции существующих и использования композиции вместо наследования. Поэтому прозрачный ящик заменяется черным. В связи с непрекращающимся потоком новых требований
и стремлением ко все более высокой степени повторной используемости объектно-ориентированная программа должна вновь и вновь проходить через фазы экстенсивного роста и консолидации.
От этого цикла никуда не деться. Но хороший проектировщик предвидит изменения, которые могут потребовать реорганизации. Он знает, какие структуры классов и объектов позволят избежать реорганизации, его проект устойчив к изменениям требований. Требования проанализированы, выявлены те из них, которые, по всей вероятности, изменятся на протяжении жизненного цикла программы, и в проекте учтено все это.
В наших паттернах проектирования нашли свое применение многие структуры, появившиеся в результате реорганизации. Использование паттернов на ранних стадиях проекта предотвратит реорганизацию в будущем. Но даже если вы не увидели, как применить паттерн, пока не создали всю систему, он все равно «подскажет», как ее можно изменить.
Начало этому каталогу положила докторская диссертация Эриха [Gam91, Gam92]. Почти половина всех паттернов была представлена в этой работе. К моменту проведения конференции OOPSLA ’91 диссертацию официально признали независимым каталогом, и для продолжения работы над ним к Эриху присоединился Ричард. Вскоре после этого к компании примкнул и Джон. Незадолго до конференции OOPSLA ’92 в группу влился Ральф. Мы изо всех сил старались, чтобы каталог был готов к публикации в трудах ECOOP ’93, но вскоре осознали, что статью на 90 страницах не примут. Поэтому пришлось составить краткий реферат каталога, который и был опубликован. Вскоре после этого мы решили превратить каталог в книгу.
Названия, которые мы присваивали паттернам, по ходу дела менялись. «Обертка» (Wrapper) стала декоратором, «клей» (Glue) – фасадом, «холостяк» (Solitaire) – одиночкой, «бродяга» (Walker) – посетителем. Парочка паттернов осталась за бортом, поскольку мы не сочли их достаточно важными. Но в остальном набор паттернов в каталоге почти не менялся с конца 1992 г. Однако сами паттерны эволюционировали очень сильно.
На самом деле заметить, что нечто представляет собой паттерн, несложно. Мы все четверо активно трудимся над созданием объектно-ориентированных систем и обнаружили, что, когда поработаешь с достаточно большим числом таких систем, выявлять паттерны становится просто. Но найти паттерн куда проще, чем описать его, тем более так, чтобы проектировщики, незнакомые с ним, поняли назначение этого приема и уяснили, почему он важен.
Специалисты уже на самых ранних стадиях осознали ценность каталога. Но понять паттерны могли лишь те, кто их уже использовал.
Так как одной из главных целей книги было научить объектно-ориентированному проектированию, то мы пришли к выводу о необходимости улучшить каталог. Увеличили средний объем описания одного паттерна с двух до десяти с лишним страниц, включив подробный раздел «Мотивация» и пример кода. Мы также решили рассматривать различные компромиссы и подходы к реализации паттернов. В результате изучать паттерны стало легче.
Еще одно важное изменение, внесенное не так давно: задаче, которую призван решить тот или иной паттерн, стало уделяться более пристальное внимание. Проще взглянуть на паттерн как на решение, а не как на прием, который можно приспособить под собственные нужды и повторно использовать. Труднее увидеть, когда паттерн подходит, то есть охарактеризовать те задачи, которые он решает, и тот контекст, в котором он является наилучшим вариантом. Проще разглядеть, что делается, чем понять, почему так делается. Понимать назначение паттерна тоже важно, поскольку это помогает определить, какой паттерн стоит применить.
Мы не единственные, кому интересно писать книги, которые содержат каталог паттернов, применяемых специалистами. Мы – часть обширного сообщества, заинтересованного в паттернах вообще и в паттернах, имеющих отношение к программному обеспечению, в частности. Кристофер Александр – это архитектор, который первым начал изучать паттерны в строительстве и разработал «язык паттернов» для их генерирования. Работа Александра постоянно вдохновляла нас. Поэтому уместно и поучительно сравнить его и наши старания. Затем мы обратимся к работам других авторов по паттернам, связанным с программным обеспечением.
Наша работа напоминает работу Александра во многих отношениях. Обе основаны на изучении существующих систем и нахождении в них паттернов. И там, и там есть шаблоны для описания паттернов, хотя и совершенно различные. Работы основаны на естественном языке и примерах, а не на формальных языках.
В обеих для каждого паттерна приводятся обоснования.
Но во многих отношениях наши работы различаются:
• люди строят здания много тысяч лет, так что существует множество классических примеров. Программные же системы мы начали создавать сравнительно недавно, и лишь немногие признаны классикой;
• Александр приводит порядок, в котором следует использовать его паттерны, мы – нет;
• в паттернах Александра упор сделан на проблемах, в то время как паттерны проектирования гораздо подробнее описывают решение;
• Александр утверждает, что его паттерны способны сгенерировать проект всего здания. Мы не считаем, что наши паттерны могут создать законченную программу.
Когда наш коллега (в определенном понимании этого слова) говорит, что можно спроектировать здание, просто применяя паттерны один за другим, то он преследует те же цели, что и методисты объектно-ориентированного проектирования, которые приводят пошаговые правила. Александр не отрицает творческого подхода; некоторые из его паттернов требуют понимания привычек и обычаев людей, которые будут жить в здании, а его вера в «поэзию» проектирования подразумевает, что уровень проектировщика отнюдь не должен ограничиваться владением языком. 1 Но из его описания того, как паттерны генерируют проект, следует, что язык способен сделать процесс проектирования детерминированным и повторяемым.
Точка зрения Александра помогла нам сместить акцент на компромиссы проектирования – различные «силы», которые формируют дизайн. Под влиянием этого человека мы упорно трудились над тем, чтобы понять применимость и последствия каждого из наших паттернов. Идеология работы Александра уберегла нас от волнений по поводу формального представления паттернов. Хотя такое представление, возможно, помогло бы автоматизировать паттерны, мы полагаем, что на данном этапе важнее исследовать пространство паттернов проектирования, а не формализовать его.
С точки зрения Александра, описанные в этой книге паттерны не составляют языка. Принимая во внимание многообразие программных систем, трудно представить себе, как предложить «полный» набор паттернов, из которого можно было бы вывести пошаговые инструкции по созданию приложения. Для некоторых классов приложений, скажем, систем генерации отчетов или ввода данных путем заполнения форм это возможно. Но наш каталог представляет собой лишь набор взаимосвязанных паттернов, мы не претендуем на создание языка паттернов.
Нам кажется маловероятным, что когда-либо будет создан полный язык паттернов для проектирования программ. Но, безусловно, можно создать каталог более полный, чем наш. В него можно было бы включить описание каркасов и способов их применения [Joh92], паттернов проектирования пользовательского интерфейса [BJ94], паттернов анализа [Coa92] и прочих аспектов разработки программ. Паттерны проектирования – это лишь часть куда более широкого языка паттернов в программном обеспечении.
Нашим первым коллективным опытом изучения архитектуры программного обеспечения было участие в семинаре OOPSLA ’91, который проводил Брюс Андерсон (Bruce Anderson). Семинар был посвящен составлению справочника для архитекторов программных систем. Данное событие положило начало целой серии встреч. Последняя из них состоялась в августе 1994 г. на первой конференции по языкам паттернов программ. В результате сформировалось сообщество людей, заинтересованных в документировании опыта разработки программного обеспечения.
Разумеется, ту же цель видели перед собой и другие исследователи. Книга Дональда Кнута «Искусство программирования для ЭВМ» [Knu73] была одной из первых попыток систематизировать знания, накопленные при разработке программ, хотя акцент в ней был сделан на описании алгоритмов. Но даже эта задача оказалась слишком трудной, так что работа осталась незаконченной. Серия книг Graphics Gems [Gla90, Arv91, Kir92] – еще один каталог, посвященный проектированию, хотя и он в основном посвящен алгоритмам. Программа Domain Specific Software Architecture (Архитектура проблемно-ориентированного программного обеспечения), которую спонсирует Министерство обороны США [GM92], направлена на подбор информации архитектурного плана. Исследователи, занятые разработкой баз знаний, стремятся отразить накопленный опыт разработок. Есть и много других групп, задачи которых в той или иной мере сходны с нашими.
Большое влияние на нас также оказала книга Джеймса Коплиена Advanced C++: Programming Styles and Idioms [Cop92]. Описанные в ней паттерны в большей степени, чем наши, ориентированы на C++. Кроме того, в книге приводится много низкоуровневых паттернов. Коплиен всегда был активным членом сообщества проектировщиков, заинтересованных в паттернах. Сейчас он работает над паттернами, описывающими роли людей в организациях, занятых разработкой программ.
Одним из первых, кто стал популяризировать работы Кристофера Александра среди программистов, был Кент Бек (Kent Beck). В 1993 г. он начал вести колонку в журнале The Smalltalk Report, посвященную паттернам в языке Smalltalk. Некоторое время паттерны собирал Питер Коад (Peter Coad). В основном в его работе [Coa92] представлены паттерны анализа.
Что можете сделать лично вы, если вас интересуют паттерны? Прежде всего применяйте их и ищите другие паттерны, которые лучше отражают ваш подход
к проектированию. Разработайте свой словарь паттернов и используйте его, в частности, в беседах с коллегами о своих проектах. Размышляя о проектах и описывая их, не забывайте о словаре.
Во-вторых, критикуйте нас! Каталог паттернов – это плод напряженной работы, не только нашей, но и десятков рецензентов, который делились своими замечаниями. Если вы наткнулись на какую-то проблему или полагаете, что объяснения должны быть более подробными, пишите нам. То же самое относится к любому каталогу паттернов: авторам нужна обратная связь с читателями! Одна из самых полезных особенностей паттернов состоит в том, что они выводят процесс принятия проектных решений из туманной области интуиции. С помощью паттернов авторы могут явно сформулировать, на какие компромиссы им пришлось идти.
А это, в свою очередь, помогает разглядеть, каковы же минусы их паттернов и вступить в осмысленную дискуссию. Не упускайте такой возможности.
В-третьих, выявляйте паттерны, которыми пользуетесь, и фиксируйте их. Включите их в состав документации по своей программе. Вовсе не обязательно работать в научно-исследовательском институте, чтобы находить паттерны. Не стесняйтесь составить свой собственный каталог паттернов, но... пусть кто-нибудь поможет вам облечь его в достойную форму!
В лучших проектах используется много паттернов проектирования. Единое целое образуется в результате их согласованных взаимных действий. Вот что говорит об этом Кристофер Александр: «Можно строить здания, нанизывая паттерны в достаточно произвольном порядке. Такое здание будет просто собранием паттернов. В нем нет плотности. Нет основательности. Но можно объединять паттерны и так, что в одном и том же физическом объеме они будут перекрывать друг друга. Тогда здание получается очень плотным, в небольшом пространстве сосредотачивается много функций. За счет такой плотности здание приобретает основательность.» (A Pattern Language [AIS+77, стр. xli]).