Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию Alan Shalloway, James R. Trott Design Patterns Explained: A New Perspective on Object-Oriented Design 288 стр., с ил.; ISBN 5-8459-0301-7, 0-2017-1594-5; формат 70x100/16; 2002, 3 кв.; Вильямс. Объектно-ориентированное программирование с использованием шаблонов проектирования призвано облегчить работу проектировщиков и разработчиков программного обеспечения. Но изучение и успешное использование этих методов может оказаться достаточно сложным делом. Эта книга дает точное представление о десяти наиболее важных шаблонах проектирования, которые никогда не используются самостоятельно, а только во взаимодействии друг с другом, что и гарантирует надежность создаваемых приложений. Полученных знаний будет вполне достаточно для дальнейшего изучения литературы по шаблонам проектирования, и даже для создания своих собственных шаблонов. Книга предназначена как для профессиональных разработчиков ПО, так и для студентов, изучающих основы ООП.
8
Оглавление Введение ....................................................................................................... 9 Часть I. Введение в разработку объектно-ориентированного программного обеспечения ..................................................................... 23 1 Объектно-ориентированная парадигма .................................................. 25 2 UML — унифицированный язык моделирования .................................. 45 Часть II. Ограниченность традиционного объектноориентированного проектирования ....................................................... 55 3 Проблема, требующая создания гибкого кода ....................................... 57 4 Стандартное объектно-ориентированное решение ................................ 67 Часть III. Шаблоны проектирования .................................................... 77 5 Первое знакомство с шаблонами проектирования ................................. 79 6 Шаблон Facade ......................................................................................... 91 7 Шаблон Adapter ....................................................................................... 97 8 Расширение горизонтов ......................................................................... 107 9 Шаблон Bridge ....................................................................................... 117 10 Шаблон Abstract Factory ...................................................................... 147 Часть IV. Практическое применение шаблонов проектирования ... 163 11 Как проектируют эксперты ................................................................. 165 12 Решение задачи САПР с помощью шаблонов проектирования ......... 173 13 Обработка возможных вариаций с помощью шаблонов проектирования ......................................................................................... 191 Часть V. Обработка вариаций с применением шаблонов проектирования ...................................................................................... 197 14 Шаблон Strategy ................................................................................... 199 15 Шаблон Decorator ................................................................................ 209 16 Шаблоны Singleton и Double-Checked Locking ................................... 221 17 Шаблон Observer .................................................................................. 227 18 Шаблон Template Method .................................................................... 241 19 Шаблон Factory Method ....................................................................... 247 20 Матрица анализа .................................................................................. 251 Часть VI. Завершение и начало ............................................................ 262 21 Шаблоны проектирования и новый взгляд на объектноориентированное проектирование ........................................................... 265 22 Библиография ....................................................................................... 271
9
Введение Шаблоны проектирования и объектно-ориентированное программирование. Именно они в будущем должны облегчить работу проектировщиков и разработчиков программного обеспечения. Относящаяся к этим областям терминология каждый день обсуждается в технической и даже популярной прессе. Но их изучение и успешное использование, равно как и понимание сути используемых терминов, может оказаться достаточно сложным делом. Возможно, вы уже имеете значительный опыт работы с объектноориентированным или базирующимся на использовании объектов языком. Знаете ли вы, что основным преимуществом объектов является вовсе не наследование, а возможности инкапсуляции? Вероятно, вы действительно хотели бы познакомиться с шаблонами проектирования поближе, но соответствующая литература, как правило, написана слишком заумным и сложным языком. Если это так, то данная книга вам очень подойдет. Она составлена на основе многолетней практики преподавания соответствующего материала разработчикам программного обеспечения, как достаточно подготовленным, так и новичкам в области объектной технологии. Опыт показывает, что понимание основных принципов и мотивов, положенных в их основу, а также способов их использования существенно сокращает время и усилия, затрачиваемые на весь процесс обучения. Наше обсуждение шаблонов проектирования позволит вам усвоить основные правила объектной технологии, что необходимо для лучшего понимания предоставляемого материала. Эта книга дает точное представление о десяти наиболее важных видах шаблонов проектирования. Эти шаблоны никогда не используются по отдельности, а только во взаимодействии друг с другом, что и гарантирует надежность создаваемых приложений. Полученных знаний будет вполне достаточно для дальнейшего изучения литературы по шаблонам проектирования и даже для создания своих собственных шаблонов. И что наиболее важно, вы научитесь создавать простое в эксплуатации, достаточно гибкое и эффективное программное обеспечение.
10
От обычной объектной технологии к шаблонам проектирования - действительно объектной технологии Во многом эта книга основана на моем собственном опыте изучения шаблонов проектирования. Прежде, до обращения к ним, я считал себя опытным специалистом в объектно-ориентированном анализе и проектировании. Мой послужной список включал несколько довольно значительных проектов и разработок в различных отраслях промышленности. Я знал язык С++ и приступил к изучению языка Java. Объекты создаваемого мной программного кода отличались хорошей организацией и полнотой инкапсуляции. Я мог создать замечательную структуру абстрактных данных, образующих стройную иерархию наследования. Поэтому я полагал, что достаточно хорошо знаю объектную технологию. Сейчас, оглядываясь назад, я понимаю, что в действительности я не представлял себе всех возможностей объектно-ориентированного проектирования, хотя и в точности выполнял все советы признанных экспертов. До того как я начал изучение шаблонов проектирования, расширение и углубление моих знаний в объектно-ориентированном проектировании шло относительно медленно. Изучение шаблонов проектирования, даже если я и не использовал их в работе непосредственно, помогло мне повысить уровень создаваемых разработок. Я начал изучение шаблонов проектирования в 1996 году. Тогда я возглавлял отдел объектно-ориентированного проектирования, работавший на языке С++, в крупной аэрокосмической компании на северо-западе. Несколько человек объединились в группу по изучению шаблонов проектирования и попросили меня возглавить ее. Именно там я познакомился с моим будущим соавтором Джимом Троттом (Jim Trott). Эта группа привнесла в мою жизнь много нового. Во-первых, мне удалось в полной мере оценить возможности шаблонов проектирования. В процессе изучения я сравнивал собственные разработки с разработками других, более опытных, специалистов. В результате обнаружилось, что при проектировании интерфейсов я часто не использовал все существующие возможности, поскольку не всегда принимал во внимание тип объекта при использовании его другим объектом. Я также заметил, что новички в объектно-ориентированном проектировании - те, кто начал
11
изучение шаблонов проектирования относительно недавно, - получали ровно столько же полезной информации, сколько и опытные специалисты. Шаблоны представляют собой замечательный пример выполнения объектно-ориентированных проектов и иллюстрируют основные принципы объектной технологии, что способствует ускорению выполнения программных проектов. К концу работы группы изучения я был полностью убежден в том, что шаблоны проектирования - это лучшее, что появилось в области методологии разработки программного обеспечения со времени изобретения самого объектно-ориентированного проектирования. Однако к этому моменту, оценив свою работу, я понял, что до сих пор ни разу не включил ни один шаблон проектирования в текст собственных программ. Я пришел к выводу, что моих знаний шаблонов проектирования все еще недостаточно, и необходимо продолжить обучение. К этому времени я был знаком только с шестью из них. Но затем меня, как говорится, осенило. В очередном проекте я отвечал за этап объектноориентированной разработки, который заключался в конструировании высокоуровневой объектной схемы приложения. Руководитель проекта был чрезвычайно резким человеком, но обладал относительно небольшими познаниями в области объектно-ориентированной разработки. Поставленная задача сама по себе не составляла особой сложности, трудность же заключалась в необходимости создания понятного и удобного в эксплуатации кода. Буквально после двухминутного изучения проблемы я набросал схему проекта, руководствуясь своими обычными принципами абстрагирования данных. К сожалению, полученный проект никуда не годился. Абстрактное представление данных само по себе меня уже не устраивало. Необходимо было найти что-нибудь получше. Спустя два часа, применив все известные мне технологии проектирования, я все еще был на том же месте. Изменить исходный проект сколько-нибудь существенным образом мне никак не удавалось. Наиболее удручающим было то, что я знал, что улучшение проекта возможно, но, как этого достичь, не понимал. По иронии судьбы, я также знал о существовании четырех шаблонов проектирования, которые, в
12
целом, подходили под мою проблему, но не представлял, как их использовать. Вот так: предполагаемый эксперт в объектноориентированном проектировании был сбит с толку совсем несложной задачей. Удрученный, я прервал работу и начал ходить по коридору, убеждая себя не думать о проблеме хотя бы в течение 10 минут. Но спустя 30 секунд я уже думал о ней снова! Наконец, совершенно случайно, мне пришло в голову взглянуть на эти шаблоны проектирования в ином свете: вместо того, чтобы использовать их как отдельные элементы, мне следовало рассматривать все четыре шаблона как единое целое. Для решения поставленной задачи шаблоны проектирования необходимо связать в единое целое. Я слышал об этом подходе раньше, но не до конца понимал его. Поскольку шаблоны в теории программирования были представлены как шаблоны проектирования, то в своих работах я всегда предполагал, что в основном они должны использоваться на этапе конструирования. Я считал, что в реальных проектах шаблоны большей частью призваны выполнять связующие функции между классами. Но затем я прочел замечательную книгу Кристофера Александера (Christopher Alexander) Строительство на века (The Timeless Way of Building). Из нее мне стало понятно, что шаблоны проектирования существуют на всех этапах: анализа, разработки и реализации. В своей книге Александер показывает, что шаблоны помогают разобраться во всем, что относится к предметной области решаемой задачи (включая даже и ее описание). Моя ошибка заключалась в попытке выделить отдельные классы в предметной области поставленной задачи с последующим соединением их для получения законченной системы. Как указывал Александер, эту методику нельзя считать удачной. Я никогда не задумывался над тем, верен ли мой способ создания классов, поскольку он был совершенно очевиден. Как только я начинал анализ предметной области, в моем воображении сразу возникали классы, связанные с каждым "существительным" в описании задачи, - как того требует методика проектирования, которой нас обучали. Но впоследствии для соединения полученных классов в единое целое мне всегда требовалось прилагать значительные усилия.
13
Когда я вернулся к работе и попытался применить при разработке классов рекомендуемый Александером подход с использованием шаблонов проектирования, то буквально в течение нескольких минут нашел наиболее подходящее решение. Проект получился удачным, и мы сразу же запустили его в производство. Я был взволнован тем, что сумел найти удачное решение, а также тем, что наконец оценил использование шаблонов проектирования. С этого момента я начал включать шаблоны проектирования в свои разработки и внес их обсуждение в программу обучения. Я обнаружил, что у программистов, только начинающих работать в области объектно-ориентированного проектирования, никогда не возникало трудностей с освоением шаблонов проектирования. На их основе они очень скоро вырабатывали у себя основной набор навыков, необходимых для успешной реализации объектно-ориентированных проектов. Это было очевидно и мне, и моим студентам. Представьте себе мое удивление! Все книги по шаблонам проектирования, которые я прочитал, и все специалисты в этой области, с которыми мне приходилось общаться, в один голос утверждали о необходимости глубокого знания основ объектно-ориентированного проектирования у тех, кто приступает к изучению шаблонов проектирования. Тем не менее, за время своего преподавания я сделал совершенно противоположный вывод, что даже начинающие студенты быстрее осваивают основы объектно-ориентированной разработки, параллельно изучая шаблоны проектирования. При этом собственно шаблоны проектирования они изучают почти так же быстро, как те, кто имеет немалый опыт применения объектной технологии на практике. С тех пор я начал использовать шаблоны проектирования в качестве базового материала при обучении. Я назвал свой курс Проектирование на основе шаблонов - использование шаблонов проектирования на всех стадиях разработки, от анализа и до реализации. Я хотел, чтобы мои студенты разбирались в шаблонах проектирования, и обнаружил, что лучшим способом достичь этого является применение исследовательского подхода. Например, вместо того, чтобы просто описать шаблон Bridge (мост), лучше сформулировать подходящую задачу и попросить студентов решить ее на основе нескольких
14
рекомендуемых принципов и методов, которые всегда можно выделить в большинстве шаблонов проектирования. В процессе поиска студенты сами находят то решение, которое называется шаблоном Bridge, и прочно запоминают его. Как бы там ни было, по моему мнению, подобные рекомендуемые принципы и методы могут использоваться для "выведения" нескольких шаблонов проектирования. Под "выведением шаблона проектирования" я понимаю такой процесс, когда при обдумывании задачи, которая потенциально может быть решена с использованием некоторого шаблона проектирования, применение определенных руководящих принципов и методов позволяет найти решение поставленной задачи, выраженное в контексте этого шаблона. Безусловно, я отдаю себе отчет в том, что для полного освоения студентами шаблонов проектирования этого будет недостаточно. Я лишь демонстрирую им один из возможных вариантов мыслительного процесса тех людей, которые впервые нашли метод решения поставленной задачи. Впоследствии найденный ими метод был классифицирован как шаблон проектирования, рекомендуемый ко всеобщему использованию. Мои способности объяснить эти немногочисленные, но мощные принципы и методы другим требовали постоянного развития. По мере их совершенствования я приходил к заключению, что имеет смысл применять этот подход при изучении все новых и новых шаблонов "банды четырех". В частности, сейчас эти принципы и методы я использую для объяснения 12 из 14 шаблонов, обсуждаемых в курсе изучения шаблонов проектирования. Я также обнаружил, что уже использовал эти принципы в своих проектах, разрабатываемых как с использованием шаблонов, так и без них. И это меня не удивило. Если с помощью подобных методов возможно создание проектных решений, эквивалентных по качеству шаблонам проектирования, значит, они позволяют создавать замечательные проекты (поскольку сами шаблоны по определению являются отличными проектными решениями). И созданный на основе этих принципов и методов проект не становится хуже от того, что я не знаю названия аналогичного шаблона, который, в принципе, может вообще не существовать.
15
Это понимание помогло мне обучать студентов (и писать данную книгу). Я уже читал этот курс лекций нескольким группам обучающихся. Материал данного курса включает основы объектно-ориентированного анализа и проектирования. При изложении материала по шаблонам проектирования я использовал последние, прежде всего, в качестве образцов правильного выполнения объектно-ориентированного анализа и проектирования. Кроме того, использование шаблонов проектирования при изложении концепций объектного ориентирования способствует правильному пониманию студентами принципов объектной технологии. А освоение при обучении упоминавшихся выше основных руководящих принципов и методов впоследствии позволяет студентам создавать проекты, по качеству не уступающие собственно шаблонам проектирования. Все это я рассказываю потому, что данная книга в значительной степени сама является шаблоном читаемого мной курса лекций (извините за каламбур). Фактически, начиная с главы 3, в книге представлен материал, излагаемый в первый день моего двухдневного курса обучения под названием Проектирование на основе шаблонов - использование шаблонов проектирования на всех стадиях разработки, от анализа и до реализации. Прочитав эту книгу, вы будете хорошо разбираться в шаблонах. Но, что еще важнее, вы поймете, как они работают, каждый по отдельности и все вместе, а также усвоите принципы и методы их применения. Эти навыки, несомненно, помогут вам в дальнейшем. Когда в тексте книги я ставлю очередную задачу, попытайтесь вспомнить аналогичную ситуацию, с которой вы сталкивались раньше. Эта книга не содержит никакой новой информации или описания каких-либо новых шаблонов проектирования, но предлагает новый подход к объектно-ориентированному анализу и проектированию программного обеспечения. Я надеюсь, что ваш личный опыт в соединении с изложенными здесь принципами разработки шаблонов проектирования станут вашими надежными союзниками в дальнейшем обучении. Алан Шаллоуей Декабрь, 2000
16
От искусственного интеллекта к шаблонам и действительно объектной технологии Исходная точка, с которой я начал путешествие в мир шаблонов проектирования, несколько отличается от той, которая описана Аланом, но конечные выводы у нас одинаковые. Анализ с использованием шаблонов позволит вам повысить эффективность труда и качество выполняемого анализа, поскольку создаваемые модели будут носить более абстрактный характер и отражать в себе коллективный опыт многих других специалистов. Шаблоны проектирования помогут вам в освоении основных принципов объектной технологии. Они поясняют, что, как и почему можно делать с различными объектами. Я начал свою карьеру в области изучения искусственного интеллекта (artificial intelligence - AI), создавая экспертные системы, управляемые по методу правил. Моя работа заключалась в разработке моделей на основе изучения процесса принятия решений специалистами с последующим программированием этих моделей в правила интеллектуальной системы. При построении подобных систем я обратил внимание на характерную особенность: для решения схожих задач специалисты используют аналогичные приемы. Например, специалисты, выполняющие диагностический контроль оборудования, всегда начинают с поиска мелких, легко устраняемых неисправностей, а затем применяют более систематический подход, разбивая весь процесс на несколько этапов, на которых тестируются отдельные компоненты оборудования. На каждом этапе диагностики сначала выполняются более простые тесты или такие тесты, которые выявляют сразу широкий класс проблем. И только после этого специалист переходит к более сложным тестам различных типов. Указанное правило соблюдается при диагностике и компьютерного оборудования, и добывающих агрегатов нефтяной промышленности. Сегодня я назвал бы эти повторяющиеся схемы шаблонами. При проектировании новых экспертных систем я интуитивно всегда искал схожие черты. Я был готов к восприятию идеи шаблонов проектирования, но не до конца понимал, что они собой в действительности представляют.
17
В 1994 году я узнал, что европейские исследователи запрограммировали повторяющиеся схемы действий специалистов и объединили их в пакет, получивший название Knowledge Analysis and Design Support (Анализ знаний и поддержка проектирования), или KADS. В США первой начала использовать пакет KADS в своей работе Карен Гарднер, талантливый аналитик, разработчик моделей, преподаватель и очень хороший человек. Она продолжила европейские исследования и распространила использование пакета KADS на объектно-ориентированные системы. Она открыла мне мир анализа и проектирования на основе шаблонов - нового этапа в развитии программирования, наибольший вклад в который сделал Кристофер Александер (Christopher Alexander). Описание его работы и ее оценку К. Гарднер приводит в книге Шаблоны познания (Cognitive Patterns, Cambridge University Press, 1998). Неожиданно я понял, как уже на ранних стадиях проектирования, избегая ловушек чрезмерной сложности и учета исключений, можно выполнить моделирование работы специалистов. Мои следующие три проекта были закончены за меньшее время, с меньшими усилиями и были более удобны в использовании. Причин этому несколько. Использование шаблонов способствовало более быстрому конструированию моделей. Шаблоны указывали на наиболее существенные объекты и предупреждали, на что необходимо обратить особое внимание. Я нашел более гибкий способ общения со специалистами, что позволило более упорядоченно работать с мелкими деталями и исключениями из правил. В шаблонах я обнаружил рекомендации по разработке улучшенных методик обучения конечных пользователей моих систем, поскольку шаблоны выделяют наиболее важные функции создаваемой системы. Последний пункт является наиболее важным. Шаблоны помогают конечным пользователям понять особенности системы, поскольку они образуют основной контекст системы, определяющий, почему события в системе происходят именно таким образом. Шаблоны могут применяться для описания основополагающих принципов и особенностей функционирования системы. Кроме того, их можно использовать для
18
выработки наиболее наглядных примеров, помогающих конечному пользователю разобраться в системе. Я был заинтригован и поэтому весьма обрадован созданию по месту моей службы группы по изучению шаблонов проектирования. Именно там я встретил Алана, пришедшего к той же точке зрения на шаблоны проектирования, что и я, но со стороны объектно-ориентированной разработки программ. Результатом нашей совместной работы и стала эта книга. Я надеюсь, что изложенные в этой книге принципы помогут вам научиться принимать более эффективные решения и повысить качество выполнения анализа. Джеймс Р. Тротт Декабрь, 2000
19
Несколько слов о соглашениях, принятых в этой книге При написании этой книги мы заранее обсудили особенности ее стиля и обязанности каждого автора. И поскольку отдельные принятые нами решения могут вызвать у читателя удивление, мы считаем необходимым дать некоторые пояснения. Выбранный подход Изложение от первого лица
Комментарий
Эта книга является плодом совместных усилий двух авторов. Мы много обсуждали и тщательно совершенствовали наше общее представление о наилучшем стиле изложения материала. Использование Аланом материала книги в проводимых им курсах обучения позволило дополнительно улучшить этот стиль. Мы остановились на изложении материала от первого лица, поскольку, как мы считаем, информация при этом подается в более удобной и естественной форме Сканирование Мы постарались сделать книгу максимально легкой для текста сканирования, чтобы читатель мог отыскивать наиболее важные для него данные, даже не читая основную часть текста. Мы всемерно старались достичь легкости восприятия таблиц и маркированных списков. Обсуждение каждого шаблона проектирования дополнено сводной таблицей его основных характеристик. Мы надеемся, что это делает книгу более простой для понимания Фрагменты Эта книга в большей степени предназначена для текста изучения методов анализа и проектирования, нежели для программ нужд практической реализации. Наша главная задача научить вас мыслить на основе шаблонов проектирования, опираясь на собственную интуицию и используя лучшие традиции объектно-ориентированного программирования, зафиксированные в шаблонах проектирования. Главной опасностью для любого программиста является преждевременный переход к
20
этапу реализации системы, когда ее проект еще недостаточно продуман. Зная это, мы намеренно опустили некоторые вопросы реализации. Приведенные в книге примеры текста программ выглядят несколько урезанными и фрагментарными. В частности, ни один из приведенных фрагментов программного кода никогда не проверялся на наличие ошибок. Это вызвано тем, что данный программный код служит лишь для иллюстрации обсуждаемых концепций Методы и Наша книга является вводным курсом. Она поможет вам принципы в дальнейшем успешно продолжить изучение шаблонов проектирования. Вы освоите принципы и методы, вызвавшие к жизни концепцию шаблонов проектирования. Прочитав эту книгу, вы сможете приступить к изучению более сложных учебных пособий или справочных руководств. Последняя глава содержит различные ссылки, которые, как мы считаем, могут оказаться вам полезными Демонстрация Мы пытаемся дать общее представление о шаблонах существующих проектирования и показать вам их возможности, но мы возможностей не стремимся до конца раскрыть смысл каждого из них и (смотрите предыдущее объяснение). Наш подход предоставление следующий. Подумайте, что бы вы хотели показать общего человеку, в первый раз приехавшему в США всего на две впечатления недели? Вероятно, лишь некоторые достопримечательности - чтобы ознакомить его с архитектурой, обществом, дать ему общее представление о городах и огромных пространствах, разделяющих их, скоростных автострадах и кафетериях. Но вы не сможете показать ему всего. Для того чтобы дополнить его знания, можно организовать просмотр слайдов с видами других достопримечательностей и городов - это позволит приезжему углубить его общее впечатление о всей стране. В дальнейшем этот человек сможет уже самостоятельно планировать свои визиты в США. В этой книге мы раскроем перед вами главные концепции шаблонов проектирования, а затем проведем краткий обзор близких с ним областей с тем, чтобы в дальнейшем вы сами могли планировать свои путешествия в мир шаблонов
21
Отзывы читателей Шаблоны проектирования - развивающаяся область знания. Их разработка представляет собой результат усилий исследователей, отыскивающих наиболее оптимальные решения и изучающих фундаментальные принципы объектной технологии. Мы хотим узнать ваше мнение об этой книге. Что в ней сделано хорошо, а что плохо? Есть ли в ней ошибки, требующие исправления? Были ли у вас какие-либо трудности в понимании написанного? Пожалуйста, посетите Web-узел этой книги по адресу: http://www.netobjectives.com/dpexplained. Здесь имеется форма, с помощью которой можно отправить авторам свои комментарии и вопросы. Здесь же вы найдете материалы о наших последних исследованиях.
22
Благодарности Введение почти каждой книги заканчивается благодарностями в адрес тех, кто помогал в ее создании. Мы просто не понимали, насколько эта помощь необходима, пока не начали писать свою собственную книгу. Она является результатом совместной работы многих людей. Их список достаточно большой. Следующие люди особенно помогли нам. Дебби Лафферти (Debbie Lafferty) из издательства Addison-Wesley, которая не уставала ободрять и поддерживать нас в работе. Скотт Бейн (Scott Bain), который терпеливо просматривал нашу работу, давая ценные замечания. И особенно Лей и Джилл, наши терпеливые жены, которые помогли воплотить в реальность мечты о создании этой книги. Отдельные благодарности от Алана. Мне помогло содействие некоторых моих студентов, хотя они об этом, вероятно, и не догадываются. Много раз во время чтения своего курса я испытывал колебания в отношении представления своих новых идей - а не выдаю ли я желаемое за действительное? Однако их энтузиазм при восприятии новых концепций во время чтения курса помог мне отбросить всякую неуверенность. Спасибо Лансу Янгу (Lance Young), Питеру Ширли (Peter Shirley), Джону Террелли (John Terrell) и Карену Аллену (Karen Allen). Они служили мне примером того, насколько постоянным может быть вдохновение. Спасибо Джону Влиссайдесу (John Vlissides) за его вдумчивые комментарии и настойчивые вопросы. Отдельные благодарности от Джима. Доктору Карен Гарднер (Karen Gardner) - руководителю и мудрому учителю в области шаблонов человеческого мышления. Доктору Марелу Норвуду (Marel Norwood) и Артуру Мерфи (Arthur Murphy), моим единомышленникам в области применения KADS и использования шаблонов в анализе. Бреду Ван-Бику (Brad VanBeek), который позволил мне совершенствоваться в этой дисциплине. Алексу Сидею (Alex Sidey), научившему меня дисциплине и тонкостям написания технических книг.
ЧАСТЬ I
Введение в разработку объектно ориентированного программного обеспечения
В этой части В главах этой части рассматривается метод разработки объектноориентирован ного программного обеспечения, основанный на использовании шаблонов, пред ставляющих собой обобщение теоретических исследований и лучших практических решений, найденных за многие годы деятельности разработчиков и пользователей. В качестве средства поддержки будет использоваться унифицированный язык моде лирования UML. Этот подход отличается от положений объектноориентированной парадигмы, сформулированной в 1980х годах, согласно которым разработчику достаточно было отыскать в предоставленных ему исходных требованиях к программному обеспече нию отдельные существительные и создать на их основе объекты. В этой парадигме инкапсуляция рассматривалась как сокрытие внутренней структуры данных, а объек ты должны были содержать как данные, так и методы для работы с ними. Это весьма ограниченное представление, поскольку внимание фокусируется только на принци пах реализации объектов. Однако этого мало.
В данной части обсуждается другая версия объектноориентированной парадигмы, построенная на расширенных определениях основных концепций. Эти определения являются результатом обобщения тех методов и принципов, которые были найдены в процессе разработки и реализации шаблонов проектирования. Новая схема более полно раскрывает суть объектной технологии. Глава
Предмет обсуждения
1
Современное представление о понятии объекта
2
Знакомство с унифицированным языком моделирования UML, представляю щим собой средство наглядного и понятного описания объектно ориентированных проектов
Глава 1. Объектноориентированная парадигма
25
Глава 1
Объектноориентированная парадигма
Введение Данная глава предназначена для ознакомления с объектноориентированной па радигмой, которое будет проведено посредством сравнения и сопоставления ее по ложений с уже знакомыми читателю стандартными положениями структурного про граммирования. Разработка объектноориентированной парадигмы была вызвана трудностями, имевшими место в установившейся ранее практике применения положений стан дартного структурного программирования. Подробное обсуждение этих трудностей позволит нам более полно раскрыть преимущества объектноориентированного про граммирования, а так же углубить понимание его механизма. Изучение материала этой главы не сделает из вас специалиста по объектно ориентированным методам. Более того, здесь даже не предусматривается ознакомле ние читателя со всеми фундаментальными концепциями объектноориентированного подхода. Тем не менее, данная глава является необходимым вступлением к остальной части книги, в которой раскрываются методы правильного объектноориентирован ного проектирования, применяемые экспертами на практике. В этой главе... •
Обсуждается общий метод анализа, называемый функциональной декомпозицией.
•
Рассматривается проблема формирования требований к создаваемому програм мному обеспечению и необходимость постоянного внесения в них изменений (главный бич программирования!).
•
Описывается объектноориентированная парадигма и демонстрируется ее практическое применение.
•
Выявляются некоторые специфические методы объектов.
•
Приводится сводная таблица важнейших терминов объектноориентированной технологии, используемых в этой главе.
26
Часть I. Введение в разработку...
Функциональная декомпозиция: в преддверии появления объектноориентированной парадигмы Давайте начнем с рассмотрения типичного подхода к разработке программного обеспечения. Предположим, что задание заключается в написании программы, кото рая должна выбирать из базы данных хранящиеся в ней описания фигур, а затем ото бражать их. В этом случае вполне уместно было бы начать с определения основных этапов решения поставленной задачи. Например, возможное решение может выгля деть следующим образом. 1. Получить перечень фигур, описания которых сохранены в базе данных. 2. Открыть найденный список. 3. Отсортировать список в соответствии с некоторыми правилами. 4. Последовательно отобразить отдельные фигуры на экране. Любой из перечисленных выше этапов можно разбить на более мелкие этапы, необ ходимые для его реализации. Например, этап 4 можно разделить следующим образом. Для каждой из перечисленных в списке фигур выполнить следующее. 4.1. Определить тип фигуры. 4.2. Получить информацию о расположении фигуры. 4.3. Вызвать соответствующую функцию, которая отобразит фигуру исходя из информации о ее расположении. Этот подход называется методом функциональной декомпозиции, поскольку разра ботчик разбивает (подвергает декомпозиции) проблему на несколько функциональ ных этапов, обеспечивающих ее решение. Мы поступаем так потому, что проще иметь дело с небольшими частями, чем со всей проблемой в целом. Тот же подход можно было бы использовать при написании инструкции по скалолазанию или по сборке ве лосипеда. Мы используем этот подход настолько часто и естественно, что у нас редко возникает вопрос о возможных альтернативах. Основная проблема метода функциональной декомпозиции заключается в том, что он не позволяет какимлибо образом подготовить текст программы к последую щим изменениям, появляющимся по мере ее постепенной эволюции. Чаще всего по добные изменения связаны с необходимостью учесть новый вариант поведения про граммы в уже существующей системе. В нашем примере это может быть создание но вой фигуры или реализация нового способа отображения фигур. Если всю логику реализации выбранной поэтапной схемы поместить в одну большую функцию или единственный модуль, то каждое изменение любого из этапов потребует внесения изменений в эту функцию или модуль. С другой стороны, внесение изменений обычно повышает опасность появления ошибок или других нежелательных последствий. Иначе это можно сформулировать следующим образом.
Глава 1. Объектноориентированная парадигма
27
Множество ошибок появляется при внесении в текст программы изменений. Попробуйте обосновать это утверждение исходя из собственного опыта. Вспомни те, что всякий раз, когда в текст программы необходимо внести изменения, возникает опасение, что изменение текста программы в одном месте может привести к сбою в другом. Почему же это происходит? Должен ли программист анализировать все имеющиеся функции и обращать внимание на способ их использования? Следует ли ему учитывать, как функции могут взаимодействовать друг с другом? Обязан ли он проверять все множество различных подробностей, связанных с каждой функцией, — реализуемую этой функцией логику, компоненты с которыми эта функция взаимодей ствует, данные, которые она использует? Как это часто бывает с людьми, необходи мость учесть при внесении изменений слишком много различных деталей обычно приводит к появлению ошибок. И не имеет значения, сколько усилий было приложено, насколько тщательно был проведен анализ — пользователь просто не может сформулировать все необходимые требования сразу. Слишком много неизвестного несет в себе будущее. Времена меня ются. И так было всегда... Ничто не может предотвратить наступление перемен. Однако это не значит, что к их приходу нельзя подготовиться.
Проблема формулирования требований к создаваемому программному обеспечению Любой опрос среди разработчиков программного обеспечения по качеству пре доставленных им заказчиком требований к создаваемому программному продукту едва ли даст большое разнообразие ответов. Скорее всего они будут следующими. •
Требования являются неполными.
•
Требования по большей части ошибочны.
•
Требования (и пользователи) противоречивы.
•
Требования не описывают поставленную задачу подробно.
Практически нет шансов получить ответ, подобный следующему: “предоставлен ные требования не только исчерпывающе полные, ясные и понятные, но также включают описание всех функциональных возможностей, которые потребуются за казчику в течение пяти последующих лет!” За тридцать лет практической деятельности в области разработки программного обеспечения главный вывод, который мне удалось сделать относительно предостав ляемых заказчиком требований, можно сформулировать следующим образом. Требования к программному обеспечению всегда изменяются. Я также понял, что большинство разработчиков знают это обстоятельство и счи тают его весьма неприятным. Но лишь немногие из них действительно учитывают возможность изменения существующих требований при написании программ. Изменение требований к программному обеспечению происходит вследствие ряда простых причин.
28
Часть I. Введение в разработку...
•
Взгляды пользователей на их нужды изменяются как в результате обсуждений с разработчиками, так и при ознакомлении с новыми возможностями в области программного обеспечения.
•
Представление разработчиков о предметной области меняется по мере созда ния ими программного обеспечения, предназначенного для ее автоматизации, поскольку им становятся известны дополнительные особенности этой области.
•
Появляются все новые вычислительные среды, предназначенные для разработ ки программного обеспечения. (Кто мог пять лет назад предположить, что Webдизайн когдалибо достигнет таких высот, как в настоящее время?)
Все это вовсе не значит, что можно опустить руки и отказаться от сбора полно ценных требований пользователей к создаваемой системе. Напротив, это значит, что необходимо научиться приспосабливать создаваемый программный код к неизбеж ному внесению изменений. Это также значит, что необходимо прекратить обвинять себя (или заказчиков) в том, что является совершенно закономерным.
Требуется внести изменения? Так сделайте это! •
За исключением самых простых случаев, требования изменяются всегда и везде, вне зависимости от того, насколько хорошо был сделан первичный анализ!
•
Вместо того чтобы жаловаться на изменение исходно предоставленных требований, необходимо так модифицировать процесс разработки, чтобы обеспечить эффективную обработку любых возникающих изменений.
Внесение изменений при использовании метода функциональной декомпозиции Вернемся к задаче отображения фигур. Как написать текст программы таким обра зом, чтобы было проще отражать в нем возможные изменения исходных требований? Вместо того чтобы реализовать всю функциональность программы в одной большой функции, можно разбить ее текст на несколько модулей. Например, для реализации этапа 4.3, назначение которого можно сформулировать так: "Вызвать соответствующую функцию, которая отобразит фигуру исходя из ин формации о ее расположении", можно написать модуль, текст которого представлен в листинге 1.1. Листинг 1.1. Применение модульности для уменьшения влияния изменений function: display shape input: тип фигуры, описание фигуры action: switch (тип фигуры) case квадрат: (текст функции отображения квадрата) case круг: (текст функции отображения круга)
Теперь при поступлении требований обеспечить отображение фигур нового типа — например, треугольников — потребуется изменить только этот модуль (звучит обнадеживающе!).
Глава 1. Объектноориентированная парадигма
29
Однако данный подход имеет свои недостатки. Например, в тексте модуля указано, что входными данными для него являются тип и описание фигуры. Однако организация их единообразного описания, подходящего для всех фигур, может быть как возможна, так и не возможна — в зависимости от того, как реализовано сохранение сведений о фи гурах. Что, если описание фигуры в некоторых случаях будет сохраняться в виде масси ва точек? Будет ли предложенная схема применима и в этой ситуации? Несомненно, применение модульного подхода позволяет сделать программу более понятной, что обусловливает и простоту ее сопровождения. Но модульность не всегда гарантирует легкость любого требуемого изменения программы. Подход, который я ранее использовал, имеет существенные недостатки, связан ные с понятиями слабой связности и сильной связанности. Стив МакКоннел (Steve McConnel) в своей книге Code Complete дает замечательное определение как понятия связности (cohesion), так и понятия связанности (coupling). •
Понятие связности относится к тому, насколько "сильна взаимозависимость 1 операций подпрограммы".
В других случаях связность иногда определяют как прозрачность (clarity), поскольку чем больше взаимозависимость операций подпрограммы (или класса), тем понятнее ее структура и назначение. •
Понятие связанности относится к тому, насколько "сильна связь между двумя подпрограммами. Связанность является дополнением понятия связности. Свя зность определяет, насколько тесно внутренние элементы подпрограммы взаимодействуют между собой. Связанность характеризует, насколько тесно подпрограмма связана с другими подпрограммами. При этом целью разработ чика является создание подпрограмм, обладающих внутренней целостностью (сильной связностью) и слабой, прямой, явной и гибкой зависимостью от других подпрограмм (слабая связанность)".
Практический опыт большинства программистов показывает, что изменение функции или части кода программы в одном месте зачастую приводит к неожиданным последствиям совсем в другом месте кода. Этот тип ошибки получил название "нежелательный побочный эффект". Он объясняется тем, что, оказывая необходимое влияние (внося изменение), мы одновременно совершаем совсем ненужное воздейст вие — т.е. вносим ошибку! Хуже всего то, что подобные ошибки часто очень трудно найти, поскольку, делая первоначальное изменение, мы обычно не замечаем той свя зи, которая приводит к нежелательным побочным эффектам (если бы эта связь была замечена, то вносимые изменения имели бы совсем другой характер). В действительности, по поводу ошибок подобного типа можно сделать один пора зительный вывод. На практике ошибки исправляются относительно быстро. Я считаю, что на исправление ошибок затрачивается лишь незначительная часть времени всего процесса внесения изменений и их отладки. Основная часть времени при внесении изменений и их отладке тратиться на обнаружение неполадок и выявле 1 McConnel, S. Code Complete: A Practical Handbook of Software Construction, Redmond, Microsoft Press, 1993.
30
Часть I. Введение в разработку...
ние нежелательных побочных эффектов. Сам же процесс исправления ошибок отно сительно непродолжителен. Поскольку нежелательные побочные эффекты относятся к тому типу ошибок, ко торые труднее всего обнаружить, внесение любого изменения в функцию, обрабаты вающую большое количество разнообразных данных, с высокой степенью вероятно сти приведет к возникновению проблем.
Устранять побочные эффекты очень трудно •
Применение функциональной декомпозиции способствует появлению побочных эффектов, которые очень трудно обнаружить.
•
Большая часть времени, затрачиваемого на сопровождение и отладку, расходуется не на исправление ошибок, а на их обнаружение, а также на поиск решений, позволяющих избежать появления нежелательных побочных эффектов от внесения требуемых исправлений.
При использовании метода функциональной декомпозиции постоянное измене ние требований сводило все мои усилия по разработке и сопровождению программ ного обеспечения на нет. Главные проблемы были связаны с функциями. Внесение изменений в одну группу функций или данных неизбежно приводило к необходимо сти внесения изменений в другую группу функций или данных, что, в свою очередь, затрагивало следующую группу функций и т.д. Функциональная декомпозиция прило жения приводит к каскадным изменениям, подобным снежному кому, скатывающему ся с высокой горы, избежать которых достаточно сложно.
Обработка изменяющихся требований Чтобы найти способ справиться с проблемой изменяющихся требований, а заодно подыскать альтернативу методу функциональной декомпозиции, давайте рассмотрим, как люди обычно подходят к решению стоящих перед ними задач. Представим себя на месте преподавателя. После окончания лекции слушателям предстоит посетить заня тия по другим предметам, но они не знают, где расположены соответствующие ауди тории. В ваши обязанности входит информирование слушателей относительно места проведения их следующих занятий. Если следовать принципам структурного программирования, можно поступить, например, следующим образом. 1. Составить список присутствующих. 2. Для каждого человека в списке выполнить следующее: а) определить предмет, по которому проводится следующее занятие; б) выяснить расположение соответствующей аудитории; в) определить путь от данной аудитории к той, в которой будет проводиться следующее занятие; г) объяснить слушателю, как пройти в искомую аудиторию. Для реализации перечисленных выше действий потребуется уточнить следующее.
Глава 1. Объектноориентированная парадигма
31
1. Способ получения списка присутствующих. 2. Метод получения расписания занятий для каждого слушателя в аудитории. 3. Алгоритм определения пути следования из вашей аудитории в любую указанную аудиторию. 4. Алгоритм управляющей программы, которая работала бы с каждым слушателем в аудитории и выполняла все необходимые этапы. Я сомневаюсь в том, что ктолибо действительно выберет подобный подход. Ско рее всего, преподаватель просто вывесит на стене схему, поясняющую, как пройти в другие аудитории, и сделает вначале занятий следующее объявление: "На стене ауди тории вывешено расписание последующих занятий и схема расположения соответст вующих аудиторий. С их помощью каждый из вас сможет определить свои дальней шие действия". При этом преподаватель будет уверен в том, что все присутствующие смогут получить необходимую информацию о своих последующих занятиях и само стоятельно определить по схеме маршрут своего перемещения. Какая же разница между этими двумя подходами? •
В первом случае, составляя подробные инструкции для каждого слушателя, потребуется не упустить из виду множество мелких деталей. И никто, кроме вас, не несет ответственности за информирование слушателей. Нагрузка просто чрезмерна!
•
Во втором случае просто составляются общие инструкции, и каждому из присутствующих предоставляется возможность самостоятельно найти решение стоящей перед ним задачи.
Главное различие заключается в передаче ответственности. В первом случае от ветственность за все несет только преподаватель, во втором — каждый из слушателей сам отвечает за свои действия. В любом из вариантов предусматривается достижение одного и того же результата, различаются только способы его достижения. Какой же вывод можно сделать из сказанного? Чтобы оценить эффект перераспределения ответственности, давайте посмотрим, что произойдет при введении нового требования. Предположим, было получено указание давать специальный инструктаж для аспи рантов, принимающих участие в занятиях. Допустим, им поручили узнать оценки слушателей по итогам занятия и передать эти сведения в канцелярию по пути в сле дующую аудиторию. В первом варианте потребуется изменить алгоритм управляющей программы, чтобы она смогла отличать аспирантов от обычных слушателей и давать аспирантам специальные инструкции. Вполне вероятно, что подобная корректировка потребует значительных усилий. Однако во втором случае, когда слушатели несут ответственность за себя сами, по требуется лишь вывесить дополнительные указания для аспирантов. Завершающая фраза лектора будет той же самой: "Каждый слушатель может идти в свою следующую аудиторию". При этом все слушатели будут следовать этой инструкции в соответствии со своим индивидуальным расписанием. Различие в требуемых действиях весьма значительное. С одной стороны, каждый раз при появлении новой категории слушателей со специальными инструкциями поведения
32
Часть I. Введение в разработку...
потребуется модифицировать алгоритм управляющей программы. С другой стороны, новые категории слушателей попрежнему будут действовать самостоятельно. Можно выделить три основных отличия второй схемы от первой. •
Каждый слушатель сам несет ответственность за свои действия, тогда как в первом случае за все отвечает управляющая программа. (Для этого лишь необходимо, чтобы каждый слушатель знал, к какому разряду он относиться.)
•
К различным категориям слушателей применяется одна и та же схема взаимо действия (как к аспирантам, так и к обыкновенным слушателям).
•
Нет необходимости иметь сведения об особых обязанностях отдельных слуша телей.
Чтобы полностью понять смысл всего вышесказанного, введем некоторую специ альную терминологию. Мартин Фулер (Martin Fowler) в книге UML Distilled описывает 2 три различных подхода к процессу разработки программного обеспечения. Они представлены в табл. 1.1. Таблица 1.1. Различные подходы к процессу разработки программного обеспечения Название
Описание
На концептуальном уровне
При этом подходе "выявляются основные концепции изу чаемой области задач... концептуальная модель может не значительно или даже вообще не касаться вопросов ее реа лизации в создаваемом программном обеспечении..."
На уровне спецификаций
"В этом случае проектируется само программное обеспече ние, но лишь с точки зрения его интерфейсной части, а не полной реализации"
На уровне реализации
На этом этапе создается собственно текст программ. "В боль шинстве случаев именно этот подход воспринимается как основной, но зачастую оказывается оправданным предвари тельное обращение к подходам предыдущих типов"
Еще раз вернемся к примеру с посещением следующих занятий. Обратите внима ние на то, что преподаватель взаимодействует со слушателями на концептуальном уров! не, т.е. он говорит другим, что необходимо сделать, но не объясняет как это делается. Однако тот путь, который слушатель должен пройти, чтобы попасть в следующую ау диторию, вполне конкретен. Каждый из них следует определенным инструкциям, а это значит, что они действуют на уровне реализации. Взаимодействуя со слушателями на одном уровне (концептуальном), преподава тель закладывает основу их будущих действий на другом уровне (реализации), при этом он не вникает в подробности происходящего и определяет лишь общую схему необходимых действий. Этот механизм может быть весьма эффективным. Давайте попробуем применить его к процессу создания программных продуктов.
2 Fowler, M., Scott, K., UML Distilled: A brief Guide to the Standard Object Modeling Language. 2nd edition. Reading Mass., Addison-Wesley, 1999.
Глава 1. Объектноориентированная парадигма
33
Объектноориентированная парадигма Объектноориентированная парадигма строится на основе концепции объекта. Все в ней сосредоточено на объектах, поэтому текст создаваемых программ также строится на использовании объектов, а не функций. Что же такое объект? Традиционно объект понимается как совокупность данных и методов (объектноориентированный термин для функций). К сожалению, такое оп ределение объекта очень ограничено. Ниже будет дано более емкое определение (оно повторно приводится в главе 8, Расширение горизонтов). Затем мы обсудим данные объекта, которыми могут являться как обычные числа и строки символов, так и дру гие объекты. Преимущество использования объектов заключается в том, что на них возлагается ответственность за их собственное поведение (табл. 1.2). Объекты изначально отно сятся к определенному типу, известному им. Данные объекта позволяют ему опреде лять свое состояние, а программный код объекта обеспечивает его корректное функ ционирование (т.е. выполнение тех действий, для которых он, собственно, предна значен). Таблица 1.2. Объекты и возложенные на них обязанности Тип объекта
Обязанности объекта
Student (Слушатель)
Знать, в какой аудитории он сейчас находится. Знать, в какую ау диторию необходимо перейти. Перейти из данной аудитории в следующую
Instructor (Преподаватель)
Выдать сообщение о том, что необходимо перейти в следующую аудиторию
Classroom (Аудитория)
Предоставить информацию о местонахождении
Direction_giver (Путеводитель)
Определить пары аудиторий. Указать маршрут перехода из од ной аудитории в другую
В нашем примере объекты были определены для каждой сущности в предметной области. Обязанности объектов (т.е. методы) были установлены согласно действиям, выполняемым соответствующими сущностями. Подобный подход согласуется с пра вилом выделения объектов посредством выборки всех присутствующих в требовани ях существительных, а необходимые этим объектам методы устанавливаются при об наружении всех связанных с данными существительными глаголов. Я нахожу этот ме тод несколько ограниченным и далее в книге предлагаю лучший вариант. Тем не менее, этот подход позволяет нам начать работу. Лучшим способом восприятия концепции объекта является представление его как некоей сущности, имеющей определенные обязанности. Хороший стиль проектиро вания предполагает создание объектов, обладающих всей полнотой ответственности за свое собственное поведение, и эта ответственность должна быть четко определена. Именно поэтому на объект, представляющий слушателя, возложена обязанность знать, как пройти из одной аудитории в другую. Рассмотрим объекты с точки зрения тех подходов, которые выделены в работе Фулера.
34
Часть I. Введение в разработку...
•
На концептуальном уровне объекты выступают как совокупность обязательств.
•
На уровне определения спецификаций объекты понимаются как набор методов, которые могут вызываться другими объектами или этим же объектом.
•
На уровне реализации объекты рассматриваются как совокупность программного кода и данных.
К сожалению, изучение и обсуждение методов объектноориентированного про ектирования чаще всего ведется только с точки зрения уровня реализации — в терми нах программного кода и данных, а не с позиций концептуального уровня или уровня определения спецификаций. Однако потенциал последних двух вариантов представ ления объектов также достаточно высок! Поскольку объекты обладают обязанностями и несут полную ответственность за свои действия, обязательно должен существовать способ управлять ими — т.е. указывать, что необходимо сделать. Не забывайте, что объект содержит данные, описывающие его текущее состояние, и методы, реализующие его функциональность. Некоторые методы объекта идентифицируются как методы, доступные для других объектов. Набор этих методов определяет открытый интерфейс объекта (public interface). Например, в предыдущем примере объект Student может включать метод gotoNextClassroom(). Нет необходимости передавать этому методу какиелибо па раметры, поскольку каждый объект Student несет полную ответственность за свое поведение. В частности, ему должно быть известно следующее. •
Что ему необходимо, чтобы перейти в следующую аудиторию.
•
Как получить дополнительную информацию, необходимую для такого перехода.
Первоначально в задаче была определена только одна категория — обыкновенные слу шатели, посещающие различные занятия. Обратите внимание на то, что в аудитории (в моделируемой системе) может присутствовать множество обыкновенных слушателей. Но что делать, если потребуется учитывать существование дополнительных категорий слушателей? Очевидно, что решение, при котором каждый объект student будет обла дать уникальным набором методов, определяющих его функциональность, является не эффективным. Это тем более справедливо при наличии задач, общих для всех слушателей. Более эффективно реализовать ряд методов, общих для всех объектов student, которые каждый из них мог бы использовать или приспосабливать для своих нужд. Целесообразно будет определить объект "типичный слушатель", содержащий все об щеупотребительные методы. Затем на его основе можно будет создавать индивиду альные объекты слушателей, каждый из которых будет поддерживать собственную, необходимую ему информацию. В объектноориентированной терминологии подобный обобщенный объект полу чил название класса. Класс является определением линии поведения объекта. Он со держит полное описание следующих элементов: •
всех элементов данных, входящих в состав объекта;
•
всех методов, которые объект способен выполнять;
•
необходимых способов доступа к имеющимся элементам данных и методам.
Поскольку содержащимся в объекте элементам данных можно присваивать раз личные значения, объекты одного и того же класса будут содержать различные набо
Глава 1. Объектноориентированная парадигма
35
ры конкретных значений данных, но, в то же время, обладать одной и той же функ циональностью (реализуемой методами). Чтобы получить доступ к объекту, предварительно следует сообщить программе, что необходим новый объект такогото типа (т.е. того класса, к которому относится этот объект). Создаваемый новый объект получил название экземпляра класса. Созда ние экземпляра класса называется его реализацией. При использовании объектноориентированного подхода создание приложения имитации перехода слушателей в различные аудитории существенно упрощается. Требуемый алгоритм будет выглядеть следующим образом. 1. Запустить управляющую программу. 2. Создать коллекцию экземпляров объектов, представляющих находящихся в ау дитории слушателей. 3. Послать объекту коллекции сообщение о необходимости перехода в следую щую аудиторию. 4. Объект коллекции уведомляет каждый объект слушателя о необходимости пе рехода в следующую аудиторию. 5. Каждый объект слушателя выполняет такие действия: а) б) в) г)
определяет, в какой аудитории будет проходить следующее занятие; выясняет, как пройти в эту аудиторию; выполняет переход; завершает свою работу.
Указанная схема хорошо работает до тех пор, пока не возникнет необходимость добавить новый тип слушателей — например аспирантов. В этом случае возникает дилемма. Ясно, что коллекция должна включать любые категории слушателей (как студентов, так и аспирантов). Проблема заключается в том, как коллекция должна будет обращаться к собственным составляющим. Посколь ку речь идет о написании программы, то коллекция должна иметь вид массива или яв ляться какимлибо другим объектом подобного типа. Если коллекция будет иметь оп ределенный тип, например RegularStudent, то в нее нельзя будет поместить объек ты типа GraduateStudent. Если же коллекция будет представлять собой некоторый произвольный набор объектов, то нельзя гарантировать, что в нее никогда не будет помещен недопустимый тип объекта (который не способен выполнять операцию "переход в следующую аудиторию"). Решить проблему очень просто. Необходимо иметь общий тип, который содержал бы более одного конкретного типа. В нашем случае это класс Student, включающий в себя как класс RegularStudent, так и класс GraduateStudent. В объектно ориентированной терминологии подобный класс называется абстрактным классом. Абстрактные классы определяют поведение других, родственных им классов. Эти "другие" классы представляют специфический тип родственного поведения. Такой класс обычно называют конкретным классом, поскольку он представляет особую опре деленную, фиксированную реализацию общей концепции. В нашем примере абстрактным классом является класс Student, а два производных класса представлены конкретными классами RegularStudent и GraduateStudent.
36
Часть I. Введение в разработку...
Здесь класс RegularStudent представляет один, а GraduateStudent — другой вариант класса Student. Такой тип отношений между объектами называется "отношением is!a" (является), более формальное название которого — наследование. Таким образом, можно сказать, что класс RegularStudent наследует классу Student. В другом варианте это же от ношение выражают, говоря, что класс GraduateStudent порожден, или является под! классом класса Student. С другой стороны, о классе Student говорят, что это базовый класс, генерализация, или суперкласс, для классов RegularStudent и GraduateStudent. Абстрактные классы для других классов выступают в роли шаблонов. Я использую их для определения методов, реализуемых в их классахпотомках. Абстрактные клас сы могут также содержать общие методы, которые будут использоваться всеми клас самипотомками. Использует класспотомок методы своего родителя или реализует свои собственные, в любом случае в иерархии классов он занимает следующую, более низкую, позицию (что отвечает общему требованию, по которому объекты должны сами отвечать за свое поведение). Все это означает, что теперь можно определить коллекцию, объединяющую лю бые объекты класса Student. Используемый тип ссылки будет при этом иметь значе ние Student, и компилятор сможет проверить, являются ли ссылающиеся на указа тель Student объекты на самом деле экземплярами суперкласса Student. Достигае мые при этом преимущества очевидны. •
Коллекция будет содержать только экземпляры класса Student.
•
Обеспечивается контроль согласования типов (в коллекцию включаются только экземпляры класса Student, которые поддерживают метод "переход в следующую аудиторию").
•
Каждый подкласс класса Student волен реализовать свою функциональность по собственному усмотрению.
Абстрактные классы Абстрактные классы часто описываются как классы, не предусматривающие создания своих экземпляров. Это определение правильно, но только на уровне реализации. В остальных случаях оно оказывается слишком ограниченным. Полезнее будет определить понятие абстрактного класса на концептуальном уровне. На концептуальном уровне абстрактные классы — это просто шаблоны для создания других классов.
Следовательно, с помощью абстрактного класса мы просто присваиваем собственное название некоторому множеству родственных классов. Это позволит нам в дальнейшем рассматривать данное множество как единую концепцию. При использовании объектно"ориентированной парадигмы любую проблему следует рассматривать со всех трех точек зрения.
Поскольку объекты несут полную ответственность за собственное поведение, су ществует множество элементов, доступ к которым со стороны других объектов они разрешать не заинтересованы. Ранее уже упоминалось понятие открытый интерфейс (public interface) — оно охватывает все методы объекта, доступные для любых других объектов. В объектноориентированных системах существует несколько уровней дос тупа к элементам объектов. •
Public (открытый) — доступ разрешен для всех.
Глава 1. Объектноориентированная парадигма
37
•
Protected (защищенный) — доступ разрешен только объектам этого же класса или его классампотомкам.
•
Private (закрытый) — доступ предоставляется только объектам этого класса.
Теперь мы подошли к понятию инкапсуляции. Инкапсуляцию чаще всего опреде ляют как сокрытие данных. Объекты обычно не разрешают доступ извне к своим внутренним даннымчленам (т.е. для этих элементов указывается уровень доступа за! щищенный или закрытый). Однако инкапсуляция представляет собой нечто большее, чем просто сокрытие данных. В общем случае понятие инкапсуляции охватывает любой тип сокрытия. В нашем примере преподаватель не делает различия между студентами и аспиран тами. Сведения о категории слушателя скрыты от него (т.е. конкретный тип объекта Student оказывается инкапсулированным). Как будет показано позже, этот принцип очень важен. Следующее понятие, которое подлежит обсуждению, — полиморфизм. В объектноориентированных языках к объектам часто обращаются по ссылкам определенного типа, представляющего собой тип соответствующего абстрактного класса. Однако в действительности обращение выполняется к экземплярам конкрет ных классов, порожденных от данного абстрактного класса. Таким образом, если объекту с помощью абстрактной ссылки дается указание о реализации какоголибо концептуального действия, то реальное поведение этого объекта будет зависеть от его конкретного производного типа. Термин полиморфизм происходит от слов poly (что значит "много") и morph (что значит "форма"), т.е. до словно оно означает "много форм". Это очень точное название, поскольку одно и то же обращение может иметь следствием множество различных форм поведения. В нашем примере преподаватель говорит слушателям, что они "должны перейти в следующую аудиторию". Однако реальное поведение слушателя будет зависеть от то го, к какой категории он относится (яркий пример полиморфизма).
Обзор объектноориентированной терминологии Термин
Описание
Объект
Сущность, обладающая всей полнотой ответственности за свое поведение. Реализуется посредством включения в текст программы описания класса, содержащего требуемые компоненты данных (переменные, входящие в состав объекта) и методы (связанные с объектом функции).
Класс
Репозиторий методов. Описывает данные"члены объектов. Текст программы организуется вокруг классов.
Инкапсуляция
Обычно определяется как сокрытие данных, но более полное определение подразумевает сокрытие информации любого рода.
Наследование
Позволяет описать класс как специализированный подтип другого класса. Подобные специализированные классы называются производными от базового (или исходного) класса. Базовый класс иногда называют суперклассом, тогда как классы"потомки иногда называют подклассами.
Экземпляр
Конкретный представитель класса (он всегда является объектом).
Реализация
Процесс создания экземпляра класса.
38
Часть I. Введение в разработку...
Полиморфизм
Возможность одним и тем же способом обращаться к различным подклассам некоторого родительского класса. При этом достигаемый результат будет зависеть от конкретного типа того класса"потомка, к которому производится обращение.
Подход
Существует три различных подхода к анализу объектной модели: на концептуальном уровне, на уровне спецификаций и на уровне реализации. Выделение этих подходов позволяет лучше понять механизм взаимодействия между абстрактными классами и их потомками. Абстрактные классы помогают найти решение задачи на концептуальном уровне, а также определить способ взаимодействия объектов любых порожденных от них классов. Каждый класс"потомок предназначен для реализации каких"либо специфических потребностей.
Объектноориентированное программирование в действии Давайте еще раз вернемся к примеру с рисованием фигур, обсуждавшемуся в нача ле этой главы. Как можно реализовать поставленную задачу, используя объектно ориентированный подход? Вспомним основные этапы, выделенные для ее решения. 1. Найти перечень фигур, описания которых сохранены в базе данных. 2. Открыть найденный список. 3. Отсортировать список в соответствии с некоторыми правилами. 4. Последовательно отобразить отдельные фигуры на экране. Для решения поставленной задачи с использованием объектноориентированного подхода потребуется определить необходимые объекты и распределить между ними обязанности. Будем считать, что для решения задачи необходимы объекты, перечисленные в табл. 1.3. Таблица 1.3. Объекты, необходимые для решения задачи рисования фигур Класс
Обязанности (методы)
ShapeDataBase
getCollection — извлекает заданный набор фигур
Shape (абстрактный класс)
display — описывает интерфейс для фигур getX — возвращает координату X фигуры (используется для сортировки) getY — возвращает координату Y фигуры (используется для сортировки)
Square (производный от класса Shape)
display — отображает квадрат (представленный этим объектом)
Circle (производный от класса Shape)
display — отображает окружность (представленную этим объектом)
Глава 1. Объектноориентированная парадигма
39
Окончание таблицы Класс
Обязанности (методы)
Collection
display — посылает всем входящим в коллекцию объек там сообщение о необходимости отображения представ ляемых ими фигур sort — сортирует коллекцию объектов
Display
drawLine — выводит на экран линию drawCircle — выводит на экран окружность
В этом случае алгоритм основной программы может быть следующим. 1. Основная программа создает экземпляр объекта, представляющего базу данных. 2. Основная программа посылает объекту базы данных указание отыскать сведе ния о всех затребованных фигурах и создать коллекцию объектов, содержащих сведения по отдельным фигурам (в действительности в коллекцию будут поме щены только два типа объектов: circle и square, поскольку наш объект кол лекции может содержать только эти типы объектов). 3. Основная программа посылает объекту коллекции указание упорядочить по мещенные в нее объекты. 4. Основная программа посылает объекту коллекции указание вывести изображе ния фигур на экран. 5. Объект коллекции посылает каждому входящему в него объекту указание вы вести на экран изображение представляемой им фигуры. 6. Каждый объект фигуры самостоятельно осуществляет вывод на экран той фи гуры, которую он представляет, посылая соответствующие сообщения объекту Display. Давайте посмотрим, как предложенная выше схема позволяет учитывать измене ние существующих требований (не забывайте, что требования к программному обес печению постоянно изменяются). Допустим, что изменение требований заключается в следующем. •
Добавить новый тип фигуры (например треугольник). Для введения нового типа фигуры необходимо выполнить только следующие два действия: − создать новый класс (производный от класса Shape), предназначенный для описания вновь добавляемой фигуры; − в этом новом классе реализовать такую версию метода отображения, которая будет соответствовать добавляемой фигуре.
•
Изменить алгоритм сортировки. Для изменения метода, отвечающего за сортировку фигур, необходимо выполнить только одно действие: − внести требуемые изменения в метод сортировки объекта Collection, при этом новый алгоритм будет использоваться для упорядочения фигур всех типов.
Можно сделать вывод, что применение объектноориентированного подхода сни жает влияние изменения требований на систему.
40
Часть I. Введение в разработку...
Кроме того, определенные преимущества достигаются и за счет инкапсуляции. Тот факт, что инкапсуляция позволяет скрывать информацию от пользователя, имеет следующие следствия. •
Упрощается использование отдельных объектов, поскольку пользователь не вникает в подробности способа их реализации.
•
Способ реализации объекта может быть изменен без необходимости дополни тельной проверки обращающихся к нему объектов. (Поскольку изначально вызывающие объекты не имели никаких сведений о реализации того объекта, к которому они обращаются, то изменение его реализации не будет иметь для них никакого значения.)
•
Внутренние компоненты объекта неизвестны другим объектам — они использу ются только этим объектом с целью реализации функциональности, определя емой его интерфейсом.
И наконец, рассмотрим проблему нежелательных побочных эффектов, которая часто возникает при изменении функций. Устранение ошибок такого рода весьма эффективно обеспечивается инкапсуляцией. Внутренние компоненты объектов неиз вестны другим объектам. Если использовать принцип инкапсуляции и следовать пра вилу, согласно которому объект должен сам отвечать за свое поведение, то оказать на объект некоторое воздействие можно будет только посредством вызова его методов. В результате данные объекта и механизмы реализации его функциональности оказы ваются полностью защищенными от изменений со стороны других объектов.
Инкапсуляция — эффективное средство защиты •
Чем больше на объект возлагается ответственности за его поведение, тем меньше ответственности остается на долю управляющей программы.
•
Инкапсуляция позволяет сделать внесение изменений во внутренние механизмы объекта совершенно прозрачным для других объектов.
•
Инкапсуляция помогает предотвратить нежелательные побочные эффекты.
Специальные методы объектов Выше обсуждались методы, которые либо используются самим объектом, либо вы зываются другими объектами. Но как происходит создание объектов или их удаление? Поскольку объекты являются самодостаточными единицами, вероятно, полезно было бы иметь методы, обрабатывающие эти специфические ситуации. Действительно, такие специальные методы существуют и называются, соответст венно, конструкторами (constructor) и деструкторами (destructor). Конструктор — это специальный метод, который вызывается при создании объек та. Его назначение — обеспечить создание данного объекта. Это одна из важных сто рон ответственности объекта за свое собственное поведение. Конструктор — это са мое подходящее место для инициализации элементов объекта, установки значений данныхчленов, принимаемых по умолчанию, определения отношений с другими объектами и выполнения любых других действий, необходимых для создания объек
Глава 1. Объектноориентированная парадигма
41
тов, полностью подготовленных к работе. Все объектноориентированные языки поддерживают методыконструкторы и организуют их вызов при создании объектов. Правильное применение конструкторов позволяет устранить (или по крайней ме ре сократить) использование неинициализированных переменных. Этот тип ошибок является обычным следствием проявленной разработчиком небрежности. Когда су ществует единая схема и некоторое фиксированное место (в данном случае это кон структоры объектов) инициализации всех элементов текста программы, можно быть уверенным в том, что эта инициализация действительно произойдет. Ошибки, вы званные обращением к неинициализированным переменным, легко исправить, но трудно найти. Поэтому подобное соглашение (автоматический вызов метода конструктора) позволяет повысить эффективность работы программистов. Деструктор — это специальный метод, обеспечивающий необходимую очистку ис пользуемых ресурсов перед тем, как объект перестанет существовать; т.е. перед унич тожением объекта. Все объектноориентированные языки поддерживают методы деструкторы и выполняют их вызов при удалении объекта. Деструктор обычно используется для освобождения ресурсов, использовавшихся объектом при выполнении возложенных на него задач. Поскольку язык Java включает встроенные средства автоматической сборки мусора (автоматического удаления объ ектов, уже выполнивших свою задачу), в языке Java функциидеструкторы не так важ ны, как в языке C++. В языке C++ деструкторы объектов обычно включают действия по уничтожению других объектов, использовавшихся только данным объектом.
Резюме В этой главе было показано, как применение объектной технологии может осла бить влияние частых изменений требований к системе. Кроме того, мы выяснили, чем объектная технология отличается от метода функциональной декомпозиции. Были рассмотрены важнейшие концепции объектноориентированного програм мирования и представлены основные используемые термины с пояснением их смысла и назначения. Усвоение этого материала очень важно для успешного восприятия концепций, обсуждаемых в остальной части этой книги (табл. 1.4 и 1.5). Таблица 1.4. Основные концепции объектно!ориентированного программирования Концепция
Краткое описание
Функциональная декомпозиция
Программисты, использующие структурированный подход, обычно начинают работу над проектом с выполнения функциональной де композиции. Метод функциональной декомпозиции заключается в разбиении задачи на более мелкие подзадачи (функции). Каждая функция, в свою очередь, разбивается на более мелкие до тех пор, пока каждая полученная функция не станет легко реализуемой
Изменение требований
Изменение требований пользователя к системе — это неотъем лемая часть общего жизненного цикла программного обеспече ния. Вместо того, чтобы обвинять пользователя (или себя) по поводу мнимой невозможности выработки корректных и пол ных требований к системе, следует использовать такие методы проектирования, которые позволят эффективно модернизиро вать систему в соответствии с изменяющимися требованиями
42
Часть I. Введение в разработку...
Окончание таблицы Концепция
Краткое описание
Объекты
Объекты определяются по кругу выполняемых ими обязанностей. Использование объектов упрощает алгоритм работы программы, поскольку каждый объект сам отвечает за свое поведение
Конструкторы и де структоры
Объект включает следующие специальные методы, которые вы зываются при его создании и удалении: ∑ конструкторы, которые выполняют инициализацию или ис ходную настройку объекта; ∑ деструкторы, которые уничтожают объект при его удалении. Все объектноориентированные языки поддерживают методы конструкторы и деструкторы, позволяющие упростить управле ние объектами
Таблица 1.5. Объектно!ориентированная терминология Термин
Определение
Абстрактный класс
Определяет методы и общие свойства некоторого множества классов, подобных друг другу на концептуальном уровне. Реали зация абстрактных классов невозможна
Свойство
Данные, принадлежащие объекту (также называются данными членами класса)
Класс
"Проект" объекта содержит описание методов и данных объек тов соответствующего типа
Конструктор
Специальный метод, который вызывается при создании экземп ляра объекта
Инкапсуляция
Некоторый вид сокрытия информации. Объекты инкапсулиру ют свои данные. Абстрактные классы инкапсулируют свои по рожденные конкретные классы
Порожденный класс
Класс, который является специализацией своего суперкласса. Включает все свойства и методы суперкласса, но может также включать собственные свойства и иную реализацию методов су перкласса
Деструктор
Специальный метод, который вызывается при удалении объекта
Функциональная декомпозиция
Один из методов анализа, при котором поставленная задача разбивается на более мелкие подзадачифункции
Наследование
Механизм получения классом специальных признаков, устанав ливающий связь между классамипотомками и классами родителями (возможно, абстрактными)
Экземпляр
Конкретный объект, относящийся к некоторому классу
Реализация
Процесс создания экземпляра класса
Глава 1. Объектноориентированная парадигма
43
Окончание таблицы Концепция
Краткое описание
Компонент
Отдельный элемент данных или метод класса
Метод
Функция, входящая в состав объекта
Объект
Сущность, наделенная определенными обязанностями. Особая, самодостаточная структура, содержащая как данные, так и мето ды, манипулирующие этими данными. Данные объекта защище ны от доступа со стороны внешних объектов
Полиморфизм
Способность родственных объектов различным образом реали зовывать методы, присущие их типу
Суперкласс
Класс, от которого происходят другие классы. Содержит базовые определения свойств и методов, которые будут использоваться всеми классамипотомками (возможно, с переопределением)
44
Часть I. Введение в разработку...
Глава 2
UML — унифицированный язык моделирования
Введение Эта глава содержит краткий обзор основных понятий языка UML (Unified Model ing Language — унифицированный язык моделирования). UML — это специализиро ванный язык моделирования, который используют специалисты, работающие с объ ектноориентированными технологиями. Если вы еще не знакомы с языком UML, то, прочитав эту главу, вы получите те минимальные знания, которые необходимы для ясного понимания всех рисунков и диаграмм, содержащихся в книге. В этой главе представлено следующее. •
Общее описание языка UML и его назначения.
•
Диаграммы языка UML, широко используемые в этой книге: − диаграмма классов; − диаграмма взаимодействий.
Что такое язык UML Язык UML является визуальным языком (использующим графические элементы с буквенными обозначениями), который предназначен для создания моделей прог рамм. Под моделями программ понимается графическое представление программ в виде различных диаграмм, отражающих связи между объектами в программном коде. Язык UML включает несколько типов диаграмм: одни из них предназначены для проведения анализа, другие — для проектирования, третьи — для целей реализации (или, выражаясь более точно, для распределения программного кода между объектами). Назначение различных диаграмм описано в табл. 2.1. Каждая диаграмма отражает те или иные связи между различными наборами сущностей в зависимости от ее типа. Таблица 2.1. Диаграммы языка UML и их назначение Когда используются
Наименование
На стадии анализа
∑ Диаграммы прецедентов (Use Case Diagrams). Пред ставляют различные сущности, взаимодействующие с системой (например, пользователей или другие про граммные системы), и указывают функции, которые необходимо реализовать в создаваемой системе.
46
Часть I. Введение в разработку...
Окончание таблицы Когда используются
Наименование ∑ Диаграммы видов деятельности (Activity Diagrams). Предназначены для представления последователь ностей бизнесопераций, существующих в проблем ном домене. Под последним подразумевается та ре альная среда, в которой работают люди и функцио нируют другие агенты — т.е. предметная область программной системы. Примечание. Поскольку в книге обсуждаются преиму щественно вопросы проектирования, то диаграммы прецедентов и диаграммы видов деятельности в ней не используются
Изучение взаимодействия объектов
∑ Диаграммы взаимодействий (Interaction Diagrams). Представляют способы взаимодействия между собой конкретных объектов. Поскольку они чаще используют ся для рассмотрения конкретных случаев, нежели об щих ситуаций, то представляют наибольший интерес при контроле исходных требований к проектам или при проверке самих проектов. Наиболее распространенным типом диаграмм взаимодействий является Диаграмма последовательностей (Sequence Diagram)
На этапе проектирования
∑ Диаграммы классов (Class Diagrams). Детально представляют различные виды взаимодействия от дельных классов системы
Изучение поведения объектов в зависимости от того со стояния, в котором они на ходятся
∑ Диаграммы состояний (State Diagrams). Подробно представляют различные состояния, в которых могут находиться объекты, а также возможные переходы между этими состояниями
На этапе реализации
∑ Диаграммы развертывания (Deployment Diagrams). Показывают, как разворачиваются различные моду ли. В данной книге диаграммы этого типа обсуждать ся не будут
Для чего используется язык UML Прежде всего язык UML — это инструмент общения с самим собой, членами ко манды и клиентами. Некорректно сформулированные требования к программному продукту (неполные или неточные) — широко распространенное явление в области разработки программного обеспечения. Язык UML позволяет разработать более ка чественные исходные требования. С помощью языка UML можно определить, является ли достигнутое при анализе понимание системы адекватным. Поскольку большинство систем обладает сложной структурой и включает различного рода информацию, которую требуется обрабаты вать так или иначе, разработчику предлагается набор диаграмм, различающихся по типу представляемой на них информации.
Глава 2. UML — унифицированный язык моделирования
47
Проще всего оценить достоинства языка UML, если вспомнить несколько послед них обсуждений проектов, в которых вам приходилось принимать участие. Если на подобных обсуждениях ктолибо сообщает о написанном им коде и пытается охарак теризовать его без использования языка моделирования, подобного UML, то, веро ятнее всего, разговор окажется путанным и бесконечным. Язык UML предлагает не только оптимальный путь описания проектов, созданных с применением объектных технологий, но также вынуждает разработчика более четко формулировать исполь зуемые им принципы (поскольку их требуется изложить в письменном виде).
Диаграмма классов Одной из основных диаграмм языка UML является диаграмма классов. Она описы вает классы и отражает отношения, существующие между ними. Возможны следую щие типы отношений. •
Отношение is!a — в этом случае один класс является подвидом другого класса.
•
Когда существует взаимосвязь между двумя классами: − отношение has!a — т.е. один из классов "содержит" другой класс; − отношение uses — т.е. один класс "использует" другой класс.
Обсуждение этой темы можно продолжить. Например, выражение "одно содержит другое" может означать следующее. •
Один объект является частью другого объекта, который его содержит (как двигатель в автомобиле).
•
Имеется набор (коллекция) объектов, которые могут существовать сами по себе (как самолеты в аэропорту).
Первый пример получил название объединение (composition), а второй — агрегация 1 (aggregation). На рис. 2.1 представлено несколько важнейших моментов. Прежде всего, каждый прямоугольник здесь соответствует определенному классу. В языке UML представле ние класса может включать до трех элементов. •
Имя класса.
•
Имена данныхчленов класса.
•
Имена методов (функций) класса.
1 Гамма (Gamma), Хелм (Helm), Джонсон (Johnson) и Влиссайдес (Vlissides) (“банда четырех”) первый пример назвали “aggregation” (агрегация), а второй — “composition” (соединение) — как раз наоборот по отношению к тому, как это принято в языке UML. Однако книга “банды четырех” была написана раньше завершения разработки языка UML. Тем не менее, представленные определе! ния сейчас чаще ассоциируются именно с языком UML. Это иллюстрирует один из важнейших мо! тивов создания языка UML — до его появления существовало несколько различных языков моделиро! вания и каждый имел собственные понятия и термины.
48
Часть I. Введение в разработку...
ÐÈÑ. 2.1. Диаграмма классов — три возможных варианта пред! ставления класса
На диаграмме представлены три различных способа описания класса. •
Прямоугольник слева содержит лишь имя класса. Этот тип представления класса используется, если отсутствует необходимость в указании более детальной информации о данном классе.
•
Средний прямоугольник содержит имя как класса, так и его метода. В данном случае 2 можно сделать вывод, что класс Square имеет метод display(). Символ плюс (+) перед словом display (именем метода) означает, что этот метод является открытым — т.е. его могут вызывать не только объекты данного класса.
•
Прямоугольник справа, кроме предыдущих двух понятий (имя и методы класса), представляет и данноечлен класса. В этом случае знак минус (-) перед данным членом length (которое имеет тип double) означает, что оно является закрытым — т.е. оно недоступно никаким другим объектам, кроме того, 3 которому оно принадлежит.
Условные обозначения языка UML, описывающие возможности доступа Средства языка UML позволяют контролировать доступ к данным"членам и методам классов. В частности, для каждого существующего члена класса можно указать присвоенный ему уровень доступа. В большинстве языков программирования приняты три уровня доступа.
•
Public (открытый) — обозначается знаком плюс (+). Означает, что эти данные или методы могут использовать любые объекты.
•
Protected (защищенный) — обозначается знаком номер (#). Означает, что только данный класс и все его потомки (а также все классы"потомки, порожденные от его потомков) могут получать доступ к этим данным или методам.
•
Private (закрытый) — обозначается знаком минус (-). Означает, что только методы данного класса могут получать доступ к этим данным или методам. (Примечание: в некоторых языках программирования это ограничение усиливается до конкретного экземпляра объекта.)
На диаграмме классов можно также указать отношения между различными класса ми. На рис. 2.2 показано отношение, существующее между классом Shape и несколь кими порожденными от него классами.
2
Здесь и далее при указании имени класса оно будет выделяться полужирным шрифтом. В некоторых языках программирования объекты одного типа могут совместно использовать закрытые данные!члены друг друга. 3
Глава 2. UML — унифицированный язык моделирования
49
ÐÈÑ. 2.2. Диаграмма классов, представляющая отношение isa
На рис. 2.2 представлено несколько моментов. Вопервых, стрелка под классом
Shape означает, что другие классы, указывающие на класс Shape, являются его по томками. Далее, дополнительное выделение имени класса Shape курсивом означает, что этот класс является абстрактным. Абстрактный класс — это такой класс, который используется только для определения интерфейса своих классовпотомков. Фактически существует два различных вида отношения has!a. Один объект может содержать другой объект, который либо является его частью, либо нет. На рис. 2.3 показан класс Airport (аэропорт), содержащий класс Aircraft (летательный аппа рат). Класс Aircraft не является частью класса Airport, но можно сказать, что класс Airport содержит его. Этот тип отношений получил название агрегации.
ÐÈÑ. 2.3. Диаграмма классов, представляющая отно! шение hasa
На этой диаграмме также показано, что от класса Aircraft порождены классы Jet (реактивный самолет) и Helicopter (вертолет). Можно сделать вывод, что класс Aircraft является абстрактным классом, поскольку его имя выделено курсивом. А это значит, что класс Airport будет включать либо объект класса Jet, либо объект класса Helicopter, но обращаться с этими объектами он будет одинаково — как с объектом класса Aircraft. Пустой (незакрашенный) ромб с правой стороны класса Airport указывает на отношение агрегации между ним и классом Aircraft. Второй тип отношений has!a состоит в том, что вложенный объект является ча стью того объекта, в котором он содержится. Такой тип отношений получил название объединение.
50
Часть I. Введение в разработку...
На рис. 2.4 показано, что класс Tires (шины) является частью класса Car (машина) — т.е. машина состоит из шин и, вероятно, других деталей. Подобный тип отношений has!a, называемый объединением, представляется закрашенным ромбом. На этой диаграмме также показано, что класс Car использует класс GasStation (автозаправочная станция). Отношение использования представлено пунктирной линией со стрелкой. Иначе его иногда называют отношением взаимосвязи.
ÐÈÑ. 2.4. Диаграмма классов, представляю! щая отношения объединения и использования
Как объединение, так и агрегация предполагают, что объект содержит один или более других объектов. Но объединение подразумевает, что один объект является ча стью другого объекта, тогда как агрегация означает, что вложенные объекты пред ставляют собой скорее некоторый набор или коллекцию самостоятельных элементов. Объединение можно рассматривать как нераздельную взаимосвязь, когда время суще ствования вложенных объектов определяется содержащим их объектом. В данном случае функции создания и удаления вложенных объектов целесообразно возложить на методы конструктора и деструктора включающего их объекта.
Примечания в языке UML На рис. 2.5 представлено новое обозначение — примечание. Примечанием здесь является элемент, в котором содержится текст "незакрашенный ромб означает агрегацию". На диаграммах примечания выглядят как листок бумаги с отвернутым правым углом. Часто они изображаются с линией, соединяющей их с конкретным классом, — таким образом выражается их принадлежность к данному классу.
ÐÈÑ. 2.5. Диаграмма классов с примечанием
Глава 2. UML — унифицированный язык моделирования
51
Диаграммы классов отображают отношения между классами. Однако отношения объединения и агрегации могут потребовать указать дополнительные сведения об объектах какоголибо конкретного типа. Например, верно, что класс Airport содер жит класс Aircraft, но точнее будет сказать, что конкретные аэропорты содержат конкретные самолеты. Может возникнуть вопрос: "Сколько самолетов может принять аэропорт?". С помощью этого вопроса мы вводим такое понятие, как кардинальность элементов отношения. Способы представления кардинальности на диаграммах клас сов показаны на рис. 2.6 и 2.7.
ÐÈÑ. 2.6. На диаграмме классов указана кардинальность отношения Airport!Aircraft
ÐÈÑ. 2.7. На диаграмме указаны классы кардинальности отношения Car!Tire
На рис. 2.6 показано, что объект класса Airport может включать от 0 до произ вольного числа (здесь оно обозначено символом звездочки, но иногда может быть представлено буквой n) объектов класса Aircraft. Цифры 0..1 со стороны класса Airport указывают, что объект класса Aircraft может принадлежать или только одному объекту класса Airport, или вовсе не принадлежать никакому объекту (например, самолет может находиться в полете). На рис. 2.7 показано, что объект класса Car может включать либо 4, либо 5 шин (машина может иметь запасную шину или не иметь ее). Шины всегда принадлежат только одной машине. Некоторые полагают, что отсутствие спецификации карди нальности отношения указывает на то, что в ней принимает участие только один объ
52
Часть I. Введение в разработку...
ект. Однако это неправильно. В действительности, если спецификации кардинально сти отношения на диаграмме нет, мы не имеем права делать какиелибо выводы отно сительно количества участвующих в отношении объектов. Как и ранее, на рис. 2.7 пунктирная линия между классами Car и GasStation ука зывает на существующее между ними отношение использования. В языке UML пунк тирной линией принято обозначать семантические (смысловые) отношения между двумя элементами модели.
Диаграммы взаимодействий Диаграммы классов отражают статические отношения между классами. Другими словами, на них не отображаются никакие действия. Тем не менее, иногда бывает очень полезно показать действительное взаимодействие отдельных экземпляров объ ектов, полученных при реализации различных классов. Диаграммы языка UML, отражающие взаимодействие объектов друг с другом, полу чили название диаграмм взаимодействий. Наиболее распространенным видом диаграмм взаимодействий является диаграмма последовательностей, представленная на рис. 2.8. Диаграммы последовательностей следует читать сверху вниз. •
Каждый прямоугольник в верхней части диаграммы представляет конкретный объект. Большинство прямоугольников содержит имена классов, причем обратите внимание, что в некоторых случаях перед именем класса стоит двоеточие. Другие прямоугольники содержат такие имена, как shape1:Square.
•
Прямоугольники в верхней части диаграммы содержат имя класса (справа от двоеточия) и, возможно, имя объекта (указывается перед двоеточием).
•
Вертикальные линии представляют жизненный путь объектов. К сожалению, большинство программ автоматизированной подготовки UMLдиаграмм не поддерживают этот аспект и рисуют линии от верхнего края и до конца листа, оставляя неясными действительные сроки существования объекта.
•
Обмен сообщениями между объектами отображается с помощью горизонталь ных линий, проведенных между соответствующими вертикальными линиями.
•
Иногда возвращаемые значения и/или объекты должны быть указаны подробно, а иногда и так понятно, что они возвращаются.
Например, еще раз обратимся к рис. 2.8. •
Слева вверху показана подпрограмма Main, которая посылает объекту ShapeDB (который не поименован) сообщение с требованием извлечь сведения о фигурах (getShapes). Получив этот запрос, объект ShapeDB выполняет следующее. − Создает экземпляр класса Collection. − Создает экземпляр класса Square. − Добавляет объект класса Square в объект класса Collection. − Создает экземпляр класса Circle. − Добавляет объект класса Circle в объект класса Collection. − Возвращает указатель на объект класса Collection в вызывающую подпро грамму (Main).
Глава 2. UML — унифицированный язык моделирования
ÐÈÑ. 2.8. Диаграмма последовательностей для программы обработки фигур
53
54
Часть I. Введение в разработку...
Чтобы выяснить дальнейшие действия программы, достаточно прочитать в ука занной выше манере оставшуюся часть диаграммы. Данная диаграмма называется диаграммой последовательностей, поскольку она отражает последовательность вы полняемых операций.
Нотация Object:Class В некоторых диаграммах языка UML необходимо представлять обращения к объектам, указывая класс, от которого они порождены. В этом случае имена объекта и класса записываются через двоеточие. На рис. 2.8 запись shape1:Square указывает, что выполняется обращение к объекту shape1, являющемуся экземпляром класса Square.
Резюме Язык UML предоставляет разработчику средства как демонстрации, так и анализа проектов. Не стоит особо беспокоиться относительно строгости начертания созда ваемых диаграмм. Лучше сосредоточить внимание на тщательной проработке как ос новных концепций, так и всех деталей создаваемого проекта. Главные рекомендации заключаются в следующем. •
Если появляется необходимость выразить чтолибо на диаграмме обычными словами, воспользуйтесь примечанием.
•
Если у вас были сомнения по поводу назначения некоторой пиктограммы или символа, и для уточнения их значения потребовалось отыскать некоторые дополнительные источники, целесообразно поместить на диаграмму примеча ние с соответствующими разъяснениями, поскольку и другие могут испытывать затруднения в их истолковании.
•
Всегда стремитесь к максимальной ясности создаваемых диаграмм.
Не вызывает сомнений, что следует избегать использования языка UML в какой либо нестандартной манере — это лишь осложняет работу. Просто постарайтесь чет ко представить себе, что именно вы стремитесь передать при построении диаграмм.
ЧАСТЬ II
Ограниченность традиционного объектно ориентированного проектирования В этой части В этой части мы обсудим решение некоторых задач, взятых из реальной жизни, с использованием стандартных методов объектноориентированной разработки. Над обсуждаемой ниже проблемой я работал в то время, когда только приступал к изуче нию шаблонов проектирования. Глава
Предмет обсуждения
3
∑ Описание характерной для систем автоматизированного проектирования (САПР) проблемы: выборка информации из больших производственных баз данных систем САПР для нужд сложных и дорогих прикладных про граммам анализа ∑ Поскольку программное обеспечение систем САПР постоянно развивает ся и модернизируется, упомянутая выше проблема неразрешима без соз дания гибкого (настраиваемого) кода
4
∑ Первое решение связанной с системой САПР проблемы, выполненное с использованием стандартных объектноориентированных методов ∑ Работая над указанной проблемой, я еще не полностью разобрался с теми принципами, которые были положены в основу многих шаблонов проек тирования. В результате было предложено решение, от которого позднее
пришлось отказаться. Без особых затруднений я реализовал работающее решение, но впоследствии столкнулся с необходимостью учитывать мно жество особых случаев ∑ Найденное решение было связано с существенными проблемами: высо кой стоимостью сопровождения и недостаточной гибкостью — а это как раз то, чего я намеревался избежать, обратившись к объектно ориентированной технологии ∑ Позднее, в части 4, мы еще вернемся к этой проблеме (глава 12, Решение задачи САПР с помощью шаблонов проектирования). Там мы обсудим другое решение этой проблемы, базирующееся на использовании шаблонов про ектирования для организации структуры приложения и реализующих его элементов. Такой подход позволяет получить намного более гибкое и простое в обслуживании решение
Очень важно прочесть эту часть книги, поскольку она иллюстрирует типичные проблемы, свойственные традиционному объектноориентированному проектирова нию — необходимость организации иерархии наследования классов, имеющей силь ную связанность и слабую связность.
Глава 3. Проблема, требующая создания гибкого кода
57
Глава 3
Проблема, требующая создания гибкого кода
Введение Эта глава содержит краткий обзор проблемы, которую нам предстоит решить: из влечение информации из базы данных большой системы автоматизированного про ектирования (САПР) с целью передачи ее на обработку в сложную и дорогостоящую программу анализа. Поскольку программное обеспечение САПР постоянно развива ется и модернизируется, решение указанной задачи невозможно без создания гибкого (настраиваемого) программного кода. В этой главе дается краткий обзор проблемы, связанной с САПР, вводится харак терная для этой области терминология и описываются несколько важных особенно стей данной проблемы.
Извлечение информации из базы данных САПР Сначала обсудим мой первый проект, указавший мне дорогу к пониманию всех тех вещей, о которых идет речь в этой книге. В то время я работал в проектном центре, в котором инженеры использовали САПР для создания чертежей различных деталей, вырезаемых из металлического листа. Примером может служить деталь, изображенная на рис. 3.1.
ÐÈÑ. 3.1. Пример детали, вырезанной из металлического листа
58
Часть II. Ограниченность...
Я должен был создать компьютерную программу, которая могла бы извлекать ин формацию из базы данных САПР и передавать ее экспертной системе в таком виде, чтобы последняя могла использовать полученные данные с определенной целью. Информация о детали нужна была экспертной системе для того, чтобы управлять ее изготовлением. Поскольку модификация самой экспертной системы — задача слиш ком сложная, а срок ее эксплуатации предполагался существенно большим, чем время жизни текущей версии программного обеспечения САПР, мне необходимо было на писать программу так, чтобы ее можно было легко модифицировать для работы с по следующими версиями САПР.
Что такое экспертная система Экспертная система — это специальная компьютерная программа, которая использует правила, сформулированные экспертами"людьми, для автоматизированного принятия решений. Экспертная система создается в два этапа. На первом определяется набор правил, необходимых для решения поставленных задач. На втором этапе эти правила помещаются непосредственно в компьютер. Обычно для этого как инструмент используют одну из многих доступных коммерческих экспертных систем. Заметим, что для аналитика первый этап — несравненно более трудная задача.
Необходимый понятийный аппарат Первая задача анализа состоит в том, чтобы усвоить терминологию, используемую пользователями и экспертами в предметной области задачи. Для нас сейчас важны те термины, которые описывают размеры и геометрию листа металла. На рис. 3.1 показан фрагмент металлического листа определенного размера, в ко тором вырезаны отверстия различной формы. Эксперты называют эти прорези об щим именем "элементы". Таким образом, в нашей задаче изготовленная из металличе ского листа деталь может быть полностью описана своими внешними размерами и указанием содержащихся в ней элементов. Типы фигурэлементов, которые могут быть вырезаны в деталях, изготовляемых из металлического листа, описаны в табл. 3.1. Это как раз те элементы, с которыми будет работать экспертная система. Таблица 3.1. Элементы, которые могут вырезаться в металлическом листе Фигура
Описание
Паз
Прямая прорезь в листе металла постоянной ширины, ограни ченная прямоугольными или закругленными торцами. Пазы могут быть ориентированы под любым углом. Обычно они вырезаются с помощью фрезерных станков. На рис. 3.1 показано три паза, расположенных с левой стороны листа. Один из пазов ориенти рован вертикально, а два других — горизонтально
Отверстие
Отверстия круглой формы, прорезанные в металлическом листе. Чаще всего они вырезаются сверлами различного диаметра. На рис. 3.1 слева показано отверстие, окруженное тремя пазами, а так же еще одно отверстие большего диаметра на правой стороне листа
Глава 3. Проблема, требующая создания гибкого кода
59
Окончание таблицы Фигура
Описание
Просечка
Отверстие с прямоугольными или закругленными краями — обычно выдавливается в листе с помощью пуансона. На рис. 3.1 показано три подобных отверстия, причем отверстие внизу спра ва повернуто под углом в 45 градусов
Отверстия специ альной формы
Отверстия в листе металла, не являющиеся пазами, круглыми от верстиями или просечками. Для их быстрого изготовления обыч но создается соответствующий пуансон. Типичным примером от верстия специальной формы является гнездо для электрического разъема. На рис. 3.1 примером отверстия специальной формы яв ляется просечка в виде звезды
Отверстие непра вильной формы
Все остальные виды отверстий. Обычно они выполняются в листе металла с помощью комбинации различных инструментов. Пример отверстия неправильной формы приведен на рис. 3.1 справа внизу
Эксперты, работающие с САПР, пользуются также некоторой дополнительной терминологией, которая будет важна для понимания нашего дальнейшего обсужде ния. Эта терминология приведена в табл. 3.2. Таблица 3.2. Дополнительная терминология САПР Термин
Описание
Геометрия
Описание внешнего вида детали, изготовленной из металличе ского листа: расположение каждого элемента и его размеры, а также характеристики внешней формы всей детали в целом
Деталь
Деталь, вырезанная из листа металла, сама по себе. Создаваемая программа должна позволять сохранять сведения о геометрии каждой детали
Набор данных или модель
Набор записей в базе данных САПР, хранящих сведения о гео метрии детали
Станок с ЧПУ и ЧПУпрограмма
Станок с числовым программным управлением, предназначенный для обработки металла с помощью набора различных режущих головок и работающий под управлением специальной компьютер ной программы. Обычно компьютерная программа управляет станком в соответствии с описанием геометрии детали, составляя из отдельных команд обработки последовательность, образую щую ЧПУпрограмму
Описание задачи Необходимо создать программу, которая позволит экспертной системе находить и считывать набор данных (модель), содержащий описание геометрии требуемой дета ли. Затем экспертная система должна будет сгенерировать команды, образующие про грамму управления станком с числовым программным управлением, изготовляющим данную деталь из металлического листа.
60
Часть II. Ограниченность...
Здесь мы ограничимся рассмотрением только тех деталей, которые вырезаются из металлического листа, хотя САПР, безусловно, могут использоваться и во множестве других случаев. На концептуальном уровне требуется, чтобы система выполнила следующие дей ствия. •
Проанализировала характеристики детали, изготовляемой из металлического листа.
•
Определила технологию изготовления детали, исходя из особенностей имею щихся в ней элементов.
•
Подготовила соответствующий набор инструкций, предназначенных для управ ления технологическим оборудованием. Этот набор инструкций называется ЧПУпрограммой и предназначен для управления работой станка с числовым управлением.
•
Передала готовую ЧПУпрограмму технологическому оборудованию, когда пот ребуется изготовить ту или иную деталь.
Основная трудность при программировании данной задачи состоит в том, что нельзя просто извлечь описание элементов из базы данных и сразу же сгенерировать ЧПУпрограмму для соответствующего станка. Тип и порядок следования составляю щих ЧПУпрограмму команд зависит от характеристик элементов детали и их взаим ного расположения. Например, рассмотрим отверстие, представляющее собой комбинацию из не скольких элементов — просечки и двух пазов. Один из пазов пересекает просечку вер тикально, а другой — горизонтально, как это показано на рис. 3.2.
ÐÈÑ. 3.2. Комбинация из просечки и двух пазов. Слева: конечный результат. Справа: составные части элемента
Важно понять, что в действительности для получения элемента такой формы, кото рая показана слева, потребуется вырезать в детали три элемента, показанных на рисунке справа. Это происходит потому, что работающие с САПР инженеры, чтобы получить элемент сложной формы, обычно манипулируют простейшими элементами. Поступая так, они предполагают, что этим ускорят изготовление необходимых деталей. Проблема состоит в том, что для реального технологического оборудования нель зя генерировать команды ЧПУпрограммы изготовления всех трех элементов незави симо друг от друга и при этом полагать, что деталь получится должным образом.
Глава 3. Проблема, требующая создания гибкого кода
61
Существует понятие технологической последовательности изготовления детали, ко торую необходимо предварительно разработать. В нашем примере, если сначала вы резать в детали пазы, а затем попытаться сделать просечку (как показано на рис. 3.3), то при изготовлении просечки (напомним, что просечка делается с помощью пуансо на под воздействием удара большой силы) металлический лист согнется, так как прочность металла в этом месте была ослаблена пазами.
ÐÈÑ. 3.3. Неправильная последовательность изготовления требуемого от! верстия — в результате металлический лист согнется
Отверстие, показанное на рис. 3.2, следует делать, начиная с операции просечки, и лишь затем переходить к прорезке пазов. В этом случае результат будет удовлетвори тельным, так как пазы вырезаются с помощью фрезерного станка, создающего боко вое давление, а не поперечное. Фактически, предварительно сделав просечку, мы да же упростим работу фрезерному станку, как это показано на рис. 3.4.
ÐÈÑ. 3.4. Правильный подход к выполнению отверстия требуемой формы
К счастью, правила для экспертной системы уже были разработаны ранее и мне не пришлось беспокоиться по этому поводу. Мы обсудили указанные проблемы лишь для того, чтобы лучше понять, какого рода информация была необходима экспертной системе.
62
Часть II. Ограниченность...
Возможные подходы к решению проблемы Программное обеспечение САПР постоянно развивается и совершенствуется. Моя задача состояла в том, чтобы помочь компании продолжать использовать имеющуюся у нее дорогую экспертную систему даже в том случае, если программное обеспечение САПР будет меняться. На тот момент в компании использовалась САПР версии 1 (V1), но вскоре должна была поступить новая версия (V2). Несмотря на общего производителя, эти версии были несовместимы между собой. По разным техническим и административным причинам невозможно было пере нести данные из одной версии САПР в другую. Таким образом, от экспертной систе мы требовалось поддерживать обе версии САПР одновременно. Фактически ситуация была даже несколько хуже: недостаточно было решить ука занную проблему только для двух различных версий САПР. Я знал, что вскоре ожи дался выпуск третьей версии этой системы, но не знал, когда именно это произойдет. Чтобы сохранить инвестиции компании в экспертную систему, я стремился создать систему с общей структурой, схематически представленной на рис. 3.5.
ÐÈÑ. 3.5. Концептуальная схема предлагаемого решения
Согласно этой схеме приложение способно инициализировать работу с любой версией САПР и организовать работу экспертной системы с ней. Однако экспертная система должна быть способна использовать любую версию САПР. Следовательно, программа должна быть реализована так, чтобы версии V1 и V2 для экспертной сис темы представлялись неразличимыми. Хотя полиморфизм определенно необходим на уровне получения геометрической информации, нет необходимости поддерживать его на уровне самих элементов. Суть в том, что экспертная система должна знать, с какими именно элементами она имеет дело. Тем не менее, даже при появлении 3й версии САПР нежелательно вносить ка киелибо изменения в саму экспертную систему.
Глава 3. Проблема, требующая создания гибкого кода
63
Согласно основными концепциям объектноориентированного проектирования диаграмма классов верхнего уровня приложения должна была иметь вид, представ ленный на рис. 3.6.
ÐÈÑ. 3.6. Диаграмма классов первой версии приложения
1
Из этой диаграммы следует, что экспертная система связывается с САПР через класс Model. Класс Main отвечает за создание экземпляра объекта требуемой версии класса Model (т.е. класса V1Model или класса V2Model). Остановимся на описании САПР и особенностей ее работы. К сожалению, основ ные характеристики обеих версий существенно отличались друг от друга. Версия 1 по существу представляла собой набор библиотек подпрограмм. Для вы борки информации из модели требовалось произвести ряд вызовов подпрограмм. Типичный набор запросов мог быть таким, как показано во врезке.
Последовательность действий при работе с САПР версии 1
1
1.
Открыть модель детали XYZ и возвратить указатель на нее.
2.
Сохранить этот указатель в переменной H.
3.
Определить количество элементов в модели, указатель на которую хранится в переменной H, сохранить полученное значение в переменной N.
4.
Для каждого элемента (от 1 до значения в переменной N) модели, указатель на которую хранится в переменной H, выполнить следующие действия: а)
определить идентификатор i"го элемента модели и сохранить его в переменной ID;
б)
определить тип элемента модели, идентификатор которого хранится в переменной ID, и сохранить его в переменной T.
Эта и все другие диаграммы классов в данной книге построены с использованием нотации язы! ка UML. Описание этой нотации можно найти в главе 2, UML — унифицированный язык моделирования.
64
Часть II. Ограниченность...
в)
…
для каждого элемента модели, идентификатор которого хранится в переменной ID, определить координату по оси Х и сохранить ее значение в переменной X (при этом значение в переменной T, используется для выбора подпрограммы, соответствующей данному типу элемента)… …
Приведенная система несовершенна и в действительности не является объектно ориентированной. Тот, кто работает с этой системой, должен вручную управлять контекстом каждого запроса. При каждом вызове функции необходимо знать, с каким именно элементом ведется работа. Разработчики программного обеспечения САПР хорошо понимали недостатки, свойственные данному типу систем. Основным побуждением для создания версии V2 было стремление сделать систему объектноориентированной. Поэтому данные о геометрии в системе версии V2 хранятся в объектах. Когда к системе обращаются с запросом о выдаче модели, она возвращает объект, представляющий модель. Этот объект модели включает набор объектов, каждый из которых представляет отдель ный элемент. Поскольку задача исходно формулировалась в терминах элементов, не удивительно, что система версии V2 использует набор классов, соответствующий ти пам элементов, обсуждавшихся нами выше, — паз, отверстие, просечка, отверстие специальной формы, отверстие произвольной формы. Следовательно, в системе версии V2 можно сразу получить набор объектов, опи сывающих все элементы, которые присутствуют в детали, вырезаемой из металличе ского листа. Существующие классы элементов показаны на UMLдиаграмме, пред ставленной на рис. 3.7.
ÐÈÑ. 3.7. Классы элементов в системе версии V2
Префикс OOG в имени классов означает "Объектноориентированная геометрия" — это просто напоминание о том, что система версии V2 является объектноориентиро ванной.
Глава 3. Проблема, требующая создания гибкого кода
65
Резюме В этой главе описана проблема, связанная с эксплуатацией САПР. •
Информация, извлеченная из САПР различного типа, должна быть представ лена в одном и том же виде. Это позволит сохранить большие инвестиции, вложенные компанией в экспертную систему, так как последняя сможет работать с различными САПР без дорогостоящих модификаций.
•
Существуют две системы, построенные с использованием совершенно разных подходов, но содержащие по существу одну и ту же информацию.
Эта задача имеет много общего с другими проблемами, с которыми мне приходи лось сталкиваться в различных проектах. Кратко ее можно сформулировать так: "Существует несколько различных реализаций некоторой системы, и требуется орга низовать работу так, чтобы другие объекты могли работать с ними одинаково".
66
Часть II. Ограниченность...
Глава 4
Стандартное объектно ориентированное решение
Введение В этой главе рассматривается первоначально выбранное решение проблемы, упо мянутой в главе 3, Проблема, требующая создания гибкого кода. Вначале это решение вы глядит приемлемым и быстро приводящим к требуемому результату. Однако в нем не учитывается важное требование, предъявляемое к создаваемой системе, — обеспече ние работы с постоянно развивающейся САПР, выражающееся в поддержке несколь ких ее версий одновременно. В этой главе описывается решение проблемы, типичное для объектноориенти рованного подхода. Безусловно, оно не самое лучшее из возможных, но вполне рабо тоспособное. Замечание. В тексте главы примеры программного кода приведены на языке Java. Примеры соответствующего кода на языке C++ можно найти в конце главы.
Решение с обработкой особых случаев Рассмотрим две различные версии САПР, описанные в главе 3, Проблема, требую! щая создания гибкого кода. Наша цель заключается в создании системы, способной из влечь информацию из базы данных таким образом, чтобы для экспертной системы не имело значения, какая именно версия системы САПР используется. Обдумывая решение поставленной задачи, я полагал, что если будет найдено ре шение для обработки пазов, то его можно будет использовать также и для работы с другими элементами: просечками, отверстиями и т.д. Анализируя работу с пазами, легко заметить, что можно без труда создать производные классы для каждого из воз можных вариантов. Другими словами, если для представления пазов определить класс SlotFeature, то можно создать производный от него класс V1Slot для работы с первой версией системы САПР и класс V2Slot для работы со второй версией систе мы, — как показано на рис. 4.1. Окончательное решение заключается в распространении данного подхода на все типы элементов, поддерживаемые в системе, — как показано на рис. 4.2. Не вызывает сомнения, что подобное решение будет вполне работоспособно. Ка ждый из классов V1xxx будет работать с соответствующей ему библиотекой САПР версии V1. В свою очередь, каждый из классов V2xxx будет взаимодействовать с соот ветствующим объектом САПР версии V2.
68
Часть II. Ограниченность...
ÐÈÑ. 4.1. Проектное решение для обработки пазов
ÐÈÑ. 4.2. Исходный вариант решения проблемы извлечения информации
Чтобы лучше представить себе работу всей системы, рассмотрим каждый класс в отдельности. •
Класс V1Slot в этом случае следует реализовать так, чтобы при создании его экземпляров запоминалась модель (тип детали) и ее идентификатор в системе V1. Кроме того, всякий раз при вызове одного из методов объекта класса V1Slot для получения определенной информации, этот метод, в свою очередь, должен вызывать некоторую последовательность подпрограмм системы V1, позволяющую достичь требуемого результата.
Глава 4. Стандартное объектноориентированное решение
•
69
Класс V2Slot следует реализовать подобным же образом, но за исключением того, что в этом случае каждый объект класса V2slot должен включать объект, представляющий соответствующий паз в системе V2. При этом, обращаясь к объекту класса V2Slot за информацией, он просто передает поступивший запрос объекту OOGSlot и возвращает полученный от него ответ объекту, обратившемуся к нему за информацией.
Общая структура приложения, обеспечивающего работу с САПР обеих версий, V1 и V2, показана на рис. 4.3. В листингах 4.1–4.2 представлены примеры реализации отдельных пар классов в рассматриваемом проекте. Эти примеры помогут вам понять, как практически может быть реализован данный проект. Читатель, не нуждающийся в подобных примерах или предпочитающий работать с языком C++, может просто пропустить приведенный ниже Javaкод или же обратиться к примерам программного кода на языке С++, поме щенным в конце этой главы (листинги 4.5–4.8). Листинг 4.1. Реализация представления элементов для системы версии V1 на языке Java // Реализация представления элементов деталей. // Данный код не был протестирован – это лишь иллюстрация // возможного проектного решения. // // // // //
Каждый представляющий элемент объект для выборки запрашиваемой информации должен знать номер модели и ID представляемого элемента. Обратите внимание на то, как эта информация передается в конструктор каждого объекта. // Открытие модели modelNum = V1OpenModel (modelName); nElements = V1GetNumberofElements (modelNum); Feature features[]= new Feature[MAXFEATURES];
// Выполнить для каждого элемента в модели. for (i = 0; i < nElements; i++) { // Определить тип элемента и создать соответствующий // этому элементу объект. switch(V1GetType(modelNum, i)) { case SLOT: features[i]= new V1Slot(modelNum, V1GetID(modelNum, i)); break; case HOLE: features[i]= new V1Hole(modelNum, V1GetID( modelNum, i)); break; … } ...}
Часть II. Ограниченность...
РИС. 4.3. Первый вариант решения проблемы
70
ÐÈÑ. 4.3. Первый вариант решения проблемы
Глава 4. Стандартное объектноориентированное решение
Листинг 4.2. Реализация методов для системы версии V1 на языке Java // ModelNum и myID – закрытые переменные-члены класса, // содержащие информацию о модели и элементе (в системе V1), // которому этот объект соответствует. class V1Slot { Double getX () { // Вызов соответствующего метода версии V1 с целью // получения требуемых данных. // Замечание: в действительности для получения // информации этот метод может вызывать несколько // методов в системе V1. return V1GetXforSlot (modelNum, myID); } } Class V1Hole { Double getX () { // Вызов соответствующего метода версии V1 с целью // получения требуемых данных. // Замечание: в действительности для получения // информации этот метод может вызывать несколько // методов в системе V1. return V1GetXforHole (modelNum, myID); } }
Листинг 4.3. Реализация представления элементов для системы версии V2 на языке Java // Реализация представления элементов деталей. // Данный код не был протестирован – это лишь иллюстрация // возможного проектного решения. // // // // //
Каждый представляющий элемент объект для выборки запрашиваемой информации должен знать номер представляемого элемента в системе V2. Обратите внимание на то, как эта информация передается в конструктор каждого объекта. // Открытие модели MyModel = V2OpenModel(modelName); nElements = myModel.getNumElements(); Feature features[]= new Feature[MAXFEATURES]; OOGFeature oogF; // Выполнить для каждого элемента в модели. for (i = 0; i < nElements; i++) { // Определить тип элемента и создать // соответствующий этому элементу объект. oogF = myModel.getElement(i); switch (oogF.myType()) { case SLOT: features[i]= new V2Slot(oogF); break; case HOLE: features[i]= new V2Hole( oogF);
71
72
Часть II. Ограниченность...
break; … } }
Листинг 4.4. Реализация методов для системы версии V2 на языке Java // Метод oogF ссылается на объект, представляющий в // системе V2 элемент, соответствующий данному. Class V2Slot { double getX() { // Вызвать соответствующий метод oogF с целью // получения требуемой информации. return oogF.getX(); } } Class V2Hole { double getX() { // Вызвать соответствующий метод oogF с целью // получения требуемой информации. return oogF.getX();} } }
На рис. 4.3 представлено несколько методов, необходимых для обработки элемен тов различных типов. Обратите внимание на то, что они различаются в зависимости от типа элемента. Это означает отсутствие полиморфизма в представлении элемен тов. Однако это не помеха, поскольку экспертной системе в любом случае необходимо точно знать тип обрабатываемого элемента, так как для описания разных элементов необходима различная информация. Из сказанного можно сделать вывод, что мы не столько заинтересованы в поли морфизме при представлении элементов, сколько в способности экспертной системы без какихлибо изменений работать с различными версиями САПР. Однако поставленная цель — прозрачная (для экспертной системы) работа с не сколькими различными версиями САПР — выявляет наличие в предложенном выше решении нескольких недостатков. •
Избыточное количество методов. Совершенно очевидно, что методы, выполняю щие обращение к системе V1, будут во многом похожи друг на друга. Например, методы V1getX для класса Slot и класса Hole мало чем отличаются друг от друга.
•
Беспорядочность. Это не самый важный показатель, но, тем не менее, один из факторов, усугубляющих неудовлетворенность найденным решением.
•
Сильная связанность. Найденное решение характеризуется сильной связан ностью, поскольку все элементы косвенно связаны друг с другом. Эта избыточ ная связанность проявляется в том, что потребуется модификация представ ления практически всех элементов в системе при возникновении одного из следующих событий:
Глава 4. Стандартное объектноориентированное решение
73
− появление новой версии САПР; − очередная модификация существующей версии САПР. •
Слабая связность. Уровень связности в системе невысок потому, что методы, реализующие основные функции системы, рассеяны среди множества различ ных классов.
Однако наибольшее беспокойство вызывает предполагаемое появление третей версии САПР. Умножение количества классов по правилам комбинаторики фактиче ски приведет к краху проекта. Чтобы понять это, достаточно просто проанализиро вать количество классов на нижнем уровне диаграммы, представленной на рис. 4.3. •
На этом уровне представлены пять существующих типов элементов.
•
Представление каждого типа включает два класса, по одному для каждой версии САПР.
•
При появлении третьей версии каждое представление будет включать уже не два, а три класса.
•
Поэтому в приложении будет уже не десять, а пятнадцать классов.
В результате будет получено столь громоздкое приложение, что его практически невозможно будет сопровождать. Данный вариант был моим первым решением и я сразу же понял, что его нельзя считать удовлетворительным. Это ощущение было скорее интуитивным, а не какимто логическим выводом, сделанным на основе приведенных выше рассуждений. Я чувствовал, что данный вариант имеет существенные недостатки.
Недостатки проведенного анализа: слишком рано чрезмерное внимание было уделено подробностям Одна из самых распространенных ошибок предварительного анализа состоит в том, что слишком рано чрезмерное внима" ние уделяется мелким деталям проектируемых процессов. Однако это вполне закономерно, поскольку работать с деталями проще. Решения для реализации отдельных деталей проекта лежат на поверхности, но это не самый лучший подход к началу работы над ним. Излишней детализации проекта следует избегать как можно дольше. В нашем случае я стремился к одной цели — создать единый API для извлечения информации об элементах деталей. Кроме того, я формулировал определение объектов с точки зрения возлагаемых на них обязанностей, рассматривая все существующие возможности по отдельности. Если обнаруживался новый особый случай, он подлежал независимой реализации. В результате процедура сопровождения создаваемой системы чрезвычайно усложнилась.
Не вызывало сомнения, что существует некое лучшее решение. Однако спустя два часа мне так и не удалось его найти. Как выяснилось позднее, проблема состояла в са мом общем подходе, который был выбран мной. Подтверждение этому вы найдете ни же, на страницах этой книги.
74
Часть II. Ограниченность...
Руководствуйтесь своей интуицией Интуитивная оценка — это на удивление эффективный индикатор качества создаваемого проекта. Я полагаю, что будущих разработчиков программного обеспечения следует приучать полагаться на свою интуицию. Под интуицией я здесь понимаю то особое ощущение, которое возникает у вас при взгляде на что"то такое, что вам не нравится. Я понимаю, что подобное определение звучит крайне ненаучно (и это действительно так), однако мой личный опыт убедительно доказывает, что если проектное решение мне интуитивно не нравится, то где"то рядом обязательно имеется лучшее. Безусловно, иногда можно предложить сразу несколько возможных направлений совершенствования, и выбор лучшего из них бывает не вполне очевиден.
Резюме В этой главе было показано, как можно быстро решить поставленную задачу, вы деляя каждый возможный вариант в особый случай. Требуемое решение совершенно очевидно. Оно позволяет добавлять дополнительные методы без изменения уже су ществующих. Однако подобному решению свойственно несколько недостатков: высо кая избыточность, слабая связность, большое количество классов (с учетом будущих расширений). Чрезмерное увлечение традиционными методами разработки будет иметь следст вием высокую стоимость сопровождения системы (по крайней мере, об этом говорит мой личный опыт).
Примеры программного кода на языке С++ Листинг 4.5. Реализация представления элементов для системы версии V1 на языке C++ // Реализация представления элементов деталей. // Данный код не был протестирован – это лишь иллюстрация // возможного проектного решения. // // // // //
Каждый представляющий элемент объект для выборки запрашиваемой информации должен знать номер модели и ID представляемого элемента. Обратите внимание на то, как эта информация передается в конструктор каждого объекта. // Открытие модели modelNum = V1OpenModel(modelName); nElements = V1GetNumberofElements(modelNum); Feature *features[MAXFEATURES]; // Выполнить для каждого элемента в модели for (i = 0; i < nElements; i++) { // Определить тип элемента и создать соответствующий // этому элементу объект. switch(V1GetType(modelNum, i)) { case SLOT: features[i] = new V1Slot(modelNum, V1GetID(modelNum, i)); break;
Глава 4. Стандартное объектноориентированное решение
case HOLE: features[i] = new V1Hole(modelNum, V1GetID(modelNum, i)); break; … } }
Листинг 4.6. Реализация методов для системы версии V1 на языке C++ // ModelNum и myID – закрытые переменные-члены класса, // содержащие информацию о модели и элементе (в системе V1), // которому этот объект соответствует. Double V1Slot::getX () { // Вызов соответствующего метода версии V1 с целью // получения требуемых данных. // Замечание: в действительности для получения // информации этот метод может вызывать несколько // методов в системе V1. return V1GetXforSlot(modelNum, myID); } Double V1Hole::getX() { // Вызов соответствующего метода версии V1 с целью // получения требуемых данных. // Замечание: в действительности для получения // информации этот метод может вызывать несколько // методов в системе V1. return V1GetXforHole(modelNum, myID); }
Листинг 4.7. Реализация представления элементов для системы версии V2 на языке C++ // Реализация представления элементов деталей. // Данный код не был протестирован – это лишь иллюстрация // возможного проектного решения. // // // // //
Каждый представляющий элемент объект для выборки запрашиваемой информации должен знать номер представляемого элемента в системе V2. Обратите внимание на то, как эта информация передается в конструктор каждого объекта. // Открытие модели MyModel = V2OpenModel(modelName); nElements = myModel -> getNumElements(); Feature *features [MAXFEATURES]; OOGFeature *oogF; // Выполнить для каждого элемента в модели. for (i = 0; i < nElements; i++) {
75
76
Часть II. Ограниченность...
// Определить тип элемента и создать // соответствующий этому элементу объект. oogF = myModel -> getElement(i); switch(oogF -> myType()) { Case Slot: features[i] = new V2Slot(oogF); break; case HOLE: features[i]= new V2Hole(oogF); break; … } }
Листинг 4.8. Реализация методов для системы версии V2 на языке C++ // Метод oogF ссылается на объект, представляющий в // системе V2 элемент, соответствующий данному. Double V2Slot::getX() { // Вызвать соответствующий метод oogF с целью // получения требуемой информации. return oogF -> getX(); } Double V2Hole:getX() { // Вызвать соответствующий метод oogF с целью // получения требуемой информации. return oogF -> getX(); }
ЧАСТЬ III
Шаблоны проектирования
В этой части В этой части мы познакомимся с шаблонами проектирования — узнаем, что они собой представляют и как используются. Здесь описываются четыре шаблона, позво ляющих успешно решить задачу о системе САПР, обсуждавшуюся в главе 3, Проблема, требующая создания гибкого кода. Мы рассмотрим каждый из них в отдельности, а затем в применении к нашей задаче. Описание этих шаблонов опирается на объектно ориентированные концепции, изложенные в основополагающей работе "банды че тырех" (Гамма, Хелм, Джонсон и Влиссайдес) Design Patterns: Elements of Reusable Object Oriented Software. Глава
Предмет обсуждения
5
∑ Знакомство с шаблонами проектирования ∑ Концепция шаблонов проектирования, их первоначальное появление в архитектуре и последующий перенос этой концепции в область проекти рования программного обеспечения ∑ Мотивы для изучения шаблонов проектирования
6
∑ Шаблон Facade (фасад), его определение, использование и реализация ∑ Шаблон Facade в применении к задаче, связанной с САПР
7
∑ Шаблон Adapter (адаптер), его определение, использование и реализация ∑ Сравнение шаблонов Facade и Adapter ∑ Шаблон Adapter в применении к задаче, связанной с САПР
8
∑ Некоторые важные концепции объектноориентированного программи рования: полиморфизм, абстракция, классы и инкапсуляция. Обсуждение ведется на основе материала, рассмотренного в главах 5–7
9
∑ Шаблон Bridge (мост). Этот шаблон несколько сложнее двух предыдущих, однако намного полезнее, поэтому и рассматривается более подробно ∑ Шаблон Bridge в применении к задаче, связанной с САПР
10
∑ Шаблон Abstract Factory (абстрактная фабрика), позволяющий созда вать семейство объектов, его определение, использование и реализация ∑ Шаблон Abstract Factory в применении к задаче, связанной с САПР
Ознакомившись с материалом этой части, читатель узнает, что такое шаблоны проектирования, чем они полезны, и получит представление о четырех конкретных шаблонах. Кроме того, он узнает, как эти шаблоны можно применить к нашей задаче о системе САПР. Однако полученной информации будет все еще недостаточно для создания проекта лучшего, чем самый первый, основанный на простейшем механиз ме наследования. Успеха мы достигнем лишь тогда, когда научимся использовать шаблоны проектирования способом, отличным от того, который применяет боль шинство разработчиковпрактиков.
Глава 5. Первое знакомство с шаблонами проектирования
79
Глава 5
Первое знакомство с шаблонами проектирования Введение В этой главе мы познакомимся с концепцией шаблонов проектирования. Здесь будут рассмотрены: •
концепция шаблонов проектирования, их первоначальное появление в архи тектуре и последующее распространение на область проектирования програм много обеспечения;
•
мотивы для изучения шаблонов проектирования.
Шаблоны проектирования — это один из важнейших компонентов объектно ориентированной технологии разработки программного обеспечения Они широко применяются в инструментах анализа, подробно описываются в книгах и часто обсу ждаются на семинарах по объектноориентированному проектированию. Существует множество групп и курсов подготовки по их изучению. Часто приходится слышать, что приступать к изучению шаблонов проектирования следует только после того, как будут приобретены определенные навыки применения объектноориентированной технологии. Однако, по мнению автора, истинно обратное утверждение: предвари тельное изучение шаблонов помогает лучше понять объектноориентированное про ектирование и анализ. В остальной части книги мы будем обсуждать не только шаблоны проектирования, но и то, как в них проявляются и утверждаются принципы объектноориентированной технологии. Эта книга с одной стороны должна помочь читателю глубже понять эти принципы, а с другой — проиллюстрировать, каким образом обсуждаемые в ней шабло ны проектирования могут способствовать созданию хороших проектов. Предлагаемый ниже материал в некоторых случаях может показаться чересчур аб страктным или философским. Но читателю следует набраться терпения, поскольку в этой главе обсуждаются те основы, которые просто необходимы для понимания шаб лонов проектирования. Кроме того, изучение этого материала поможет вам лучше понять принципы построения и методы работы с новыми шаблонами. Многие из идей, изложенных здесь, были найдены автором в книге Кристофера 1 Александера (Christopher Alexander) The Timeless Way of Building . Эти идеи будут обсуж даться в различных главах книги.
1 Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, New York, NY: Oxford University Press, 1979.
80
Часть III. Шаблоны проектирования
Шаблоны проектирования пришли из области архитектуры и культурологии Много лет назад архитектор по имени Кристофер Александер задумался над во просом: "Является ли качество объективной категорией?". Следует ли считать пред ставление о красоте сугубо индивидуальным, или люди могут прийти к общему согла шению, согласно которому некоторые вещи будут считаться красивыми, а другие нет? Александер размышлял о красоте с точки зрения архитектуры. Его интересовало, по каким показателям мы оцениваем архитектурные проекты. Например, если некто вознамерился спроектировать крыльцо дома, то как он может получить гарантии, что созданный им проект будет хорош? Можем ли мы знать заранее, что проект будет действительно хорош? Имеются ли объективные основания для вынесения такого су ждения? Существует ли необходимая основа для достижения общего согласия? Александер принял как постулат, что в области архитектуры такое объективное основание существует. Суждение о том, что некоторое здание является красивым, — это не просто вопрос вкуса. Красоту можно описать с помощью объективных крите риев, которые могут быть измерены. К похожим выводам пришли и исследователи в области в культурологии. В преде лах одной культуры большинство индивидуумов имеют схожие представления о том, что является сделанным хорошо и что является красивым. В основе их суждений есть нечто более общее, чем сугубо индивидуальные представления о красоте. Похоже, что существуют некоторые трансцендентные образы или шаблоны, являющиеся объ ективным основанием для оценки предметов. Основная задача культурологии состоит в описании подобных шаблонов, определяющих каноны поведения и систему ценно 2 сти каждой из культур. Исходная посылка создания шаблонов проектирования также состояла в необхо димости объективной оценки качества программного обеспечения. Если идея о том, что оценить и описать высококачественный проект возможно, возражений не вызывает, то не пора ли попробовать создать нечто подобное? Я пола гаю, Александер сформулировал для себя следующие вопросы. 1. Что есть такого в проекте хорошего качества, что отличает его от плохого проекта? 2. Что именно отличает проект низкого качества от проекта высокого качества? Эти вопросы навели Александера на мысль о том, что если качество проекта явля ется объективной категорией, то мы можем явно определить, что именно делает про екты хорошими, а что — плохими. Александер изучал эту проблему, обследуя множество зданий, городов, улиц и все го прочего, что люди построили для своего проживания. В результате он обнаружил, что все, что было построено хорошо, имело между собой нечто общее. Архитектурные структуры отличаются друг от друга, даже если они относятся к одному и тому же типу. Однако, не взирая на имеющиеся различия, они могут оста ваться высококачественными.
2
Антрополог Рут Бенедикт (Ruth Benedict) была одним из создателей метода анализа культур с помощью выявления существующих в ней шаблонов. Например, см. Benedict R., The Chrysanthemum and the Sword, Boston, MA: Houghton Mifflin, 1946.
Глава 5. Первое знакомство с шаблонами проектирования
81
Например, два крыльца могут выглядеть конструктивно различными и, тем не ме нее, обладать высоким качеством — просто они решают различные задачи в различных зданиях. Одно крыльцо может служить для прохода с тротуара ко входной двери, а на значение другого может состоять в создании тени жарким днем. В другом случае два крыльца могут решать одну и ту же задачу, но различными способами. Александер понял это и пришел к выводу, что сооружения нельзя рассматривать обособленно от проблемы, для решения которой они предназначены. Поэтому в сво их попытках найти и описать критерии красоты и качества проекта он стал рассмат ривать различные архитектурные элементы, предназначенные для решения одинако вых задач. Например, на рис. 5.1 продемонстрированы два различных варианта оформления входа в здание.
ÐÈÑ. 5.1. Архитектурные элементы могут выгля! деть различными, но выполнять одну функцию
Александер установил, что, сфокусировав внимание на структурах, предназначен ных для решения подобных задач, можно обнаружить сходство между различными проектами, которым присуще высокое качество. Он назвал эти сходства шаблонами. 3 Он определил понятие шаблона как "решение проблемы в контексте". Каждый шаблон описывает проблему, которая возникает в данной среде снова и снова, а затем предлагает принцип ее решения таким способом, который можно будет применять многократно, получая каждый раз результаты, не повторяющие друг друга. 4
В табл. 5.1 приведены размышления Александера, взятые мной из его работы . Они иллюстрируют сделанное выше утверждение. Таблица 5.1. Выдержка из книги "Строительство на века" Слова Александера
Мой комментарий
Аналогичным образом, правильно спро ектированный внутренний двор дома спо собствует отдыху и восстановлению сил его жителей
Шаблон всегда имеет уникальное имя и предназначен для определенной цели. В данном случае шаблон называется "Внутренний двор", а его назначение со стоит в предоставлении жителям дома места для отдыха и восстановления сил
3 Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, New York, NY: Oxford University Press, 1979. 4 Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, New York, NY: Oxford University Press, 1979.
82
Часть III. Шаблоны проектирования
Продолжение таблицы Слова Александера
Мой комментарий
Рассмотрим, чем занимаются люди во внут реннем дворе. Прежде всего, они ищут воз можность уединиться на свежем воздухе и найти такое место, где можно будет посидеть под открытым небом, полюбоваться звезда ми, насладиться солнечным теплом или по садить цветы. Это совершенно очевидно
Хотя иногда это может быть вполне оче видно, нам важно четко сформулировать ту основную задачу, которая может быть решена с помощью данного шаблона. Именно это делает Александер при обсуж дении шаблона "Внутренний Двор"
Но не следует забывать и о других, на пер вый взгляд, менее важных задачах. В слу чае, если двор слишком замкнут и не дает необходимого обзора, люди в нем чувст вуют себя неуютно. У них возникает жела ние покинуть это место, поскольку они нуждаются в более широком поле обзора
Здесь подчеркиваются трудности, связан ные с упрощенным подходом к решению задачи, а затем предлагается лучший спо соб решения, позволяющий избежать ука занных затруднений
Однако привычка — вторая натура. Когда люди в своей обыденной жизни изо дня в день проходят через внутренний двор множество раз, это место становится для них знакомым, т.е. привычным местом прохода — и именно для этих целей оно будет использоваться ими в дальнейшем
Привычка очень часто мешает нам уви деть очевидные вещи. Ценность шаблонов в том, что люди, не обладающие большим опытом, могут воспользоваться преиму ществами, найденными их более опытны ми коллегами. Шаблоны позволяют им определять, какие детали должны быть включены для достижения высокого каче ства проекта, а также чего следует избе гать, чтобы не потерять это качество
Внутренний двор, не имеющий выхода на улицу, становится местом, которое посе щают только изредка, когда хочется именно туда. Он остается непривычным местом, которое используется все реже и реже, поскольку люди, в основном, склон ны к посещению знакомых им мест. Кроме того, есть чтото неприятное, не желательное в том, чтобы из дома выйти сразу на улицу. Это вроде бы мелочь, но ее достаточно, чтобы вызвать нервозность или раздражение. Если есть дополнительное пространство в виде крыльца или веранды под навесом, но открытое для воздуха — с психологи ческой точки зрения это промежуточное состояние между домом и улицей. В ре зультате человеку становиться легче и проще сделать тот короткий шаг, которые выведет его во внутренний двор
Предлагается решение, способное изме нить ваше мнение о преимуществах хоро шего внутреннего двора
Глава 5. Первое знакомство с шаблонами проектирования
83
Окончание таблицы Слова Александера
Мой комментарий
Если внутренний двор позволяет взглянуть на окружающее пространство, представля ет собой удобный путь между различными комнатами, включает в себя некоторый ва риант крыльца или веранды, то это напол няет его существование смыслом. Внешний обзор делает посещение внутреннего дво рика приятным, а пересечение в нем мно гих путей между помещениями обеспечива ет этим посещениям чувство привычности. Наличие калитки или веранды придает до полнительные психологические удобства при выходе из дома на улицу
Александер рассказывает, как построить отличный внутренний двор…
…и поясняет, почему он будет так хорош
В качестве заключения укажем четыре компонента, которые, по мнению Алексан дера, должны присутствовать в описании каждого шаблона. •
Имя шаблона.
•
Назначение шаблона и описание задачи которую он призван решать.
•
Способ решения поставленной задачи.
•
Ограничения и требования, которые необходимо принимать во внимание при решении задачи.
Александер принял как постулат, что с помощью шаблонов может быть решена любая архитектурная задача, с которой столкнется проектировщик. Затем он пошел дальше и высказал утверждение о том, что совместное использование нескольких шаблонов позволит решать комплексные архитектурные проблемы. Как можно применить одновременно несколько шаблонов, мы обсудим на страни цах этой книги позднее. Сейчас же сосредоточимся на пользе отдельных шаблонов при решении специализированных проблем.
Переход от архитектурных шаблонов к шаблонам проектирования программного обеспечения Но какое отношение может иметь весь этот архитектурный материал к специали стам в области программного обеспечения, коими мы являемся? В начале 1990х некоторые из опытных разработчиков программного обеспе чения ознакомились с упоминавшейся выше работой Александера об архитектур ных шаблонах.
84
Часть III. Шаблоны проектирования
Они задались вопросом, возможно ли применение идеи архитектурных шаблонов 5 при реализации проектов в области создания программного обеспечения. Сформулируем те вопросы, на которые требовалось получить ответ. •
Существуют ли в области программного обеспечения проблемы, возникающие снова и снова, и могут ли они быть решены тем же способом?
•
Возможно ли проектирование программного обеспечения в терминах шабло нов — т.е. создание конкретных решений на основе тех шаблонов, которые будут выявлены в поставленных задачах?
Интуиция подсказывала исследователям, что ответы на оба эти вопроса опреде ленно будут положительными. Следующим шагом необходимо было идентифициро вать несколько подобных шаблонов и разработать стандартные методы каталогиза ции новых. Хотя в начале 1990х над шаблонами проектирования работали многие исследова тели, наибольшее влияние на это сообщество оказала книга Гаммы, Хелма, Джонсона и Влиссайдеса Шаблоны проектирования: элементы многократного использования кода в 6 объектноориентированном программировании. Эта работа приобрела очень широкую из вестность. Свидетельством ее популярности служит тот факт, что четыре автора кни ги получили шутливое прозвище "банда четырех". В книге рассматривается несколько важных аспектов проблемы. •
Применение идеи шаблонов проектирования в области разработки програм много обеспечения.
•
Описание структур, предназначенных для каталогизации и описания шаблонов проектирования.
•
Обсуждение 23 конкретных шаблонов проектирования.
•
Формулирование концепций объектноориентированных стратегий и подхо дов, построенных на применении шаблонов проектирования.
Важно понять, что авторы сами не создавали тех шаблонов, которые описаны в их книге. Скорее, они идентифицировали эти шаблоны как уже существующие в разра ботках, выполненных сообществом создателей программного обеспечения. Каждый из шаблонов отражает те результаты, которые были достигнуты в высококачествен ных проектах при решении определенных, специфических проблем (этот подход на поминает нам о работе Александера). На сегодня существует несколько различных форм описания шаблонов проекти рования. Поскольку эта книга вовсе не о том, как следует описывать шаблоны проек 5 Консорциум ESPRIT в Европе обратился к этому же вопросу еще в 1980х. Проекты ESPRIT с номерами 1098 и 5248 состояли в разработке методологии проектирования с использованием шаб лонов, получившей название KADS (Knowledge Analysis and Support — анализ и поддержка знаний). Эта методология предусматривала использование шаблонов при создании экспертных систем. Ка рен Гарднер (Karen Gardner) распространила аналитические шаблоны KADS на объектную техноло гию. См. Gardner K. Cognitive Patterns: Problem Solving Frameworks for Object Technology, New York, NY: Cambridge University Press, 1998. 6 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995.
Глава 5. Первое знакомство с шаблонами проектирования
85
тирования, мы не приводим здесь нашего мнения по поводу того, какая из структур описания шаблонов является оптимальной. Однако пункты, приведенные в табл. 5.2, безусловно, должны присутствовать в любом описании шаблона проектирования. Каждый шаблон, упоминаемый в этой книге, будет представлен в виде краткого резюме, описывающего его важнейшие характеристики. Таблица 5.2. Важнейшие характеристики шаблонов Характеристика
Описание
Имя
Все шаблоны имеют уникальное имя, служащее для их идентификации
Назначение
Назначение данного шаблона
Задача
Задача, которую шаблон позволяет решить
Способ решения
Способ, предлагаемый в шаблоне для решения задачи в том контек сте, где этот шаблон был найден
Участники
Сущности, принимающие участие в решении задачи
Следствия
Последствия от использования шаблона как результат действий, выполняемых в шаблоне
Реализация
Возможный вариант реализации шаблона. Замечание. Реализация является лишь конкретным воплощением общей идеи шаблона и не должна пониматься как собственно сам шаблон
Ссылки
Место в книге "банды четырех", где можно найти дополнительную информацию
Следствия и действия Следствия — это термин, который широко используется в отношении шаблонов проектирования, но часто неправильно истолковывается. В применении к шаблонам проектирования под следствием понимается любой эффект, вызванный какой" либо причиной в процессе его функционирования. Иначе говоря, если шаблон реализован так"то и так"то, то следствиями его использования будут результаты любых выполняемых в нем действий.
Зачем нужно изучать шаблоны проектирования Теперь, когда мы знаем, что такое шаблоны проектирования, можно попытаться ответить на вопрос, зачем нужно их изучать. На то имеется несколько причин, часть из которых вполне очевидна, тогда как об остальных этого не скажешь. Чаще всего причины, по которым следует изучать шаблоны проектирования, формулируют следующим образом. •
Возможность многократного использования. Повторное использование решений из уже завершенных успешных проектов позволяет быстро приступить к решению новых проблем и избежать типичных ошибок. Разработчик получает прямую выгоду от использования опыта других разработчиков, избежав необходимости вновь и вновь изобретать велосипед.
86
Часть III. Шаблоны проектирования
•
Применение единой терминологии. Профессиональное общение и работа в группе (команде разработчиков) требует наличия единого базового словаря и единой точки зрения на проблему. Шаблоны проектирования предоставляют подобную общую точку зрения как на этапе анализа, так и при реализации проекта.
Однако существует и третья причина, по которой следует изучать шаблоны проек тирования. Шаблоны проектирования предоставляют нам абстрактный высокоуровневый взгляд как на проблему, так и на весь процесс объектноориентированной разра ботки. Это помогает избежать излишней детализации на ранних стадиях проекти рования. Я надеюсь, что, прочитав эту книгу, вы согласитесь с тем, что именно последняя причина является важнейшей для изучения шаблонов проектирования. Я постараюсь изменить ваш образ мышления за счет усиления и развития у вас навыков системного аналитика. Для иллюстрации сказанного выше представим себе разговор двух плотников о 7 том, как сделать выдвижные ящики для письменного стола.
7 Этот диалог взят автором из рассказа Ральфа Джонсона (Ralph Johnson) и соответствующим образом переработан.
Глава 5. Первое знакомство с шаблонами проектирования
87
Плотник 1. Как, по твоему мнению, следует сделать эти ящики? Плотник 2. Ну, я думаю, соединение будет прочным, если сначала сделать пропил прямо поперек доски, а затем повернуть обратно под углом 45 градусов, после чего снова углубиться в дерево, а затем повернуть назад под углом 45 градусов, но уже в другую сторону, потом вновь пилить поперек доски, а затем… Попробуйте понять, о чем здесь идет речь! Не правда ли, довольно запутанное описание? Что именно второй плотник имеет в виду? Чрезмерное количество подробностей мешает уловить смысл обсуждаемого. Попытаемся представить это описание графически. Не напоминает ли это вам устное описание фрагмента программного кода? Веро ятно, программист сказал бы чтото, похожее на следующее: …а затем я использую цикл WHILE, чтобы выполнить…, за которым следует не сколько операторов IF, предназначенных для…, а потом я использую оператор SWITCH для обработки… Здесь представлено подробное описание фрагмента программы, но из него оста ется абсолютно неясным, что эта программа делает и для чего! Конечно, никакой уважающий себя плотник не стал бы говорить ничего подобно го тому, что было приведено выше. В действительности мы услышали бы примерно такой диалог. Плотник 1. Какой вариант соединения мы будем использовать, — "ласточкин хвост" или простое угловое соединение под 45 градусов? Нетрудно заметить, что новый вариант качественно отличается от прежнего. Плотники обсуждают отличия в качестве решения проблемы, поэтому в данном слу чае их общение проходит на более высоком, а значит, и более абстрактном уровне. Они не тратят время и силы на обсуждение специфических деталей отдельных типов соединений и не вдаются в излишние подробности их описания. Когда плотник говорит о простом соединении деревянных деталей под углом 45 гра дусов, он понимает, что такому решению свойственны следующие характеристики. •
Это более простое соединение. Угловое соединение является более простым в изготовлении. Достаточно обрезать торцы соединяемых деталей под углом в 45 градусов, состыковать их, а затем скрепить между собой с помощью гвоздей или клея — как показано на рис. 5.2.
•
Это соединение имеет меньшую прочность. Угловое соединение является менее прочным, чем соединение "ласточкин хвост", и не способно выдерживать большую нагрузку.
•
Это более незаметное соединение. Единственный стык в месте соединения деталей меньше заметен глазу, чем многочисленные распилы в соединении типа "ласточкин хвост".
88
Часть III. Шаблоны проектирования
ÐÈÑ. 5.2. Простое соединение под углом 45 градусов
Когда плотник говорит о соединении типа "ласточкин хвост" (способ выполнения которого был описан выше), он представляет себе его характеристики, которые могут быть вовсе не очевидны для обывателя, но, безусловно, известны любому другому плотнику. •
Это более сложный вариант. Выполнить это соединение сложнее, чем простое угловое, поэтому оно дороже.
•
Это соединение устойчиво по отношению к температуре и влажности. При изменении температуры и влажности воздуха древесина расширяется или сжимается, однако это не оказывает влияния на прочность соединения данного типа.
•
Это соединение не нуждается в дополнительном укреплении. Благодаря точному соответствию вырезов скрепляемых деталей по отношению друг к другу соединение фактически не нуждается в дополнительном укреплении клеем или гвоздями.
•
Это соединение более эстетично. Если соединение этого типа выполнено качест венно, оно оставляет очень приятное впечатление.
Другими словами, соединение типа "ласточкин хвост" надежно и красиво, но менее технологично, а поэтому изготовление его обходится дороже. Таким образом, когда первый плотник задает вопрос: Какой способ соединения мы будем использовать — типа "ласточкин хвост" или обычное, под углом в 45 градусов? В действительности он спрашивает: Мы воспользуемся более сложным и дорогим, но красивым и долговечным соеди нением или сделаем все быстро, не заботясь о высоком качестве, лишь бы ящик продержался до тех пор, пока нам не заплатят? Можно сказать, что в действительности общение плотников происходит на двух уровнях: внешнем уровне произносимых слов и более высоком уровне (метауровне) реальной беседы. Последний скрыт от обывателя и намного богаче первого по содер жанию. Этот более высокий уровень — назовем его уровнем "шаблонов плотников" — отражает реальные вопросы разработки проекта в нашем примере с плотниками.
Глава 5. Первое знакомство с шаблонами проектирования
89
В первом варианте второй плотник затеняет реально обсуждаемые проблемы, углубляясь в детали выполнения соединения. Во втором варианте первый плотник предлагает принять решение о выборе типа соединения, исходя из его стоимости и качества. Чей подход более эффективен? С кем, по вашему мнению, предпочтительнее ра ботать? Шаблоны позволяют нам видеть лес за деревьями, поскольку помогают поднять уро вень мышления. Ниже в этой книге будет показано, что, когда удается подняться на бо лее высокий уровень мышления, разработчику становятся доступны новые методы про ектирования. Именно в этом состоит реальная сила шаблонов проектирования.
Другие преимущества применения шаблонов проектирования Мой опыт работы в группах разработчиков показал, что в результате применения шаблонов проектирования повышается эффективность труда отдельных исполните лей и всей группы в целом. Это происходит изза того, что начинающие члены группы видят на примере более опытных разработчиков, как шаблоны проектирования могут применяться и какую пользу они приносят. Совместная работа дает новичкам стимул и реальную возможность быстрее изучить и освоить эти новые концепции. Применение многих шаблонов проектирования позволяет также создавать более модифицируемое и гибкое программное обеспечение. Причина состоит в том, что эти решения уже испытаны временем. Поэтому использование шаблонов позволяет создавать структуры, допускающие их модификацию в большей степени, чем это воз можно в случае решения, первым пришедшего на ум. Шаблоны проектирования, изученные должным образом, существенно помогают общему пониманию основных принципов объектноориентированного проектирова ния. Я убеждался в этом неоднократно в процессе преподавания курса объектно ориентированного проектирования. Курс я начинал с краткого представления объ ектноориентированной парадигмы, а затем переходил к освещению темы шаблонов проектирования, используя основные концепции ООП (инкапсуляция, наследование и полиморфизм) лишь для иллюстрации излагаемого материала. К концу трехдневно го курса, хотя речь на лекциях шла главным образом о шаблонах, базовые концепции ООП также становились понятными и знакомыми всем слушателям. "Бандой четырех" было предложено несколько стратегий создания хорошего объ ектноориентированного проекта. В частности, они предложили следующее. •
Проектирование согласно интерфейсам.
•
Предпочтение синтеза наследованию.
•
Выявление изменяющихся величин и их инкапсуляция.
Эти стратегии использовались в большинстве шаблонов проектирования, обсуж даемых в данной книге. Для оценки полезности указанных стратегий вовсе не обяза тельно изучить большое количество шаблонов — достаточно всего нескольких. При обретенный опыт позволит применять новые концепции и к задачам собственных проектов, даже без непосредственного использования шаблонов проектирования.
90
Часть III. Шаблоны проектирования
Еще одно преимущество состоит в том, что шаблоны проектирования позволяют разработчику или группе разработчиков находить проектные решения для сложных проблем, не создавая громоздкой иерархии наследования классов. Даже если шаблоны не используются в проекте непосредственно, одно только уменьшение размера иерар хий наследования классов уже будет способствовать повышению качества проекта.
Резюме В этой главе мы выяснили, что представляют собой шаблоны проектирования. Кри стофер Александер сказал: "Шаблоны — это решение проблемы в контексте". Шаблон проектирования это нечто большее, чем просто способ решения какойлибо проблемы. Шаблон проектирования — это способ представления в общем виде как условия задачи, которую необходимо решить, так и правильных подходов к ее решению. Мы также рассмотрели причины для изучения шаблонов проектирования. Шаб лоны могут помочь разработчику в следующем. •
Многократно применять высококачественное решение для повторяющихся задач.
•
Ввести общую терминологию для расширения взаимопонимания в пределах группы разработчиков.
•
Поднять уровень, на котором проблема решается, и избежать нежелательного углубления в детали реализации уже на ранних этапах разработки.
•
Оценить, что было создано — именно то, что нужно, или же просто некоторое работоспособное решение.
•
Ускорить профессиональное развитие как всей группы разработчиков в целом, так и отдельных ее членов.
•
Повысить модифицируемость кода.
•
Обеспечить выбор лучших вариантов реализации проекта, даже если сами шаблоны проектирования в нем не используются.
•
Найти альтернативное решение для исключения громоздких иерархий насле дования классов.
Глава 6
Шаблон Facade
Введение Изучение шаблонов проектирования мы начнем с шаблона, который вам, возможно, уже приходилось применять на практике ранее, но вы не представляли себе, что эта конструкция может иметь специальное название — шаблон проектирования Facade. В этой главе мы выполним следующее. •
Выясним, что представляет собой шаблон проектирования Facade и где он может использоваться.
•
Обсудим ключевые особенности этого шаблона.
•
Познакомимся с некоторыми разновидностями шаблона Facade.
•
Применим шаблон проектирования Facade к задаче о САПР.
Назначение шаблона проектирования Facade В книге "банды четырех" назначение шаблона Facade (фасад) определяется сле дующим образом. Предоставление единого интерфейса для набора различных интерфейсов в сис теме. Шаблон Facade определяет интерфейс более высокого уровня, что упрощает 1 работу с системой. В основном этот шаблон используется в тех случаях, когда необходим новый спо соб взаимодействия с системой — более простой в сравнении с уже существующим. Кроме того, он может применяться, когда требуется использовать систему некоторым специфическим образом — например, обращаться к программе трехмерной графики для построения двухмерных изображений. В этом случае нам потребуется специаль ный метод взаимодействия с системой, поскольку будет использоваться лишь часть ее функциональных возможностей.
Описание шаблона проектирования Facade В моей практике был случай, когда я работал по контракту для большой инже нернопроизводственной компании. В день моего первого появления на новом
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 185.
92
Часть III. Шаблоны проектирования
рабочем месте непосредственный руководитель проекта отсутствовал. Компания отказывалась оплатить этот рабочий день просто так, но никто не знал, какое за дание мне следует поручить. Они хотели, чтобы я делал хоть чтонибудь, даже ес ли это не принесло бы никакой пользы! Думаю, вам тоже приходилось сталки ваться с чемлибо подобным. В конце концов, одна из сотрудниц нашла для меня занятие. Она сказала: "Полагаю, что рано или поздно, но вам обязательно потребуется изучить ту САПР, ко торая используется у нас в настоящее время, — так что вы можете приступить к этому прямо сейчас". После этих слов девушка указала мне на кипу документации, занимав шую на книжной полке почти 3 метра. Книги были напечатаны мелким шрифтом на листах формата А4, и вся эта гора бумаги представляла собой техническое описание однойединственной, но очень сложной системы (рис. 6.1)!
ÐÈÑ. 6.1. Множество томов документации — тех! ническое описание одной сложной системы!
Если четыре или пять человек работают с подобной сложной системой, едва ли есть насущная необходимость каждому из них изучить особенности ее функциониро вания во всех подробностях. Вместо того, чтобы тратить понапрасну время каждого работника группы, лучше будет бросить жребий и поручить проигравшему написать подпрограммы, обеспечивающие остальным сотрудникам единый интерфейс для ра боты с системой. Тот, кому поручат это задание, должен будет определить, как другие члены группы намереваются использовать систему и какой API (прикладной программный интер фейс) будет оптимальным. Затем ему потребуется определить новый класс (или клас сы), предоставляющий требуемый интерфейс. Это позволит остальным членам груп пы просто обращаться к данному интерфейсу, вместо того, чтобы тратить время на подробное изучение сложной системы (рис. 6.2). Этот подход применяется, когда используется только часть всех возможностей системы или когда взаимодействие с ней осуществляется некоторым специфическим образом. Если же необходимо использовать абсолютно все компоненты и функцио нальные возможности системы, то маловероятно, что существует какаялибо возмож
Глава 6. Шаблон Facade
93
ность создать для нее упрощенный интерфейс (если только разработчики системы не отнеслись к своей работе халатно).
ÐÈÑ. 6.2. Изоляция клиентов от внутренних подсистем
Это и есть шаблон проектирования Facade. Он упрощает использование сложной системы, отдельной части системы или обеспечивает обращение к системе некото рым специфическим образом. Мы имеем сложную систему, и нам требуется использо вать только какуюто ее часть (отдельный модуль). В результате применения шаблона Facade мы получим новую, более простую в использовании систему, которая будет точно соответствовать нашим потребностям. Основная часть работы попрежнему будет выполняться исходной системой. Шаб лон Facade предоставляет лишь коллекцию методов, простых в понимании и исполь зовании. Эти методы обращаются к основной системе для реализации вновь опреде ленных функций внешней системы.
Основные характеристики шаблона Facade Назначение
Упростить работу с существующей системой, определив собственный интерфейс обращения к ней
Задача
Необходимо использовать только определенное подмножество функций сложной системы или организовать взаимодействие с ней некоторым специфическим образом
Способ решения
Шаблон Facade предоставляет клиентам новый интерфейс для взаимодействия с уже существующей системой
Участники
Клиенту предоставляется специализированный интерфейс, упрощающий работу с системой
Следствия
Применение шаблона Facade упрощает использование требуемой подсистемы, но одновременно лишает пользователя доступа ко всем функциональным возможностям системы — часть их окажется недоступной
Реализация
•
Определение нового класса (или классов) с требуемым интерфейсом.
•
В своей работе новый класс должен опираться на функциональные возможности существующей системы (рис. 6.3)
94
Часть III. Шаблоны проектирования
ÐÈÑ. 6.3. Стандартное упрощенное представление структуры шаблона проектирования Facade
Дополнительные замечания о шаблоне Facade Шаблон Facade может применяться не только для создания упрощенного интер фейса с целью вызова методов, но и для уменьшения количества объектов, с которы ми клиенту приходится иметь дело. Например, предположим, что существует объект Client (клиент), которому необходимо взаимодействовать с объектами Database (база данных), Model (модель) и Element (элемент). Объект Client должен будет сначала открыть объект Database и получить доступ к объекту Model. Затем он дол жен будет направить объекту Model запрос на получение доступа к объекту Element. И только после этого он сможет получить от объекта Element требуемую информа цию. Однако более простым решением будет создать объект Database Facade, ко торому любой объект Client сможет направить один запрос непосредственно на вы борку информации — как показано на рис. 6.4. Предположим, что помимо использования функций, реализованных в базовой системе, необходимо предоставить пользователям и некоторую новую функциональ ность. В этом случае проблема выходит за рамки простого использования некоторого подмножества функциональных возможностей существующей системы. Необходимые дополнения системы могут быть реализованы за счет добавления в класс Facade новых методов, обеспечивающих требуемую функциональность. В этом случае мы попрежнему имеем дело с шаблоном Facade, но этот шаблон будет расши рен новыми функциональными возможностями. Шаблон Facade определяет общий подход, позволяя нам целенаправленно при ступить к работе. Со стороны клиента шаблон фактически создает для него новый ин терфейс, которым клиент будет пользоваться вместо уже существующего стандартно
Глава 6. Шаблон Facade
95
го интерфейса системы. Это оказывается возможным потому, что объект Client не нуждается в использовании всех функций основной системы.
ÐÈÑ. 6.4. Шаблон Facade позволяет уменьшить количество объектов, видимых клиенту
Шаблоны определяют лишь общий подход Любой шаблон проектирования определяет лишь общий подход к решению задачи. Потребуется или нет добавлять новые функции, зависит от конкретной ситуации. Шаблоны проектирования в сущности только наброски или эскизы, показывающие, как правильно приступить к работе. Не следует представлять их себе некими догматами, высеченными на камне.
Шаблон Facade также может использоваться для сокрытия или инкапсуляции ба зовой системы. В этом случае класс Facade включает основную систему как свой за крытый член. Поэтому основная система будет взаимодействовать только с классом Facade, оставаясь недоступной и невидимой для всех остальных пользователей. Имеется несколько причин для инкапсуляции основной системы. • Контроль за использованием системы. Вынуждая пользователей все обращения к системе выполнять только через класс Facade, легко можно контролировать их доступ к ней. •
Упрощение замены системы. В будущем может потребоваться заменить существую щую систему новой. Представление базовой системы как закрытого члена класса Facade существенно упрощает эту процедуру. Хотя объем необходимых изменений может оказаться достаточно большим, все они будут сосредоточены только в одном месте программного кода — в классе Facade.
96
Часть III. Шаблоны проектирования
Применение шаблона Facade к проблеме САПР Что касается проблемы одновременной работы с различными версиями САПР, то шаблон проектирования Facade может оказаться полезным при организации работы объектов V1Slots, V1Holes и им подобных с объектом V1System. Подробнее мы об судим это в главе 12, Решение задачи САПР с помощью шаблонов проектирования.
Резюме Шаблон проектирования Facade назван так потому, что он как бы устанавливает новый фасад (интерфейс) для основной системы. Шаблон Facade применяется в следующих случаях. •
Когда нет необходимости использовать все функциональные возможности сложной системы и можно создать новый класс, который будет содержать все необходимые средства доступа к базовой системе. Если предполагается работа лишь с ограниченным набором функций исходной системы, как это обычно и бывает, интерфейс (API), описанный в новом классе, будет намного проще, чем стандартный интерфейс, разработанный создателями основной системы.
•
Существует необходимость в инкапсуляции первоначальной системы.
•
Если требуется не только использовать существующие функциональные воз можности базовой системы, но и дополнить их некоторой новой функциональ ностью.
•
Стоимость разработки нового класса меньше стоимости обучения всех участ ников проекта работе с базовой системой или суммы будущих затрат на сопро вождение создаваемой ими системы.
Глава 7
Шаблон Adapter
Введение В этой главе мы продолжим изучение шаблонов проектирования и рассмотрим шаблон Adapter. Это шаблон общего характера и, как мы увидим позднее, часто ис пользуется в комбинации с другими шаблонами. Здесь мы выполним следующее. •
Выясним, что собой представляет шаблон Adapter, где он применяется и каким образом реализуется.
•
Рассмотрим ключевые особенности этого шаблона.
•
Воспользуемся шаблоном Adapter для иллюстрации понятия полиморфизма.
•
Обсудим, как язык UML может использоваться на различных уровнях детализации.
•
Проанализируем некоторые ситуации из практики автора, связанные с при менением шаблона Adapter, а так же сравним шаблоны Adapter и Facade.
•
Применим шаблон Adapter к решению задачи о различных версиях САПР.
Замечание. В тексте главы примеры программного кода приведены на языке Java. Соответствующие примеры кода на языке C++ можно найти в конце главы (листинг 7.2).
Назначение шаблона проектирования Adapter "Банда четырех" так определяет назначение шаблона Adapter. Преобразование стандартного интерфейса класса в интерфейс, более подходящий для нужд клиента. Применение шаблона Adapter позволяет организовать совмест 1 ную работу классов с несовместимыми интерфейсами. Другими словами, данный шаблон предполагает создание нового интерфейса для требуемого объекта, который мы не можем использовать изза его неподходящего ин терфейса.
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements ofReusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 185.
98
Часть III. Шаблоны проектирования
Описание шаблона проектирования Adapter Самый простой способ понять назначение шаблона Adapter — это рассмотреть его применение на примере. Допустим, существуют такие требования. •
Создать классы для представления в системе точек, линий и квадратов, каждый из которых будет иметь метод display (отобразить).
•
Использующие их объекты не должны знать, с каким именно элементом они имеют дело в действительности — с точкой, линией или квадратом. Клиентским объектам достаточно знать, что они получили доступ к объекту, представ ляющему одну из указанных фигур.
Другими словами, необходимо представить эти конкретные фигуры с помощью концепции более высокого порядка — назовем ее изображаемой фигурой. Теперь расширим наш пример, представив себе еще одну близкую ситуацию. •
Необходимо использовать подпрограмму или метод, написанный кемто другим, поскольку в нем реализованы именно те функции, которые нам требуются.
•
При этом включить готовую подпрограмму непосредственно в создаваемую программу невозможно.
•
Интерфейс подпрограммы или способ ее вызова в коде не отвечают тем условиям, в которых она будет использоваться.
Иначе говоря, хотя в системе представлены точки, линии и квадраты, нам нужно, чтобы все выглядело так, как будто в ней существуют только некие абстрактные фигуры. •
Это позволит объектамклиентам работать с любыми типами объектовданных одним и тем же образом, не принимая во внимание существующие между ними различия (рис. 7.1).
•
Кроме того, появляется возможность впоследствии добавлять в систему новые виды фигур, не внося никаких изменений в те объекты, которые будут их использовать.
ÐÈÑ. 7.1. Все объекты, с которыми мы будем работать,… должны выгля! деть одинаково — как абстрактные фигуры
Здесь используется принцип полиморфизма; т.е. в системе присутствуют объекты данные разных типов, но использующие их объектыклиенты должны взаимодейство вать с ними одним и тем же образом. В результате объектклиент может просто указать объектуданному (независимо от того, представляет он точку, линию или квадрат), что необходимо выполнить то или
Глава 7. Шаблон Adapter
99
иное действие — например, отобразить себя на экране или, наоборот, удалить с экра на свое изображение. В этом случае каждый объект, представляющий точку, линию или квадрат, должен будет нести полную ответственность за корректное выполнение требуемых операций в полном соответствии со своим типом. Для решения поставленной задачи создадим класс Shape (фигура), а затем опре делим производные от него классы, представляющие точки (Point), линии (Line) и квадраты (Square) — как показано на рис. 7.2.
ÐÈÑ. 7.2. Классы Point, Line и Square представляют собой разно! 2 видности класса Shape
Прежде всего необходимо определить специфическое поведение, которое должен демонстрировать класс Shape. Для решения этой задачи следует описать в нем ин терфейс вызова методов, ответственных за поведение, а затем реализовать эти мето ды в каждом из порожденных классов. Поведение, которое должен демонстрировать класс Shape, предусматривает сле дующие методы (рис. 7.3). •
Получить данные о положении объекта Shape (метод setLocation).
•
Сообщить данные о положении объекта Shape (метод getLocation).
•
Отобразить представленную объектом фигуру на экране (метод display).
•
Закрасить изображение фигуры указанным цветом (метод fill).
•
Установить цвет закрашивания фигуры (метод setColor).
•
Удалить изображение фигуры с экрана (метод undisplay).
Предположим, что в систему необходимо включить новый тип объектов класса Shape, предназначенный для представления окружностей (не забывайте, что требо вания постоянно изменяются!). Для этой цели создадим новый класс, Circle (окружность), который будет представлять в системе окружности. Реализуем класс Circle как производный от класса Shape, что позволит воспользоваться преимуще ствами его полиморфного поведения.
2
Эта и все другие диаграммы классов, приведенные в данной книге, выполнены с использованием нотации языка UML. Описание языка UML см. в главе 2, UML — унифицированный язык моделиро вания.
100
Часть III. Шаблоны проектирования
ÐÈÑ. 7.3. Методы классов Shape, Point, Line и Square
Теперь необходимо написать методы display, fill и undisplay для класса Circle. Эта задача может оказаться достаточно сложной. К счастью, после недолгих поисков альтернативных решений (что всегда должен делать каждый хороший программист), выяснилось, что один из моих коллег уже описал в системе класс XXCircle, предназначенный для работы с окружностями (рис. 7.4). К сожалению, он предварительно не посоветовался со мной, как лучше на звать методы этого класса, поэтому они получили следующие имена: •
displayIt
•
fillIt
•
undisplayIt
ÐÈÑ. 7.4. Класс XXCircle
В результате, непосредственно использовать класс XXCircle нельзя, поскольку желательно сохранить полиморфное поведение, реализованное в классе Shape, но этому препятствуют следующие моменты. •
Класс Shape и класс XXCircle включают методы с разными именами и различными списками параметров.
•
Класс XXCircle должен не только иметь совпадающие имена методов, но и обязательно являться производным от класса Shape.
Глава 7. Шаблон Adapter
101
Маловероятно, что коллега согласится на изменение имен методов и вывод класса XXCircle из класса Shape. Дав такое согласие, он вынужден будет внести соответст вующие изменения в код всех объектов в системе, которые так или иначе взаимодейст вуют с классом XXCircle. Кроме того, изменение программного кода, созданного дру гим разработчиком, обычно чревато появлением непредвиденных побочных эффектов. В результате получается, что использовать готовый класс нельзя, а повторно выпол нять всю работу по созданию требуемого класса с нуля нежелательно. Что же делать? Можно создать новый класс, который действительно будет порожден от класса Shape и, следовательно, содержать реализацию интерфейса, описанного в этом клас се, но не будет включать методы работы с окружностями, реализованные в классе XXCircle. Образуется следующая структура, представленная на рис. 7.5. Класс Circle: •
является производным от класса Shape;
•
включает класс XXCircle;
•
переадресует сделанные к нему запросы объекту класса XXCircle.
ÐÈÑ. 7.5. Пример использования шаблона Adapter — класс Circle служит оболочкой для класса XXCircle
Закрашенный ромб на конце линии, соединяющей классы Circle и XXCircle (см. рис. 7.5), означает, что класс Circle содержит класс XXCircle. При создании эк земпляра объекта класса Circle необходимо будет создать и соответствующий экзем пляр объекта класса XXCircle. Запросы к объекту Circle на выполнение любых дей ствий будут просто передаваться объекту XХCircle. Если реализовать все это надле жащим образом, и если объект XXCircle будет включать все функциональные возможности, которые должен поддерживать объект Circle (чуть позже мы обсудим, что произойдет, если это не так), то объект Circle сможет продемонстрировать
102
Часть III. Шаблоны проектирования
нужное поведение, просто возложив на объект XXCircle обязанности по выполне нию всей необходимой работы. Пример создания классаоболочки приведен в листинге 7.1. Листинг 7.1. Фрагмент реализации шаблона Adapter на языке Java class Circle extends Shape { ... private XXCircle pxc; ... public Circle () { pxc= new XXCircle(); } void public display() { pxc.displayIt(); } }
Применение шаблона Adapter позволило сохранить все преимущества полимор физма при работе с классом Shape. Другими словами, объекты, обращающиеся к объ екту Shape, не знают, какой именно объект в действительности обрабатывает их за просы. Это также пример нового осмысления понятия инкапсуляция. Класс Shape ин капсулирует в себе представляемые им конкретные фигуры. Шаблон Adapter обычно используется для реализации в системе полиморфизма. Как будет показано в после дующих главах, он чаще всего применяется для обеспечения полиморфизма, необхо димого другим шаблонам проектирования.
Основные характеристики шаблона Adapter Назначение
Организовать использование функций объекта, недоступного для модификации, через специально созданный интерфейс
Задача
Система поддерживает требуемые данные и поведение, но имеет неподходящий интерфейс. Чаще всего шаблон Adapter применяется, если необходимо создать класс, производный от вновь определяемого или уже существующего абстрактного класса
Способ решения
Шаблон Adapter предусматривает создание класса"оболочки с требуемым интерфейсом
Участники
Класс Adapter приводит интерфейс класса Adaptee в соответствие с интерфейсом класса Target (от которого класс Adapter является производным). Это позволяет объекту Client использовать объект Adaptee так, словно он является экземпляром класса Target (рис. 7.6)
Следствия
Шаблон Adapter позволяет включать уже существующие объекты в новые объектные структуры, независимо от различий в их интерфейсах
Реализация
Включение уже существующего класса в другой класс. Интерфейс включающего класса приводится в соответствие с новыми требованиями, а вызовы его методов преобразуются в вызовы методов включенного класса
Глава 7. Шаблон Adapter
103
ÐÈÑ. 7.6. Стандартное упрощенное представление структуры шаблона проектирования Adapter
Дополнительные замечания о шаблоне Adapter Часто встречаются ситуации, подобные описанной выше, но при этом повторно используемый объект не обладает всей требуемой функциональностью. В этом случае также можно применить шаблон Adapter, хотя это и не позволит сразу получить готовое решение. Правильный подход в подобной ситуации может быть таким. •
Функции, реализованные в уже существующем классе, просто адаптируются по описанной выше схеме.
•
Отсутствующие функции реализуются заново — непосредственно в объекте оболочке.
Это менее эффективное решение, но оно все же позволяет избежать реализации всей требуемой функциональности. Шаблон Adapter позволяет в процессе проектирования не принимать во внимание возможных различий в интерфейсах уже существующих классов. Если имеется класс, обладающий требуемыми методами и свойствами, — по крайней мере, концептуально, то при необходимости всегда можно будет воспользоваться шаблоном Adapter для приведения его интерфейсов к нужному виду. Эта особенно важно, если применяется сразу несколько шаблонов, поскольку мно гие шаблоны требуют, чтобы используемые в них классы были порождены от одного и того же класса. Если в проект предстоит включить уже существующие классы, шаб лон Adapter может использоваться для адаптации их к требуемому абстрактному клас су (подобно тому, как класс ХХCircle был адаптирован к абстрактному классу Shape с помощью классаоболочки Circle). Фактически существует два типа шаблонов Adapter. •
Объектный шаблон Adapter. Тот вариант шаблона Adapter, который мы об суждали выше, называют объектным шаблоном Adapter, поскольку он реализуется посредством помещения одного объекта (адаптируемого) в другой (адапти рующий).
104
•
Часть III. Шаблоны проектирования
Классовый шаблон Adapter. Второй вариант реализации шаблона Adapter использует механизм множественного наследования и получил название клас сового шаблона Adapter.
Выбор варианта шаблона Adapter зависит от специфики проблемной области. На концептуальном уровне различия можно игнорировать, однако на этапе реализа 3 ции проекта потребуется принять во внимание некоторые дополнительные аспекты. На моих лекциях по изучению шаблонов проектирования почти всегда находится какойлибо слушатель, который заявляет, что не видит разницы между шаблонами Adapter и Facade. В обоих случаях имеется уже существовавший ранее класс (или классы), не обладающий требуемым интерфейсом, и в обоих случаях мы создаем но вый объект, имеющий тот интерфейс, который нам требуется (рис. 7.7).
ÐÈÑ. 7.7. Объект Client использует другой, уже существо вавший ранее объект Preexisting, интерфейс которого нас не устраивает
Оболочка и объектоболочка — это термины, которые можно слышать довольно часто. Принято считать, что использование объектовоболочек для уже существующих сис тем упрощает работу с ними. На этом самом высоком уровне шаблоны Facade и Adapter действительно кажутся похожими. В каждом из них используются классыоболочки, но это оболочки разного типа. Очень важно четко понимать то довольно тонкое различие, которое существует между ними. В этом нам поможет сравнение свойств обоих шаблонов (табл. 7.1). Таблица 7.1. Сравнение шаблона Facade с шаблоном Adapter Adapter
Facade
Использование уже существующих классов
Да
Да
Требуется разработка нового интерфейса
Нет
Да
Необходима поддержка полиморфизма
Нет
Вероятно
Требуется упростить существующий интерфейс
Да
Нет
Из сведений, приведенных в табл. 7.1, можно сделать следующие выводы. •
Как шаблон Facade, так и шаблон Adapter используют уже существующие классы.
•
Однако для шаблона Facade необходимо самостоятельно разработать требуемый интерфейс, а не использовать уже существующий, как для шаблона Adapter.
3 Рекомендации по выбору конкретного варианта шаблона Adapter можно найти в книге “банды четырех” на с. 142–144.
Глава 7. Шаблон Adapter
105
•
Реализация шаблона Facade не предусматривает поддержки полиморфизма, тогда как при применении шаблона Adapter это может оказаться необходимым. (Возможны ситуации, при которых требуется лишь поддержка определенного API, а значит, при использовании шаблона Adapter полиморфизм нас интере совать не будет — именно поэтому здесь употреблено слово "может").
•
При использовании шаблона Facade наша цель — упростить существующий интерфейс. А для шаблона Adapter нам требуется поддержка уже существую щего интерфейса и не допускаются никакие упрощения, даже если они вполне возможны.
Иногда слушатели приходят к заключению, что различие между шаблоном Facade и шаблоном Adapter состоит в том, что шаблон Facade скрывает множество классов, а шаблон Adapter — только один. Хотя довольно часто это замечание оказывается спра ведливым, возможны и противоположные ситуации. Например, шаблон Facade может применяться для работы с одним, но очень сложным объектом, в то время как шаблон Adapter может предоставлять оболочку для нескольких мелких объектов с целью объ единения той функциональности, которая в них реализована. Выводы. Шаблон Facade предназначен для упрощения интерфейса, тогда как шаблон Adapter предназначен для приведения различных существующих интерфей сов к единому требуемому виду.
Применение шаблона Adapter к проблеме САПР В задаче о поддержке различных версий САПР (см. главу 3, Проблема, требующая создания гибкого кода) элементы в системе версии V2 были представлены объектами OOGFeature. К сожалению, эти объекты имели интерфейс, не отвечающий нашим требованиям (в данной конкретной ситуации), поскольку они были созданы другими разработчиками и их невозможно было сделать производными от класса Feature. Тем не менее, в самой системе версии V2 они функционировали вполне корректно. В данной ситуации вариант создания новых классов, реализующих все функции объектов САПР, даже не рассматривался. Единственно возможное решение состояло в организации взаимодействия с объектами OOGFeature, и самый простой способ достижения этой цели заключался в использовании шаблона Adapter.
Резюме Шаблон Adapter — это очень полезный шаблон, позволяющий привести интер фейс класса (или нескольких классов) к интерфейсу требуемого нам вида. Это дости гается посредством определения нового классаоболочки с требуемым интерфейсом и помещения в него исходного класса, методы которого будут вызываться методами классаоболочки.
106
Часть III. Шаблоны проектирования
Приложение. Пример программного кода на языке C++ Листинг 7.2. Фрагмент реализации шаблона Adapter на языке С++ class Circle : public Shape { . . . private: XXCircle *pxc; . . . } Circle::Circle () { . . . pxc= new XXCircle; } void Circle::display () { pxc->displayIt(); }
Глава 8
Расширение горизонтов
Введение В предыдущих главах уже упоминалось о трех фундаментальных концепциях объектноориентированного проектирования — объектах, инкапсуляции и абстракт ных классах. Очень важно, как проектировщик применяет эти концепции. Традици онный подход к пониманию указанных терминов ограничивает возможности разра ботки. В этой главе мы вернемся на шаг назад и более подробно рассмотрим темы, уже обсуждавшиеся ранее. Здесь так же раскрываются особенности нового взгляда на объектноориентированное проектирование, связанные с появлением шаблонов про ектирования. В этой главе мы выполним следующее. •
Сравним в противопоставлении следующие подходы: − традиционный, рассматривающий объекты как совокупность данных и мето дов, и новый, понимающий под объектом предмет, имеющий некоторые обязательства; − традиционный, рассматривающий инкапсуляцию исключительно как меха низм сокрытия данных, и новый, понимающий под инкапсуляцией способ ность к сокрытию чего угодно. Особенно важно понимать, что инкапсуля ция может быть применена и для сокрытия различий в поведении; − традиционный способ применения механизма наследования с целью реализации специализации и повторного использования кода и новый, понимающий под наследованием метод классификации объектов.
•
Проанализируем, как новый подход позволяет объектам демонстрировать раз личное поведение.
•
Рассмотрим, как различные уровни проектирования — концептуальный, специ фикаций и реализации — соотносятся с абстрактным классом и его производ ными классами.
Вероятно, предложенный здесь новый подход вовсе не является оригинальным. Я уверен, что именно он позволил создателям шаблонов проектирования разработать ту концепцию, которую сейчас принято называть шаблоном. Несомненно, что этот подход был известен и Кристоферу Александеру, и Джиму Коплину (Jim Coplien), и "банде четырех". Хотя предлагаемый здесь подход нельзя считать оригинальным, я уверен, что он никогда не обсуждался ранее в таком виде, как это сделано в данной книге. В отличие
108
Часть III. Шаблоны проектирования
от других авторов я предпринял попытку отделить рассмотрение шаблонов от обсуж дения особенностей их поведения. Говоря о новом подходе, я подразумеваю, что для большинства разработчиков это, вероятно, совершенно новый взгляд на объектноориентированное проектирование. По крайней мере, для меня он был таковым, когда я впервые приступил к изучению шаблонов проектирования.
Объекты: традиционное представление и новый подход Традиционное представление об объектах состоит в том, что они представляют собой совокупность данных и методов их обработки. Один из моих преподавателей назвал их "умными данными". Лишь один шаг отделяет их от баз данных. Подобное представление возникает при взгляде на объекты с точки зрения их реализации. Хотя данное определение является совершенно точным — как объяснялось выше, в главе 1, Объектноориентированная парадигма, — оно построено при взгляде на объек ты с точки зрения их реализации. Более полезным является определение, построен ное при взгляде на объекты с концептуальной точки зрения, когда объект рассматри вается как сущность, имеющая некоторые обязательства. Эти обязательства опреде ляют поведение объекта. В некоторых случаях мы также будем представлять объект как сущность, обладающую конкретным поведением. Такое определение предпочтительнее, поскольку оно помогает сосредоточиться на том, что объект должен делать, не задаваясь преждевременно вопросом о том, как это можно реализовать. Подобный подход позволяет разделить процедуру разработки программного обеспечения на два этапа. 1. Создание предварительного проекта без излишней детализации всех процессов. 2. Реализация разработанного проекта в кодах. В конечном счете, этот подход позволяет более точно выбрать и определить объект (в смысле отправной точки любого проекта). Второе определение объекта является бо лее гибким, поскольку позволяет сосредоточиться на том, что объект делает, а механизм наследования предоставляет инструмент реализации его поведения по мере необходи мости. При взгляде на объект с точки зрения реализации также можно достичь этого, но гибкость в этом случае, как правило, обеспечивается более сложным путем. Гораздо проще рассуждать в терминах обязательств, так как это помогает опреде лить открытый интерфейс объекта. Если объект обладает обязательствами, то оче видно, что должен существовать какойто способ попросить его выполнить данные обязательства. Однако это требование никак не соотносится с тем, что находится внутри объекта. Информация, с которой связаны обязательства объекта, вовсе необя зательно должна находиться непосредственно в самом этом объекте. Предположим, что существует объект Shape, имеющий такие обязательства. •
Знать свое местоположение.
•
Уметь отобразить себя на экране монитора.
•
Уметь удалить свое изображение с экрана монитора.
Глава 8. Расширение горизонтов
109
Этот набор обязательств предполагает существование определенного набора ме тодов, необходимых для их реализации: •
метод GetLocation(...);
•
метод DrawShape(...);
•
метод UnDrawShape(...).
Для нас не имеет никакого значения, что именно находится внутри объекта Shape. Единственное, что нас интересует, это чтобы объект Shape обеспечивал требуемое по ведение. Для этой цели могут использоваться атрибуты внутри объекта или методы, вы числяющие требуемые результаты или даже обращающиеся к другим объектам. Таким образом, объект Shape может как содержать атрибуты, описывающие его местораспо ложение, так и обращаться к другим объектам базы данных, чтобы получить сведения о своем местоположении. Такой подход предоставляет высокую гибкость, необходимую для эффективного моделирования процессов в проблемной области. Интересно отметить, что перенос акцента с реализации на мотивацию — харак терная черта описания шаблонов проектирования. Стремитесь всегда рассматривать объекты в указанном ракурсе. Выбор такого под хода в качестве основного позволит вам создавать превосходные проекты.
Инкапсуляция: традиционное представление и новый подход На лекциях по курсу проектирования с применением шаблонов я часто обращаюсь к студентам со следующим вопросом: "Кто из вас знаком с определением инкапсуля ции как механизма сокрытия данных?". В ответ почти каждый человек в аудитории поднимает руку. Затем я рассказываю им историю о моем зонтике. Следует заметить, что живу я в городе Сиэтл, штат Вашингтон, который отличается очень влажным климатом. По этому зонтики и плащи с капюшоном — вещь, совершенно необходимая жителям это го города осенью, зимой и весной. Так вот, мой зонтик довольно большой — фактически, вместе со мной под ним мо гут найти укрытие еще три или четыре человека. Спрятавшись от дождя под этим зонтиком, мы можем перемещаться из одного помещения в другое, оставаясь сухими. Зонтик оборудован акустической стереосистемой, помогающей не скучать, пока мы находимся под его защитой. Представьте себе, что в нем имеется даже система кон диционирования воздуха, регулирующая окружающую температуру. Одним словом, это весьма комфортабельный зонтик. Зонтик чрезвычайно удобен и всегда ждет меня там, где я его оставляю. У него есть колесики и его не нужно переносить с места на место в руках. Более того, перемеще ние этого зонтика происходит вообще без моего участия, поскольку он оборудован собственным двигателем. Если нужно, можно даже открыть специальный люк вверху моего зонтика, чтобы впустить под него лучи солнца. (Почему я пользуюсь зонтиком при солнечной погоде, я вам объяснять не стану.) В Сиэтле есть сотни тысяч таких зонтиков различных типов и цветов. Большинство людей называет их автомобилями.
110
Часть III. Шаблоны проектирования
Однако лично я думаю о своем автомобиле как о зонтике, ведь зонтик — это пред мет, которым пользуются, чтобы укрыться от дождя. Каждый раз, когда я ожидаю ко гонибудь на улице во время дождя, я сижу в моем "зонтике", чтобы оставаться сухим! Конечно, в действительности автомобиль — это не зонтик. Но несомненно и то, что его вполне можно использовать для защиты от дождя, хотя это будет очень огра ниченное восприятие автомобиля. Точно так же, инкапсуляция — это не просто со крытие данных. Такое слишком узкое представление ограничивает возможности про ектировщика. Инкапсуляцию следует понимать как "любой вид сокрытия". Другими словами, это механизм, способный скрывать данные. Но этот же механизм может использоваться для сокрытия реализации, порожденных классов и многого другого. Обратимся к диа грамме, представленной на рис. 8.1. Мы уже встречались с этой диаграммой в главе 7, Шаблон Adapter.
ÐÈÑ. 8.1. Адаптация класса XXCircle с помощью класса Circle
На рис. 8.1 представлено несколько видов инкапсуляции. •
Инкапсуляция данных. Данные классов Point, Line, Square и Circle скрыты от всего остального мира.
•
Инкапсуляция методов. Например, метод setLocation() класса Circle является скрытым.
•
Инкапсуляция подклассов. Объектамклиентам класса Shape классы Point, Line, Square или Circle не видны.
•
Инкапсуляция другого объекта. Ничто в системе, кроме объектов класса Circle, не знает о существовании объектов класса XXCircle.
Глава 8. Расширение горизонтов
111
Следовательно, один тип инкапсуляция достигается созданием абстрактного клас са, который демонстрирует полиморфное поведение, и клиент данного абстрактного класса не знает, с каким именно производным от него классом он имеет дело в дейст вительности. Таким образом, адаптация интерфейса позволяет скрыть, что именно находится за адаптирующим объектом. Преимущество подобного взгляда на инкапсуляцию состоит в том, что он упроща ет разбиение (декомпозицию) программы. Инкапсулированные уровни в этом случае становятся интерфейсами, которые необходимо будет спроектировать. Инкапсуля ция различных видов фигур в классе Shape позволяет добавлять в систему новые классы без модификации тех клиентских программ, которые их используют. Инкап суляция объекта XXCircle в объекте Circle позволяет изменить его реализацию в будущем, если в этом возникнет потребность. Когда объектноориентированная парадигма была впервые предложена, повтор ное использование классов понималось как одно из ее важнейших преимуществ. Обычно оно достигалось посредством разработки классов с последующим созданием на их основе новых, производных классов. Для процедуры создания этих подклассов, порожденных от других, базовых классов, использовался термин специализация (а для обратного перехода к базовому классу — генерализация). Здесь я не собираюсь поднимать вопрос о точности этой терминологии, а просто рассматриваю тот подход, который в моем понимании представляет собой более мощный способ использования наследования. В приведенном выше примере можно было создать проект системы, основываясь на методе специализации класса Shape (с получением таких классов, как Point, Line, Square и Circle). Однако в этом слу чае мне, вероятно, не удалось бы спрятать указанные специализированные классы по отношению к методам использования класса Shape. Наоборот, скорее всего, я попы тался бы воспользоваться преимуществами знания особенностей реализации каждого из производных классов. Однако, если рассматривать класс Shape как обобщение для классов Point, Line, Square и Circle, то это упрощает восприятие их как единого понятия. В подобном случае разработчик, вероятнее всего, предпочтет разработать интерфейс и сделать класс Shape абстрактным. А это, в свою очередь, означает, что если потребуется вве сти в систему поддержку новой фигуры, то необходимая модернизация существующе го кода будет минимальна, поскольку никакой из объектовклиентов не знает, с каким именно подтипом класса Shape он имеет дело.
Найдите то, что изменяется, и инкапсулируйте это 1
В своей книге "банда четырех" предлагает следующее. Выделите то, что будет изменяемым в вашем проекте. Этот подход противоположен методу, построенному на установлении причины, вынуждающей прибегнуть к пе ределке проекта. Вместо выявления тех причин, которые будут способны заста вить нас внести изменения в проект, следует сосредоточиться на том, что мы хо 1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 29.
112
Часть III. Шаблоны проектирования
тим уметь изменять, не переделывая проект. Идея состоит в том, что следует ин капсулировать те концепции, которые могут изменяться, — лейтмотив многих шабло нов проектирования. Иными словами это можно выразить так: "Найдите изменяемое и инкапсулируйте это". Подобное утверждение покажется странным, если понимать инкапсуляцию только как сокрытие данных. Но оно выглядит весьма разумным, если под инкапсуляцией понимать сокрытие конкретных классов с помощью абстрактных. Помещение в текст ссылок на абстрактный класс позволяет скрыть его возможные вариации. В действительности, многие шаблоны проектирования используют инкапсуляцию для распределения объектов по различным уровням. Это позволяет разработчику вносить изменения на одном уровне, не оказывая влияния на другой. Этим достигает ся слабая связанность между объектами разных уровней. Данный принцип очень важен для шаблона Bridge (мост), о котором речь пойдет в главе 9, Шаблон Bridge. Однако перед этим хотелось бы обсудить те предубеждения, которые присущи многим разработчикам. Предположим, что мы работаем над проектом, заключающимся в моделировании различных характеристик животных. Требования к проекту состоят в следующем. •
Каждый тип животного имеет различное количество ног. − Представляющий животное объект должен быть способен запоминать и возвращать эту информацию.
•
Каждый тип животного использует различный способ передвижения. − Представляющий животное объект должен быть способен возвращать сведения о том, сколько времени понадобится животному для перемещения из одного места в другое по земной поверхности заданного типа.
Типичный подход к представлению различного количества ног у животного со стоит в том, что объект будет включать данноечлен, содержащее соответствующее значение, а также два метода для записи и считывания этого значения. Однако для представления различного поведения обычно используется другой подход. Предположим, что существует два различных метода передвижения животных: бег и полет. Поддержка этого требования потребует написания двух различных фрагмен тов программного кода — один для представления бега, а другой для представления полета. Вполне очевидно, что простой переменной в этом случае будет недостаточно. Требование реализации двух различных методов ставит нас перед необходимостью выбрать один из двух возможных подходов. •
Использование элемента данных, предназначенного для хранения сведений о типе передвижения животного, представленного объектом.
•
Создание двух различных типов класса Animal (животное), производных от абстрактного класса Animal. Один из них будет предназначен для представ ления бегающих животных, а другой — летающих.
К сожалению, оба указанных подхода не лишены недостатков. •
Сильная связанность. Первый подход (использование флажка в совокупности с построенным на нем переключателем) ведет к сильной связанности, которая
Глава 8. Расширение горизонтов
113
отрицательно проявится, если флажку потребуется принимать какието дополнительные значения. В любом случае требуемый программный код будет довольно громоздким. •
Излишняя детализация. Второй подход требует жесткого присвоения подтипа класса Animal и не позволяет представлять в системе животных, способных и бегать, и летать одновременно.
Существует и третий вариант: пусть класс Animal включает объект, представляю щий соответствующий способ передвижения (как показано на рис. 8.2).
ÐÈÑ. 8.2. Объект класса Animal содержит объект класса AnimalMovement
На первый взгляд такое решение кажется чрезмерно сложным. Однако в сущности оно совсем простое, поскольку предполагается лишь то, что класс Animal будет включать объект, представляющий способ передвижения животного. Подобный ме тод решения очень напоминает включение в класс данногочлена, содержащего све дения о количестве ног животного, — отличие лишь в том, что в этом случае сведения содержатся в данномчлене специфического объектного типа. Похоже, что на кон цептуальном уровне все это отличается сильнее, чем есть на самом деле, поскольку рис. 8.2 и 8.3 совершенно не похожи друг на друга.
ÐÈÑ. 8.3. Представление содержи мого объекта как данногочлена
Многие разработчики полагают, что объект, содержащий другой объект, существен но отличается от объекта, содержащего только простые элементы данных. Однако дан ныечлены класса, которые обычно кажутся нам отличными от объектов (например, це лые числа или числа двойной точности), на самом деле также являются объектами. В объектноориентированном программировании все является объектом, даже те встро енные типы данных, поведение которых является сугубо арифметическим.
114
Часть III. Шаблоны проектирования
Способы использования объектов для запоминания значений атрибутов и запомина ния различных типов поведения фактически не отличаются друг от друга. Проще всего подтвердить это утверждение на конкретном примере. Предположим, необходимо соз дать систему управления сетью торговых точек. В этой системе должна обрабатываться приходная накладная. В накладной присутствует итоговая сумма. Для начала представим эту итоговую сумму как число с двойной точностью (тип Double). Однако если система предназначена для международной торговой сети, то очень скоро выяснится, что она обя зательно должна обеспечивать конвертирование валют и другие подобные операции. Поэтому при расчетах потребуется использовать класс Money (деньги), включающий как значение суммы, так и описание типа валюты. Теперь для представления итоговой суммы в накладной необходимо будет использовать объект класса Money. На первый взгляд использование класса Money в данном случае вызвано только необходимостью хранения дополнительных данных. Однако, когда потребуется кон вертировать сумму в объекте класса Money из одного типа валюты в другой, именно данный объект Money должен будет выполнить такие преобразования, поскольку ка ждый объект должен нести ответственность сам за себя. Может показаться, что для выполнения преобразования достаточно просто добавить в класс еще одно данное член, предназначенное для хранения коэффициента пересчета. Однако в действительности все может оказаться сложнее. Например, от объекта может потребоваться выполнять конвертирование валюты по курсу прошлых перио дов. В этом случае при расширении функциональных возможностей класса Money до класса Currency (валюта) мы по существу расширяем и функциональные возможно сти класса SalesReceipt (накладная), поскольку он включает в свой состав объекты класса Money (или класса Currency). В последующих главах данная стратегия использования вложенных объектов для реализации требуемого поведения класса будет продемонстрирована при обсуждении нескольких новых шаблонов проектирования.
Общность и изменчивость в абстрактных классах Обратимся к рис. 8.4, на котором отражены взаимосвязи между следующими кон цепциями. •
Анализ общности и анализ изменчивости.
•
Концептуальный уровень, уровень спецификаций и уровень реализации.
•
Абстрактный класс, его интерфейс и производные от него классы.
Как показано на рис. 8.4, анализ общности имеет отношение к концептуальному уровню проекта системы, тогда как анализ изменчивости относится к уровню его реа лизации, т.е. к процедурам конкретного воплощения системы. Уровень спецификаций занимает промежуточное положение между двумя другими уровнями разработки системы. Поэтому он предусматривает выполнение обоих видов анализа. На уровне спецификаций определяется, как будет происходить взаимодейст вие с множеством объектов, которые концептуально схожи друг с другом, — т.е. каж дый из этих объектов представляет конкретный вариант некоторой общей концеп ции. На уровне реализации каждая такая общая концепция описывается абстрактным классом или интерфейсом.
Глава 8. Расширение горизонтов
115
ÐÈÑ. 8.4. Взаимосвязи между анализом общности/изменчивости, уровнями детализации и аб страктными классами
Новый взгляд на объектноориентированное проектирование кратко описывается в табл. 8.1. Таблица 8.1. Новые концепции ООП Концепция ООП
Пояснение
Абстрактный класс → центральная объе диняющая концепция
Абстрактный класс используется для пред ставления базовой концепции, объединяю щей все порожденные от него классы. Эта базовая концепция отражает некоторую общность, имеющую место в предметной области
Общность → какие абстрактные классы должны использоваться
Общности определяют абстрактные классы, которые должны существовать в системе
Вариации → классы, производные от аб страктного класса
Вариации, выявленные в пределах некото рой общности, представляются классами, производными от соответствующего абст рактного класса
Специализация → интерфейс для абст рактного класса
Интерфейс этих производных классов со ответствует уровню спецификаций
Подобный подход упрощает процесс проектирования классов и сводит его к про цедуре из двух этапов, описанных в табл. 8.2.
116
Часть III. Шаблоны проектирования
Таблица 8.2. Проектирование классов Когда определяется...
Следует задаться вопросом...
Абстрактный класс (общность)
Какой интерфейс позволит обратиться ко всем обязательствам этого класса?
Порожденные классы (изменчивость)
Как выполнить данную конкретную реали зацию (вариацию) исходя из имеющейся спецификации?
Взаимосвязь между уровнем спецификаций и концептуальным уровнем можно описать так. На уровне спецификаций определяется интерфейс, который необходим для реали зации всех вариантов проявления (вариаций) некоторой концепции (общности), выявленной на концептуальном уровне. Взаимосвязь между уровнем спецификаций и уровнем реализации можно описать так. Как, исходя из заданной спецификации, можно реализовать данное проявление (вариацию)?
Резюме Традиционное понимание концепций объекта, инкапсуляции и наследовании весьма ограниченно. Инкапсуляция представляет собой нечто большее, чем просто сокрытие данных. Расширение определения инкапсуляции до концепции сокрытия любых существующих категорий позволяет распределить объекты по разным уров ням. Это, в свою очередь, позволяет вносить изменения на одном уровне, исключив нежелательное влияние этих действий на объекты другого уровня. Наследование лучше использовать как метод определенной обработки различных конкретных классов, являющихся концептуально идентичными, а не как средство проведения специализации. Концепция использования объектов для поддержки вариаций в поведении ничем не отличается от практики использования элементов данных для поддержки вариа ций в значениях данных. Оба подхода предусматривают инкапсуляцию (т.е. расшире ние) как данных, так и поведения объектов.
Глава 9
Шаблон Bridge
Введение Продолжим изучение шаблонов проектирования и рассмотрим шаблон Bridge (мост). Шаблон Bridge несколько сложнее тех шаблонов, о которых речь шла раньше, но и намного полезнее их. В этой главе мы выполним следующее. •
Выведем концепцию шаблона Bridge, исходя из конкретного примера. Обсуж дение будет проведено очень подробно, что должно помочь вам понять сущность этого шаблона.
•
Рассмотрим ключевые особенности этого шаблона.
•
Обсудим несколько поучительных примеров использования шаблона Bridge из моей личной практики.
Назначение шаблона проектирования Bridge Согласно определению "банды четырех" "назначение шаблона Bridge состоит в от делении абстракции от реализации таким способом, который позволит им обеим из 1 меняться независимо". Я точно помню что впервые прочитав это определение, подумал: Хм, ерунда какаято! А затем: Я понимаю каждое слово в этом предложении, но не имею ни малейшего представле ния, что оно может означать. Я знал следующее. •
Отделение означает обеспечение независимого поведения элементов, или, по крайней мере, явное указание на то, что между ними существуют некоторые отношения.
•
Абстракция — это концептуальное представление о том, как различные элемен ты связаны друг с другом.
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 151.
118
Часть III. Шаблоны проектирования
Я также считал, что реализация — это конкретный способ представления абстрак ции, поэтому меня смущало предложение отделить абстракцию от конкретного спо соба ее реализации. Как оказалось, мое замешательство было вызвано недопониманием того, что здесь в действительности представляет собой реализация. В данном случае под реализацией понимались те объекты, которые абстрактный и производные от него классы исполь зовали для реализации своих функций (а не те классы, которые называются конкрет ными и являются производными от абстрактного класса). Но, честно говоря, даже ес ли бы я все понимал правильно, не уверен, что это бы сильно мне помогло. Концеп ция, выраженная в приведенном выше определении, достаточно сложна, чтобы понять ее с первого раза. Если вам сейчас тоже не до конца понятно, для чего предназначен шаблон Bridge, пусть это вас не беспокоит. Если же назначение этого шаблона вам вполне очевидно, то надо отдать должное вашей сообразительности. Bridge — один из самых трудных для понимания шаблонов. До некоторой степени это вызвано тем, что он является очень мощным и часто применяется в самых раз личных ситуациях. Кроме того, он противоречит распространенной практике при менения механизма наследования для реализации специальных случаев. Тем не ме нее, этот шаблон служит превосходным примером следования двум основным лозун гам технологии шаблонов проектирования: "Найди то, что изменяется, и инкап сулируй это" и "Компоновка объектов в структуру предпочтительней обращения к на следованию классов" (в дальнейшем мы убедимся в этом).
Описание шаблона проектирования Bridge на примере Чтобы лучше понять идею построения шаблона Bridge и принципы его работы, рас смотрим конкретный пример поэтапно. Сначала обсудим установленные требования, а затем проанализируем вывод основной идеи шаблона и способы его применения. Возможно, данный пример может показаться слишком простым. Однако присмот ритесь к обсуждаемым в нем концепциям, а затем попытайтесь вспомнить аналогич ные ситуации, с которыми нам приходилось сталкиваться ранее. Обратите особое внимание на следующее. •
Наличие вариаций в абстрактном представлении концепций.
•
Наличие вариаций в том, как эти концепции реализуются.
Полагаю, у вас не появится сомнений, что приведенный ниже пример имеет много общего с обсуждавшейся выше задачей поддержки нескольких версий САПР. Однако мы не станем предварительно обсуждать все предъявляемые требования. Как правило, приступая к решению задачи, не удается сразу увидеть все существующие вариации. Рекомендация. Формулируя требования к проекту, старайтесь как можно раньше и как можно чаще обдумывать, что в нем может изменяться в дальнейшем. Предположим, что нам нужно написать программу, которая будет выводить изо бражения прямоугольников с помощью одной из двух имеющихся графических про грамм. Кроме того, допустим, что указания о выборе первой (Drawing Program 1 —
Глава 9. Шаблон Bridge
119
DP1 ) или второй (DP2) графической программы будут предоставляться непосредственно при инициализации прямоугольника. Прямоугольники определяются двумя парами то чек, как это показано на рис. 9.1. Различия между гра фическими программами описаны в табл. 9.1. ÐÈÑ. 9.1. Принцип описания прямоугольника
Таблица 9.1. Различия между двумя графическими программами Методы в DP1
Методы в DP2
Метод отображе ния линий
draw_a_line(x1, y1, x2, y2)
drawline (x1, x2, y1, y2)
Метод отображе ния окружностей
draw_a_circle(x, y, r)
drawcircle(x, y, r)
Заказчик поставил условие, что объект коллекции (клиент, использующий про грамму отображения прямоугольников) не должен иметь никакого отношения к тому, какая именно графическая программа будет использоваться в каждом случае. Исходя из этого я пришел к заключению, что, поскольку при инициализации прямоугольника указывается, какая именно программа должна использоваться, можно создать два раз личных типа объекта прямоугольника. Один из них будет вызывать для отображения программу DP1, а другой — программу DP2. В каждом типе объекта будет присутство вать метод отображения прямоугольника, но реализованы они будут поразному — как показано на рис. 9.2.
ÐÈÑ. 9.2. Схема программы отображения прямоугольников с помощью программ DP1 и DP2
120
Часть III. Шаблоны проектирования
Создав абстрактный класс Rectangle (прямоугольник), можно воспользоваться тем преимуществом, что единственное несовпадение между различными типами его производных классов Rectangle состоит в способе реализации метода drawLine(). В классе V1Rectangle он реализован посредством ссылки на объект DP1 и его метод Draw_a_line(), а в классе V2Rectangle — посредством ссылки на объект DP2 и его метод drawline(). Таким образом, выбрав при инициализации требуемый тип объ екта Rectangle, в дальнейшем можно полностью игнорировать эти различия. В лис тинге 9.1 приведен пример реализации этих объектов на языке Java. Листинг 9.1. Пример реализации классов на языке Java. Версия 1 Class Rectangle{ Public void draw () { DrawLine (_x1, _y1, _x2, _y1); DrawLine (_x2, _y1, _x2, _y2); DrawLine (_x2, _y2, _x1, _y2); DrawLine (_x1, _y2, _x1, _y1); } abstract protected void DrawLine (double x1, double y1, double x2, double y2); } Class V1Rectangle extends Rectangle { DrawLine (double x1, double y1, double x2, double y2) { DP1.draw_a_line (x1, y1, x2, y2); } } Class V2Rectangle extends Rectangle { DrawLine (double x1, double y1, double x2, double y2) { // Аргументы в методе программы DP2 отличаются // и должны быть переупорядочены DP2.drawline (x1, x2, y1, y2); } }
Предположим, что сразу после завершения и отладки кода, приведенного в листин ге 9.1, на нас обрушивается одно из трех неизбежных зол (имеется в виду смерть, налоги и изменение требований). Необходимо включить в программу поддержку еще одного ви да геометрических фигур — окружностей. Однако при этом уточняется, что объект кол лекции (клиент) не должен ничего знать о различиях, существующих между объектами, представляющими прямоугольники (Rectangle) и окружности (Circle). Логично будет сделать вывод, что можно применить выбранный ранее подход и просто добавить еще один уровень в иерархию классов программы. Потребуется только добавить в проект новый абстрактный класс (назовем его Shape (фигура)), а классы Rectangle и Circle будут производными от него. В этом случае объект Client сможет просто обращаться к объекту класса Shape, совершенно не заботясь о том, какая именно геометрическая фигура им представлена. Для аналитика, только начинающего работать в области объектноориентиро ванной разработки, такое решение может показаться вполне естественным — реали зовать изменение в требованиях только с помощью механизма наследования. Начав
Глава 9. Шаблон Bridge
121
со схемы, представленной на рис. 9.2, добавим к ней новый уровень с классом Shape. Затем для всех видов фигур организуем их реализацию с помощью каждой из графи ческих программ, создав по отдельному производному классу для вызова объектов DP1 и DP2, как в классе Rectangle, так и в классе Circle. В итоге будет получена схема, подобная представленной на рис. 9.3.
ÐÈÑ. 9.3. Простейший прямолинейный подход к реализации двух фигур с помощью двух гра фических программ
Класс Circle можно реализовать тем же способом, что и класс Rectangle — как показано в листинге 9.2. Единственное отличие состоит в том, что вместо метода drawLine() используется метод drawCircle(). Листинг 9.2. Пример реализации классов на языке Java. Версия 2 abstract class Shape { abstract public void draw (); } abstract class Rectangle public void draw () { drawLine(_x1, _y1, drawLine(_x2, _y1, drawLine(_x2, _y2, drawLine(_x1, _y2, }
extends Shape { _x2, _x2, _x1, _x1,
abstract protected void drawLine(
_y1); _y2); _y2); _y1);
122
Часть III. Шаблоны проектирования
double x1, double y1, double x2, double y2); } class V1Rectangle extends Rectangle { protected void drawLine ( double x1, double y1, double x2, double y2) { DP1.draw_a_line(x1, y1, x2, y2); } } class V2Rectangle extends Rectangle { protected void drawLine ( double x1, double x2, double y1, double y2) { DP2.drawline(x1, x2, y1, y2); } } abstract class Circle { public void draw () { drawCircle(x, y, r); } abstract protected void drawCircle ( double x, double y, double r); } class V1Circle extends Circle { protected void drawCircle() { DP1.draw_a_circle(x, y, r); } } class V2Circle extends Circle { protected void drawCircle() { DP2.drawcircle(x, y, r); } }
Для того чтобы лучше понять особенности этого проекта, рассмотрим конкретный пример. Например, проанализируем, как работает метод draw() класса V1Rectangle. •
Метод draw() класса Rectangle не изменился (в нем четыре раза подряд вызывается метод drawLine()).
•
Метод drawLine() реализуется посредством обращения к методу draw_a_line() объекта DP1.
Схематично все это представлено на рис. 9.4.
Глава 9. Шаблон Bridge
123
ÐÈÑ. 9.4. Диаграмма последовательностей обращения к объекту класса V1Rectangle
Чтение диаграмм последовательностей Выше, в главе 2, мы уже обсуждали язык UML — унифицированный язык моделирования. Диаграмма на рис. 9.4 представляет собой один из видов диаграмм взаимодействия, называемый диаграммой последовательностей. Это типичная диаграмма языка UML. Ее назначение состоит в том, чтобы продемонстрировать взаимодействие объектов в системе.
•
Каждый прямоугольник вверху диаграммы обозначает объект. Он может быть поименован или нет.
•
Если у объекта есть имя, оно указывается слева от двоеточия.
•
Класс, которому принадлежит объект, указывается справа от двоеточия. Например, на рис. 9.4 имя среднего объекта myRectangle, и этот объект представляет собой экземпляр класса V1Rectangle.
Диаграмму последовательностей следует читать сверху вниз. На диаграмме каждое пронумерованное предложение представляет собой сообщение, отправленное объектом самому себе или другому объекту.
• • •
Последовательность начинается с непоименованного объекта класса Client, вызывающего метод draw() объекта myRectangle класса V1Rectangle. Этот метод четыре раза вызывает метод drawLine() собственного объекта (этапы 2, 4, 6 и 8 на схеме). Обратите внимание на изогнутую стрелку, указывающую на оси времени на сам объект myRectangle. В свою очередь, метод drawLine() каждый раз вызывает метод draw_a_line() непоименованного объекта класса DP1 (этапы 3, 5, 7 и 9 на диаграмме).
124
Часть III. Шаблоны проектирования
Несмотря на то что при взгляде на диаграмму классов складывается впечатление о наличии на ней множества объектов, в действительности, мы имеем дело только с тремя объектами, как показано на рис. 9.5. •
Объект класса Client, потребовавший отобразить прямоугольник.
•
Объект класса V1Rectangle.
•
Объект класса DP1, представляющий графическую программу.
ÐÈÑ. 9.5. Объекты, представленные на диаграмме последовательностей
Когда объектклиент посылает сообщение объекту класса V1Rectangle (с именем myRectangle) с требованием выполнить метод draw(), последний реализует метод draw() класса Rectangle, выполняя этапы со 2 по 9, показанные на диаграмме. К сожалению, этот подход создает новые проблемы. Взгляните еще раз на рис. 9.3 и обратите внимание на третий ряд классов. Глядя на них, можно сделать следующие выводы. •
Классы в этом ряду представляют те четыре конкретных подтипа класса Shape, с которыми мы имеем дело.
•
Что произойдет, если появится третий тип графической программы, т.е. еще один возможный вариант реализации графических функций? В этом случае потребуется создать шесть различных подтипов класса Shape (для представле ния двух видов фигур и трех графических программ).
•
Теперь предположим, что требуется реализовать поддержку нового, третьего, типа фигур. В этом случае нам понадобятся уже девять различных подтипов класса Shape (для представления трех видов фигур и трех графических программ).
Быстрый рост количества требуемых производных классов вызван тем, что в дан ном решении абстракция (виды фигур) и реализация (графические программы) же стко связаны между собой. Каждый класс, представляющий конкретный тип фигуры, должен точно знать, какую из существующих графических программ он использует. Для решения данной проблемы необходимо отделить изменения в абстракции от из менений в реализации таким образом, чтобы количество требуемых классов возрас тало линейно — как показано на рис. 9.6.
Глава 9. Шаблон Bridge
125
Вот мы и пришли к приведенному в начале главы определению назначения шаб лона Bridge — отделение абстракции от реализации, выполненное таким способом, 2 который позволит им обеим изменяться независимо .
ÐÈÑ. 9.6. Шаблон Bridge отделяет изменения в абстракции от изменений в реализации
Перед тем как рассмотреть возможное решение проблемы и вывести из него шаб лон Bridge, остановимся на нескольких других проблемах (помимо комбинаторного взрыва количества классов). Еще раз посмотрим на рис. 9.3 и зададимся вопросом: какие еще недостатки имеет данный проект? •
Существует ли в нем избыточность?
•
Что характерно для данного проекта — сильная или слабая связность?
•
Сильно или слабо связаны между собой отдельные элементы проекта?
•
Согласились бы вы выполнять сопровождение программ этого проекта в будущем?
Злоупотребление наследованием Будучи начинающим разработчиком объектно"ориентированных проектов, я обычно решал проблемы обсуждаемого типа, выделяя их как специальные случаи с последующим применением механизма наследования. Мне нравилась сама идея наследования, поскольку это представлялось мне новым и мощным подходом. Я использовал наследование всегда, когда это было возможным. Такой подход выглядит вполне естественным для новичка, но, в сущности, он весьма наивен. К сожалению, многие из существующих методик обучения объектно"ориентированному проектированию фокусируют главное внимание на абстракции данных, что делает проектирование чрезмерно зависимым от внутренней организации объектов. И даже когда я стал опытным разработчиком, то еще долго сохранял верность парадигме проектирования, основанной на широком использовании механизма наследования. Иными словами, я определял характеристики созда" ваемых классов исходя из особенностей их реализации. Однако характеристики объектов должны определяться их обязательствами, а не их содержанием или внутренней структурой. Скажем, объекты могут отвечать за предоставление информации о самих себе. Например, объекту, представляющему в системе покупателя, может потребоваться умение сообщать его имя. Следует стремиться думать об объектах прежде всего в терминах их обязательств, а не внутренней структуры.
2
Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object
126
Часть III. Шаблоны проектирования
Опытные разработчики объектно"ориентированных проектов пришли к выводу, что для полной реализации всей мощи механизма наследования следует применять его выборочно. Использование шаблонов проектирования помогает быстрее это понять. Оно позволяет перейти от выделения отдельной специализации для каждого изменения (методом наследования) к перемещению этих изменений в используемые объекты сторонней или собственной разработки.
Поначалу, столкнувшись с указанными выше проблемами, я решил, что причина их появления кроется в неправильном построении иерархии наследования. Поэтому я попробовал применить альтернативный вариант иерархии классов, представлен ный на рис. 9.7.
ÐÈÑ. 9.7. Альтернативный вариант иерархии наследования классов
Здесь попрежнему используются те же четыре класса, представляющие все воз можные комбинации. Однако в этой версии устранена избыточность пакетов классов для графических программ DP1 и DP2. К сожалению, в этом варианте мне так и не удалось устранить избыточность, свя занную с наличием двух типов классов Rectangle и двух типов классов Circle, при чем в каждой паре родственных классов используется один и тот же метод draw(). Как бы там ни было, исключить в новой схеме упомянутое выше комбинаторное увеличение количества классов так и не удалось. Диаграмма последовательностей для нового решения представлена на рис. 9.8. Хотя по сравнению с первоначальным решением новый вариант иерархии классов действительно обладает определенными преимуществами, он все же имеет недостат
Oriented Software, Reading, MA: AddisonWesley, 1995, с. 151.
Глава 9. Шаблон Bridge
127
ки. Здесь попрежнему сохраняются проблемы слабой связности классов и их избы точной связанности между собой.
ÐÈÑ. 9.8. Диаграмма последовательностей для нового варианта иерар хии классов
Замечание. Эта версия иерархии классов попрежнему не вызывает желания со провождать программный код системы! Должно существовать лучшее решение.
Ищите альтернативы на начальной стадии проектирования Хотя предложенный здесь альтернативный вариант иерархии наследования классов немного лучше первоначального, следует отметить, что сама по себе практика поиска альтернативы первоначальному варианту проекта — это хороший стиль разработки. Слишком часто проектировщики останавливаются на том варианте, который первый пришел им в голову, и в дальнейшем не предпринимают каких"либо попыток его улучшить. Я не призываю вас к чрезмерно глубокому и исчерпывающему изучению всех возможных альтернатив (это еще один способ ввергнуть проект в состояние "паралича за счет анализа"). Однако сделать шаг назад и подумать, как можно было бы преодолеть затруднения, свойственные исходному варианту проекта, — это верный подход. Фактически, именно подобный шаг назад и отказ от дальнейшей работы над заведомо слабым проектным решением привели меня к пониманию всей мощи подхода с использованием шаблонов проектирования, описанию которого, собственно, и посвящена эта книга.
128
Часть III. Шаблоны проектирования
Замечания об использовании шаблонов проектирования Впервые знакомясь с идеологией шаблонов проектирования, люди часто излишне сосредоточиваются на тех решениях, которые предлагаются шаблонами. Это кажется им вполне разумным, поскольку данная идеология преподносится как совокупность оптимальных решений разнообразных задач. Однако это не совсем правильный подход. Если изучать шаблоны проектирования преимущественно с точки зрения тех решений, которые они предлагают, то это за трудняет понимание, в каких ситуациях те или иные шаблоны применяются. Каждый шаблон сам по себе сообщает только о том, что надо сделать, но умалчивает о том, ко гда это надо делать и почему. Я пришел к выводу, что при изучении шаблонов полезнее сосредоточиться на кон тексте их применения — т.е. на тех проблемах, которые пытаются решить с их помо щью. Это позволяет получить ответы на вопросы когда и почему. Такой подход пере кликается с философией шаблонов Александера: "Каждый шаблон описывает про блему, которая возникает в данной среде снова и снова, а затем предлагает принцип 3 ее решения таким способом…" . Попробуем применить данный подход к нашему случаю и сформулируем задачу, для решения которой предназначен шаблон Bridge. Шаблон Bridge полезен тогда, когда имеется некоторая абстракция и существует не сколько различных вариантов ее реализации. Шаблон позволяет абстракции и реали зации изменяться независимо друг от друга. Указанные характеристики как нельзя лучше соответствуют нашей ситуации. Поэтому можно сделать вывод, что шаблон Bridge следует использовать, даже еще не зная толком, как он реализуется на практике. То, что абстракцию можно будет менять независимо от ее реализации, означает, что новые элементы абстракции можно будет добавлять, не внося какихлибо изменений на уровне реализации и наоборот. Существующее у нас решение не обеспечивает подобной независимости измене ний. Не вызывает сомнения, что проект был бы гораздо лучше, если бы удалось найти решение, предоставляющее такую возможность. Замечание. Обратите особое внимание на то, что, даже не зная конкретных спо собов реализации шаблона Bridge, мы смогли прийти к выводу о возможности и по лезности его применения в нашем проекте. Позднее вы поймете, что это замечание справедливо в отношении практически всех шаблонов проектирования. Иными сло вами, всегда можно установить, что применение того или иного шаблона в данной предметной области будет возможно и полезно, даже не имея точного представления о том, как именно он может быть реализован.
3 Alexander C., Ishikawa S., Silverstein M. A Pattern Language: Towns/Buildings/Construction, New York, NY: Oxford University Press, 1977, с. X.
Глава 9. Шаблон Bridge
129
Описание шаблона проектирования Bridge и его вывод Теперь, когда достигнуто ясное понимание стоящей перед нами проблемы, при шло время общими усилиями вывести шаблон Bridge. Самостоятельный вывод этого шаблона поможет нам осознать его сложность и, одновременно, мощь. Используем на практике некоторые из основных положений качественного объ ектноориентированного проектирования — они помогут нам найти решение, очень близкое к шаблону Bridge. С этой целью обратимся к работе Джима Коплина по ана 4 лизу общности и изменчивости .
Шаблоны проектирования — это проектные решения, применяемые многократно Каждый шаблон проектирования — это решение, которое успешно применялось в различных задачах на протяжении определенного времени и тем доказало свою надежность и качество. Подход, принятый в этой книге, состоит в том, чтобы вывести решение, положенное в основу шаблона, что позволит лучше изучить и понять его основные характеристики. Мы уже выяснили, что шаблоном, который необходимо вывести в данном случае, является шаблон Bridge, — выше обсуждалось его определение в книге "банды четырех" и рассматривалась возможность его применения к некоторой реальной задаче. Однако следует напомнить, что в действительности этот шаблон нами еще не выведен. По определению всякий шаблон характеризуется повторяемостью — чтобы считаться шаблоном, некоторое решение должно быть применено, по крайней мере, в трех независимых случаях. Под словом "вывести" здесь подразумевается, что в процессе проектирования самостоятельно будет найдено решение, соответствующее идее шаблона, как если бы до этого момента мы не имели о нем никакого представления. Именно этот подход позволит нам выявить ключевые принципы и полезные стратегии использования данного шаблона.
Работа Дж. Коплина по анализу общности и изменчивости включает рекоменда ции по поиску того, что изменяется и что является общим в заданной проблемной об ласти. Этими рекомендациями мы и воспользуемся. Наша задача состоит в том, чтобы определить, где возможны изменения (анализ общности), а затем установить, как это изменение происходит (анализ изменчивости). По Коплину, "анализ общности заключается в поиске общих элементов, что помо 5 жет понять, чем члены семейства похожи друг на друга." Таким образом, это процесс поиска общих черт во всех элементах, составляющих некоторое семейство (и, следо вательно, их различий). Анализ изменчивости позволяет установить, каким образом члены семейства из меняются. Изменчивость имеет смысл только в пределах данной общности. Анализ общности предусматривает выявление структур, которые вряд ли будут изме няться с течением времени, тогда как анализ изменчивости заключается в обнаруже нии структур, которые, вероятно, изменятся. Анализ изменчивости имеет смысл толь ко в терминах контекста, определенного предварительным анализом общности… В архитектурном смысле анализ общности дает архитектуре ее долговечность, а анализ изменчивости способствует достижению удобства в использовании. 4 Coplein 5 Coplein
J. MultiParadigm Design for C++. Reading, MA: AddisonWesley, 1998. J. MultiParadigm Design for C++. Reading, MA: AddisonWesley, 1998, с. 63.
130
Часть III. Шаблоны проектирования
Другими словами, если изменчивость — это особые случаи в рамках заданной пред метной области, то общность устанавливает в ней концепции, объединяющие эти осо бые случаи между собой. Общие концепции будут представлены в системе абстрактны ми классами. Вариации, обнаруженные при анализе изменчивости, реализуются по средством создания конкретных классов, производных от этих абстрактных классов. В объектноориентированном проектировании стала уже почти аксиомой практи ка, когда разработчик, анализируя описание проблемной области, выделяет в нем су ществительные и создает объекты, представляющие их. Затем он отыскивает глаголы, связанные с этими существительным (т.е. их действия), и реализует их, добавляя к объектам необходимые методы. Подобный процесс проявления повышенного вни мания к существительным и глаголам в большинстве случаев приводит к созданию слишком громоздкой иерархии классов. Я считаю, что анализ общности и изменчиво сти как первичный инструмент выделения объектов, в действительности, предпочти тельнее поиска существительных и глаголов в описании предметной области. (Полагаю, что книга Джима Коплина убедительно подтверждает эту точку зрения.) В практике проектирования для работы с изменяющимися элементами применя ются две основные стратегии. •
Найти то, что изменяется, и инкапсулировать это.
•
Преимущественно использовать композицию вместо наследования.
Ранее для координации изменяющихся элементов разработчики часто создавали обширные схемы наследования классов. Однако вторая из приведенных выше страте гий рекомендует везде, где только возможно, заменять наследование композицией. Идея состоит в том, чтобы инкапсулировать изменения в независимых классах, что позволит при обработке будущих изменений обойтись без модификации программно го кода. Одним из способов достижения подобной цели является помещение каждого подверженного изменениям элемента в собственный абстрактный класс с последую щим анализом, как эти абстрактные классы соотносятся друг с другом.
Внимательный взгляд на инкапсуляцию Чаще всего начинающих разработчиков объектно"ориентированных систем учат, что инкапсуляция заключается в сокрытии данных. К сожалению, это очень ограниченное определение. Несомненно, что инкапсуляция действительно позволяет скрывать данные, но она может использоваться и для многих других целей. Еще раз обратимся к рис. 7.2, на котором показано, как инкапсуляция применяется на нескольких уровнях. Безусловно, с ее помощью скрываются данные для каждой конкретной фигуры. Однако обратите внимание на то, что объект Client также ничего не знает о конкретных типах фигур. Следовательно, объект Client не имеет сведений о том, что объекты класса Shape, с которыми он взаимодействует, в действительности являются объектами разных конкретных классов — Rectangle и Circle. Таким образом, тип конкретного класса, с которым объект Client взаимодействует, скрыт от него (инкапсулирован). Это тот вид инкапсуляции, который подразумевается во фразе "найдите то, что изменяется, и инкапсулируйте это". Здесь как раз было обнаружено то, что изменяется, и оно было скрыто за "стеной" абстрактного класса (см. главу 8, Расширение горизонтов).
Рассмотрим данный процесс на примере задачи с вычерчиванием прямоугольника. Сначала идентифицируем то, что изменяется. В нашем случае это различные типы фигур (представленных абстрактным классом Shape) и различные версии графиче ских программ. Следовательно, общими концепциями являются понятия типа фигуры
Глава 9. Шаблон Bridge
131
и графической программы, как показано на рис. 9.9. (Обратите внимание на то, что имена классов выделены курсивом, поскольку это абстрактные классы.)
ÐÈÑ. 9.9. Сущности, которые изменяются в нашем примере
В данном случае предполагается, что абстрактный класс Shape инкапсулирует концепцию типов фигур, с которыми необходимо работать. Каждый тип фигуры дол жен знать, как себя нарисовать. В свою очередь, абстрактный класс Drawing (рисование) отвечает за вычерчивание линий и окружностей. На рисунке указанные выше обязательства представлены посредством определения соответствующих мето дов в каждом из классов. Теперь необходимо представить на схеме те конкретные вариации, с которыми мы будем иметь дело. Для класса Shape это прямоугольники (класс Rectangle) и окружно сти (класс Circle). Для класса Drawing это графические программы DP1 (класс V1Drawing) и DP2 (класс V2Drawing). Все это схематически показано на рис 9.10.
ÐÈÑ. 9.10. Представление конкретных вариаций
Сейчас наша диаграмма имеет очень упрощенный вид. Определенно известно, что класс V1Drawing будет использовать программу DP1, а класс V2Drawing — программу DP2, но на схеме еще не указано, как это будет сделано. Пока мы просто описали кон цепции, присутствующие в проблемной области (фигуры и графические программы), и указали их возможные вариации. Имея перед собой два набора классов, очевидно, следует задаться вопросом, как они будут взаимодействовать друг с другом. На этот раз попытаемся обойтись без то го, чтобы добавлять в систему еще один новый набор классов, построенный на углуб лении иерархии наследования, поскольку последствия этого нам уже известны (см. рис. 9.3 и 9.7). На этот раз мы подойдем с другой стороны и попробуем опреде лить, как эти классы могут использовать друг друга (в полном соответствии с приве денным выше утверждением о предпочтительности композиции над наследованием). Главный вопрос здесь состоит в том, какой же из абстрактных классов будет исполь зовать другой?
132
Часть III. Шаблоны проектирования
Рассмотрим две возможности: либо класс Shape использует классы графических программ, либо класс Drawing использует классы фигур. Начнем со второго варианта. Чтобы графические программы могли рисовать раз личные фигуры непосредственно, они должны знать некоторую общую информацию о фигурах: что они собой представляют и как выглядят. Однако это требование нару шает фундаментальный принцип объектной технологии: каждый объект должен не сти ответственность только за себя. Это требование также нарушает инкапсуляцию. Объекты класса Drawing должны были бы знать определенную информацию об объектах класса Shape, чтобы иметь возможность отобразить их (а именно — тип конкретной фигуры). В результате объ екты класса Drawing фактически оказываются ответственными не только за свое соб ственное поведение. Вернемся к первому варианту. Что если объекты класса Shape для отображения себя будут использовать объекты класса Drawing? Объектам класса Shape не нужно знать, какой именно тип объекта класса Drawing будет использоваться, поэтому клас су Shape можно разрешить ссылаться на класс Drawing. Дополнительно класс Shape в этом случае можно сделать ответственным за управление рисованием. Последний вариант выглядит предпочтительнее. Графически это решение пред ставлено на рис. 9.11.
ÐÈÑ. 9.11. Связывание абстрактных классов между собой
В данном случае класс Shape использует класс Drawing для проявления собствен ного поведения. Мы оставляем без внимания особенности реализации классов V1Drawing, использующего программу DP1, и V2Drawing, использующего программу DP2. На рис. 9.12 эта задача решена посредством добавления в класс Shape защищен ных методов drawLine() и drawCircle(), которые вызывают методы drawLine() и drawCircle() объекта Drawing, соответственно.
Глава 9. Шаблон Bridge
133
ÐÈÑ. 9.12. Расширение проекта
Одно правило, одно место При проектировании реализации очень важно следовать стратегии, согласно которой каждое правило реализуется только в одном месте. Другими словами, если имеется некоторое правило, определяющее способ выполнения каких"либо действий, оно должно быть реализовано только в одном месте. Такой подход обычно приводит к получению программного кода с большим количеством коротких методов. При этом за счет минимальных дополнительных усилий устраняется дублирование и удается избежать множества потенциальных проблем. Дублирование вредно не только из"за выполнения дополнительной работы по многократному вводу одинаковых фрагментов кода, но и в большей степени из"за вероятности того, что при каких"либо изменениях в будущем модификация будет произведена не везде. В методе draw() класса Rectangle можно было бы непосредственно вызвать метод DrawLine() того объекта класса Drawing, к которому обращается объект класса Shape. Однако, следуя указанной выше стратегии, можно улучшить программный код, создав в классе Shape метод drawLine(), который и будет вызывать метод DrawLine() класса Drawing. Я не считаю себя консерватором (по крайней мере, в большинстве случаев), но в этом вопросе я полагаю совершенно необходимым всегда соблюдать установленные правила. В примере, который будет обсуждаться ниже, класс Shape содержит метод drawLine(), описывающий правило вычерчивания линии объектом Drawing. Аналогичный метод drawCircle() предназначен для отображения окружностей. Следуя рекомендуемой здесь стратегии, мы готовим фундамент для появления других производных классов фигур, при вычерчивании которых могут потребоваться линии и окружности. Где впервые была предложена стратегия реализации каждого правила в одном месте? Хотя многие упоминают о ней в своих публикациях, она утвердилась в фольклоре разработчиков объектно"ориентированных проектов уже давно и всегда представлялось как оптимальная практика проектирования. Совсем недавно Кент Бекк (Kent Beck) назвал эту стратегию правилом "однажды и только однажды"6. Он определяет это правило так.
•
Система (и код, и тесты) должна иметь доступ ко всему, к чему, по вашему мнению, она должна иметь доступ.
•
Система не должна содержать никакого дублирующегося кода.
Эти две составляющие вместе и представляют собой правило "однажды и только однажды".
6 Beck K. Extreme Programming Explained: Embrace Change, Reading, MA: Addison Wesley, 2000, с. 108, 109.
134
Часть III. Шаблоны проектирования
На рис. 9.13 показано, как абстракция Shape может быть отделена от реализации
Drawing. С точки зрения методов, новый вариант системы весьма похож на реализацию, построенную на наследовании (см. рис. 9.3). Главное отличие состоит в том, что здесь методы расположены в различных объектах. Как уже было сказано в начале этой главы, мое замешательство при первом зна комстве с определением шаблона Bridge было вызвано неправильным пониманием термина "реализация". Поначалу я полагал, что этот термин относится к тому, как конкретная абстракция реализуется в программном коде. Шаблон Bridge позволяет взглянуть на реализацию как на нечто, находящееся вне наших объектов, нечто используемое этими объектами. В результате мы получаем на много большую свободу за счет сокрытия вариации в реализации от вызывающей час ти программы. Разрабатывая объекты по этому принципу, я также обнаружил воз можность размещения вариаций различного типа в раздельных иерархиях классов. Иерархия, изображенная на рис. 9.13 слева, включает вариации в абстракциях. Иерархия справа включает вариации в том, как будут реализованы эти абстракции. Такой подход отвечает новой парадигме создания объектов (использование анализа общности и изменчивости), упомянутой выше.
ÐÈÑ. 9.13. Диаграмма классов, иллюстрирующая отделение абстракции от реализации
Очень легко визуализировать сказанное, если вспомнить, что в любой момент времени в системе существует только три взаимодействующих объекта — несмотря на то, что в ней реализовано около десятка различных классов (рис. 9.14).
Глава 9. Шаблон Bridge
135
ÐÈÑ. 9.14. В любой момент времени в программе существует только три объекта
Достаточно полный фрагмент программного кода для нашего примера представ лен в листинге 9.3 для языка Java и в листингах 9.4–9.6, помещенных в приложение к этой главе, для языка C++. Листинг 9.3. Фрагмент кода на языке Java class Client { public static void main (String argv[]) { Shape r1, r2; Drawing dp; dp = new V1Drawing(); r1 = new Rectangle(dp, 1, 1, 2, 2); dp = new V2Drawing(); r2 = new Circle(dp, 2, 2, 3); r1.draw(); r2.draw(); } } abstract class Shape { abstract public draw() ; private Drawing _dp; Shape (Drawing dp) { _dp= dp; } public void drawLine ( double x1, double y1, double x2, double y2) { _dp.drawLine(x1, y1, x2, y2); } public void drawCircle (
136
Часть III. Шаблоны проектирования
double x, double y, double r) { _dp.drawCircle(x, y, r); } } abstract class Drawing { abstract public void drawLine ( double x1, double y1, double x2, double y2); abstract public void drawCircle ( double x, double y, double r); } class V1Drawing extends Drawing { public void drawLine ( double x1, double y1, double x2, double y2) { DP1.draw_a_line(x1, y1, x2, y2); } public void drawCircle ( double x, double y, double r) { DP1.draw_a_circle(x, y, r); } } class V2Drawing extends Drawing { public void drawLine ( double x1, double y1, double x2, double y2) { // У программы DP2 порядок аргументов отличается // и они должны быть перестроены DP2.drawline(x1, x2, y1, y2); } public void drawCircle ( double x, double y, double r) { DP2.drawcircle(x, y, r); } } class Rectangle extends Shape { public Rectangle ( Drawing dp, double x1, double y1, double x2, double y2) { super(dp) ; _x1 = x1; _x2 = x2; _y1 = y1; _y2 = y2; } public void draw () { drawLine(_x1, _y1, _x2, drawLine(_x2, _y1, _x2, drawLine(_x2, _y2, _x1, drawLine(_x1, _y2, _x1, }
_y1); _y2); _y2); _y1);
} class Circle extends Shape { public Circle ( Drawing dp, double x, double y, double r) { super(dp) ; _x = x; _y = y; _r = r ; }
Глава 9. Шаблон Bridge
137
public void draw () { drawCircle(_x, _y, _r); } } // Ниже приведена упрощенная реализация // для классов DP1 and DP2 class DP1 { static public void draw_a_line ( double x1, double y1, double x2, double y2) { // Реализация } static public void draw_a_circle( double x, double y, double r) { // Реализация } } class DP2 { static public void drawline ( double x1, double x2, double y1, double y2) { // Реализация } static public void drawcircle ( double x, double y, double r) { // Реализация } }
Особенности использования шаблона Bridge Теперь, когда мы проанализировали, как шаблон Bridge работает, стоит посмот реть на него с более концептуальной точки зрения. Как показано на рис. 9.13, шаблон включает две части — абстрактную (со своими производными классами) и реализации. При проектировании с использованием шаблона Bridge полезно всегда помнить об этих двух частях. Интерфейс в части реализации следует разрабатывать с учетом осо бенностей различных производных классов того абстрактного класса, который этот интерфейс будет поддерживать. Обратите внимание на то, что проектировщик не обязательно должен помещать в интерфейс реализации всех возможных производ ных классов абстрактного класса (это еще одна возможная причина возникновения "паралича от анализа"). Следует принимать во внимание только те производные клас сы, которые действительно необходимо поддерживать. Не раз и не два авторы этой книги получали подтверждение, что даже небольшое усилие по увеличению гибкости в этой части проекта существенно его улучшает. Замечание. В языке C++ реализация шаблона Bridge должна осуществляться толь ко с помощью абстрактного класса, определяющего открытый интерфейс. В языке Java могут использоваться как абстрактный класс, так и интерфейс. Выбор зависит от того, дает ли преимущество разделение в реализации общих черт абстрактных классов. (Подробности — в книге "Peter Coad, Java Design", описание которой можно найти в главе 22.)
138
Часть III. Шаблоны проектирования
Дополнительные замечания о шаблоне Bridge Обратите внимание на то, что решение, представленное на рис. 9.12 и 9.13, объеди няет шаблоны Adapter и Bridge. Это сделано изза того, что графические программы, которые необходимо использовать, были заранее жестко заданы. В этих программах уже есть интерфейсы, с которыми мы вынуждены работать. Поэтому, чтобы можно бы ло работать с ними одинаково, необходимо воспользоваться шаблоном Adapter. Несмотря на то что на практике шаблон Adapter зачастую оказывается включен ным в шаблон Bridge, он, тем не менее, вовсе не является частью шаблона Bridge.
Основные характеристики шаблона Bridge Назначение
Отделение набора объектов реализаций от набора объектов, использующих их
Задача
Классы, производные от абстрактного класса, должны использовать множество классов реализации этой абстракции, не вызывая при этом лавинообразного увеличения количества классов в системе
Способ решения
Определение интерфейса для всех используемых версий реализации и использование его во всех классах, порожденных от абстрактного
Участники
Класс Abstraction определяет интерфейс для объектов, представляющих реализации. Класс Implementor определяет интерфейс для конкретных реализации. Классы, производные от класса Abstraction, используют производные от класса Implementor, не интересуясь тем, с каким именно ConcreteImplementorX они имеют дело
Следствия
Отделение классов реализаций от объектов, которые их используют, упрощает расширение системы. Объекты"клиенты ничего не знают об особенностях конкретных реализаций
Реализация
• •
сторону классов классы, классом
Инкапсуляция вариантов реализации в абстрактном классе. Включение методов работы с ним в базовый абстрактный класс, представляющий абстракцию, подлежащую реализации.
Замечание. В языке Java вместо абстрактного класса, подлежащего реализации, можно использовать интерфейс
ÐÈÑ. 9.15. Стандартное упрощенное представление шаблона Bridge
Когда несколько шаблонов интегрируются в одно целое (подобно шаблонам Bridge и Adapter в нашем примере), результатом является так называемый составной
Глава 9. Шаблон Bridge
139
7
шаблон проектирования. Это позволяет нам говорить о шаблонах, построенных из шаблонов проектирования! Еще один интересный момент, который следует отметить, заключается в том, что объекты, представляющие абстракцию (Shape), получают доступ к функциям реали зации уже после их инициации. Эта особенность не является характерной чертой данного шаблона, однако встречается очень часто. Теперь, когда мы разобрались, что же такое шаблон Bridge, будет полезно вновь об ратиться к работе "банды четырех", а именно — к разделу, в котором описывается реали зация шаблона. Здесь обсуждаются различные проблемы, касающиеся того, как объекты абстрактной части шаблона создают и/или используют объекты части реализации. Иногда при применении шаблона Bridge требуется организовать совместное ис пользование объектов части реализации несколькими объектами, принадлежащими различным абстракциям. •
В языке Java это не вызывает никаких проблем, поскольку, когда все объекты стороны абстракции удаляются, программа автоматической сборки мусора устанавливает, что объекты на стороне реализации также больше не нужны, и удаляет их.
•
В языке C++ необходимо какимто образом управлять объектами реализации, для чего существует много способов. Например, можно организовать хранение счетчика ссылок или даже применить шаблон Singleton. Но все же лучше, когда нет необходимости прилагать дополнительные усилия для решения этого вопроса. Это иллюстрирует еще одно важное преимущество автоматической сборки мусора.
Несмотря на то что решение, которое было получено с применением шаблона Bridge, намного превосходит первоначальное, оно все же несовершенно. Оценить качество проекта можно проанализировав, насколько эффективно он позволяет вно сить изменения в систему. При использовании шаблона Bridge внесение новых вер сий реализации существенно упрощается. Программисту достаточно определить но вый конкретный класс и реализовать его. Больше никаких изменений не потребуется. Однако ситуация оказывается существенно хуже, если в систему необходимо вклю чить новый конкретный вариант абстракции. Безусловно, может оказаться, что для отображения нового типа фигуры будет достаточно тех функций реализации, кото рые уже существуют в системе. Однако это может быть и такая фигура, отображение которой потребует использования новых функций графической программы. Например, может потребоваться организовать в системе поддержку эллипсов. Суще ствующий класс Drawing не содержит метода, позволяющего корректно отображать эллипсы. В этом случае потребуется внести изменения и на стороне реализации. Од нако нам, по крайней мере, понятно, как это происходит — модификация интерфейса 7 Составные (compound) шаблоны проектирования ранее было принято называть сложными (composite), но сейчас предпочтение отдается названию составные, чтобы избежать путаницы с шаблоном Composite. Подробности — “Riehl, D., Composite Design Patterns, In, Proceedings of the 1997 Conference on ObjectOriented Programming Systems, Languages and Applications (OOPSLA ‘97), New York: ACM Press, 1997, с. 218–228”. Кроме того, представляет интерес публикация “Composite Design Patterns (They Aren’t What You Think), C++ Report, June 1998”.
140
Часть III. Шаблоны проектирования
класса Drawing (или интерфейса Drawing в языке Java) и соответствующие модифи кации всех его производных классов. Таким образом, место внесения изменений точ но локализуется, что снижает риск появления нежелательных побочных эффектов. Подсказка. Использование шаблонов не всегда означает автоматическое получе ние самых совершенных решений. Однако благодаря тому, что шаблоны представля ют собой коллективный опыт многих проектировщиков, накопленный за долгие го ды, они часто оказываются лучше тех решений, которые можно было бы быстро при думать самостоятельно. В реальном мире новые проекты редко должны включать поддержку многовари антной реализации с самого начала. Иногда мы знаем, что появление новых реализа ций возможно, но возникают они всегда неожиданно. Один из подходов состоит в том, чтобы заранее подготовиться к многовариантной реализации, всегда разделяя систе му на стороны абстракции и реализации. В результате получится хорошо структури рованное приложение. Но этот подход не стоит рекомендовать, поскольку он ведет к нежелательному увеличению количества классов в системе. Важно создавать программный код таким способом, чтобы в случае, если многовариантная реализация все же потребуется (а она будет требоваться часто), существовал простой способ модифицировать систе му, включив в нее шаблон Bridge. Модификация кода для улучшения его структуры без добавления функций называется рефакторингом (refactoring). По определению Мар тина Фаулера (Martin Fowler), "рефакторинг — это процесс такого изменения про граммного обеспечения, при котором внешнее поведение кода не изменяется, но его 8 внутренняя структура все же улучшается" . При проектировании программного кода я всегда уделял внимание возможности его рефакторинга, следуя стратегии "одно правило, одно место". Хорошим примером применения подобного подхода является метод DrawLine(). Хотя фактически фраг менты кода размещены в различных местах, перемещаться по нему совсем просто.
Рефакторинг Рефакторинг часто применяется в объектно"ориентированном проектировании. Однако данное понятие принадлежит не только OOП. Рефакторинг — это просто модификация кода с целью улучшить его структуру без добавления новых функций.
При выведении шаблона мы взяли две измененяемые части проекта (фигуры и графические программы) и инкапсулировали каждую из них в собственный абстракт ный класс. Вариации в фигурах — в классе Shape, а вариации в графических програм мах были инкапсулированы в классе Drawing. Посмотрим со стороны на две полиморфные структуры и спросим: "Что представ ляют данные абстрактные классы?". Очевидно, что класс Shape представляет различ ные виды фигур, а класс Drawing —то, как эти фигуры будут отображены. Таким обра зом, даже если к классу Drawing предъявляются новые требования (например, появ ляется необходимость реализовать поддержку эллипсов), между классами сохраняют ся ясные отношения.
8 Fowler M., Refactoring: Improving the Design of Existing Code, Reading, MA: AddisonWesley, 2000, с. xvi.
Глава 9. Шаблон Bridge
141
Резюме Для того чтобы изучить шаблон Bridge, мы рассмотрели проблему, при которой в предметной области присутствуют две вариации — геометрических фигур и графиче ских программ. В заданной предметной области каждая вариация изменяется незави симо. Трудности появились при попытке воспользоваться решением, построенным на учете всех возможных конкретных ситуаций взаимодействия. Подобное решение, примитивным образом использующее механизм наследования, приводит к созданию громоздкого проекта, характеризующегося сильной связанностью и слабой связно стью, а следовательно, крайне неудобного в сопровождении. При обсуждении шаблона Bridge мы следовали следующим стратегиям работы с вариациями. •
Найдите то, что изменяется, и инкапсулируйте это.
•
Отдавайте предпочтение композиции, а не наследованию.
Выявление того, что изменяется, — важный этап изучения предметной области. В примере с графическими программами мы столкнулись с тем, что один набор ва риаций использует другой. Это и есть показатель того, что в данной ситуации шаблон Bridge может оказаться весьма полезным. В общем случае, чтобы определить, какой именно шаблон лучше использовать в данной ситуации, следует сопоставить его с характеристиками и поведением сущно стей в проблемной области. Зная ответы на вопросы зачем и что в отношении всех из вестных вам шаблонов, несложно будет выбрать среди них именно те, которые по зволят решить проблему. Шаблоны могут быть выбраны еще до того, как станет из вестен способ их реализации. Применение шаблона Bridge позволяет получить более устойчивые проектные решения для представления элементов абстракции и реализации, упрощая их воз можное последующее изменение. Завершая обсуждение шаблона, будет полезным еще раз напомнить о тех принци пах объектноориентированного проектирования, которые используются в шаблоне Bridge (табл. 9.2). Таблица 9.2. Принципы ООП, используемые в шаблоне Bridge Концепция
Обсуждение
Объекты отвечают только за самих себя
Существует несколько классов, представляющих фигуры, но каждый из них отображает сам себя (с помощью мето да draw()). Классы Drawing отвечают за отображение элементов этих фигур
Абстрактный класс
Абстрактные классы используются для представления кон цепций. Реально в проблемной области мы имели дело с прямоугольниками и окружностями. Концепция "фигура" — это нечто, существующее только в нашей голове, средство для связывания двух реалий между собой. Поэтому она бы ла представлена в системе абстрактным классом Shape. Ни один экземпляр объекта класса Shape никогда не будет создан в системе, поскольку он не существует в проблемной
142
Часть III. Шаблоны проектирования
области сам по себе — в ней существуют только прямо угольники и окружности. Это же относится и к графиче ским программам Инкапсуляция через абст рактный класс
В этой задаче у нас есть два примера инкапсуляции по средством создания абстрактного класса. ∑ Объектклиент, обращающийся к шаблону Bridge, все гда будет иметь дело только с объектом класса, произ водного от класса Shape. Клиент не будет знать, с ка ким именно из конкретных подтипов класса Shape он взаимодействует — для клиента это всегда будет толь ко класс Shape. Следовательно, имеет место инкапсу ляция информации. Преимущество подобного подхода состоит в том, что появление нового типа фигуры в будущем никак не затрагивает объектклиент.
Окончание табл. 9.2 Концепция
Обсуждение ∑ Класс Drawing скрывает от класса Shape наличие в системе различных графических программ. На прак тике объекты со стороны абстракции смогут узнать, какая именно реализация используется, поскольку именно они создают соответствующие экземпляры объектов. В некоторых случаях это может оказаться полезным. Однако даже в тех случаях, когда это имеет место, знание о конкретной реализации ограничено функциейконструктором объекта абстракции и, сле довательно, легко модифицируемо
Одно правило, одно место
Абстрактный класс часто включает методы, которые непо средственно используют объекты из части реализации. Конкретные классы, производные от данного абстрактно го, должны вызывать именно эти методы. Такой подход упрощает модификацию системы, когда в этом появляется необходимость, а также дает хорошую отправную точку в проектировании еще до полной реализации шаблона
Приложение. Примеры программного кода на языке C++ Листинг 9.4. Только прямоугольники void Rectangle::draw () { drawLine(_x1, _y1, _x2, _y1); drawLine(_x2, _y1, _x2, _y2); drawLine(_x2, _y2, _x1, _y2);
Глава 9. Шаблон Bridge
143
drawLine(_x1, _y2, _x1, _y1); } void V1Rectangle::drawLine (double x1, double y1, double x2, double y2) { DP1.draw_a_line(x1, y1, x2, y2); } void V2Rectangle::drawLine (double x1, double y1, double x2, double y2) { DP2.drawline(x1, x2, y1, y2); }
Листинг 9.5. Прямоугольники и окружности без использования шаблона Bridge class Shape { public: void draw () = 0; } class Rectangle : Shape { public: void draw(); protected: void drawLine( double x1, y1, x2, y2) = 0; } void Rectangle::draw () { drawLine(_x1, _y1, _x2, _y1); drawLine(_x2, _y1, _x2, _y2); drawLine(_x2, _y2, _x1, _y2); drawLine(_x1, _y2, _x1, _y1); } // Классы V1Rectangle и V2Rectangle порождены от // класса Rectangle. Заголовок файла не показан void V1Rectangle::drawLine ( double x1, y1, x2, y2) { DP1.draw_a_line(x1, y1, x2, y2); } void V2Rectangle::drawLine ( double x1,y1, x2,y2) { DP2.drawline(x1,x2,y1,y2); } class Circle : Shape { public: void draw() ; protected: void drawCircle( double x, y, z) ; } void Circle::draw () { drawCircle(); } // Классы V1Circle и V2Circle порождены от // класса Circle. Заголовок файла не показан void V1Circle::drawCircle ( DP1.draw_a_circle(x, y, r); }
144
Часть III. Шаблоны проектирования
void V2Circle::drawCircle ( DP2.drawcircle(x, y, r); }
Листинг 9.6. Фрагменты кода — реализация с применением шаблона Bridge void main (String argv[]) { Shape *s1; Shape *s2; Drawing *dp1, *dp2; dp1= new V1Drawing; s1=new Rectangle(dp, 1, 1, 2, 2); dp2= new V2Drawing; s2= new Circle(dp, 2, 2, 4); s1->draw(); s2->draw(); delete s1; delete s2; delete dp1; delete dp2; } // Замечание. Управление памятью не тестировалось. // Файлы Include не показаны. class Shape { public: draw() = 0; private: Drawing *_dp; } Shape::Shape (Drawing *dp) { _dp = dp; } void Shape::drawLine( double x1, double y1, double x2, double y2) _dp -> drawLine(x1, y1, x2, y2); } Rectangle::Rectangle (Drawing *dp, double x1, y1, x2, y2) : Shape( dp) { _x1 = x1; _x2 = x2; _y1 = y1; _y2 = y2; } void Rectangle::draw () { drawLine(_x1, _y1, _x2, _y1); drawLine(_x2, _y1, _x2, _y2); drawLine(_x2, _y2, _x1, _y2); drawLine(_x1, _y2, _x1, _y1); } class Circle { public: Circle ( Drawing *dp, double x, double y, double r); }; Circle::Circle ( Drawing *dp,
Глава 9. Шаблон Bridge
double x, double y, double r) : Shape(dp) { _x = x; _y = y; _r = r; } Circle::draw () { drawCircle(_x, _y, _r); } class Drawing { public: virtual void drawLine ( double x1, double y1, double x2, double y2) = 0; }; class V1Drawing : public Drawing { public: void drawLine ( double x1, double y1, double x2, double y2); void drawCircle( double x, double y, double r); } ; void V1Drawing::drawLine ( double x1, double y1, double x2, double y2) { DP1.draw_a_line(x1, y1, x2, y2); } void V1Drawing::drawCircle ( double x1, double y, double r) { DP1.draw_a_circle (x, y, r); } class V2Drawing : public Drawing { public: void drawLine ( double x1, double y1, double x2, double y2); void drawCircle( double x, double y, double r); }; void V2Drawing::drawLine ( double x1, double y1, double x2, double y2) { DP2.drawline(x1, x2, y1, y2); } void V2Drawing::drawCircle ( double x, double y, double r) { DP2.drawcircle(x, y, r); } // Реализация показана для классов DP1 и DP2 class DP1 { public: static void draw_a_line ( double x1, double y1,
145
146
Часть III. Шаблоны проектирования
double x2, double y2); static void draw_a_circle ( double x, double y, double r); }; class DP2 { public: static void drawline ( double x1, double x2, double y1, double y2); static void drawcircle ( double x, double y, double r); };
Глава 10
Шаблон Abstract Factory Введение Продолжим изучение шаблонов и рассмотрим шаблон Abstract Factory (абстрактная фабрика), предназначенный для создания семейств объектов. В этой главе мы выполним следующее. •
Выведем шаблон Abstract Factory на основании конкретного примера.
•
Рассмотрим ключевые особенности этого шаблона.
•
Применим шаблон Abstract Factory к решению задачи о различных версиях САПР.
Назначение шаблона проектирования Abstract Factory "Банда четырех" определяет назначение шаблона Abstract Factory как "предоставле ние интерфейса для создания семейств связанных между собой или зависимых друг от 1 друга объектов без указания их конкретных классов". Иногда встречаются ситуации, когда требуется координированно создать экземп ляры нескольких объектов. Например, для отображения интерфейса пользователя система может использовать два различных набора объектов для работы в двух раз личных операционных системах. Применение шаблона проектирования Abstract Factory гарантирует, что система всегда реализует именно тот набор объектов, кото рый отвечает данной ситуации.
Описание шаблона проектирования Abstract Factory на примере Представим себе, что перед нами поставлена задача создать компьютерную систе му для отображения на мониторе и вывода на печать геометрических фигур, описа ние которых хранится в базе данных. Значение уровня разрешения при выводе на пе чать и отображении фигур зависит от характеристик того компьютера, на котором функционирует система, — например, быстродействия процессора и объема доступ ной оперативной памяти. Иными словами, система должна самостоятельно опреде лять уровень требований, предъявляемых ею к конкретному компьютеру.
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 87.
148
Часть III. Шаблоны проектирования
Решение данной задачи сводится к тому, что система должна управлять набором используемых драйверов операционной системы. На маломощных компьютерах она должна загружать драйверы, обеспечивающие низкое разрешение, а на более мощ ных — другие драйверы, обеспечивающие более высокое разрешение (табл. 10.1). Таблица 10.1. Схема использования драйверов для компьютеров различной мощности Тип устройства
Компьютер с низкой производительностью
Компьютер с высокой производительностью
Дисплей
Объект LRDD
Объект HRDD
Драйвер дисплея с низкой раз решающей способностью (LowResolution Display Driver)
Драйвер дисплея с высокой раз решающей способностью (HighResolution Display Driver)
Объект LRPD
Объект HRPD
Драйвер принтера с низкой разрешающей способностью (LowResolution Print Driver)
Драйвер принтера с высокой разрешающей способностью (HighResolution Print Driver)
Принтер
В данном примере различные наборы драйверов взаимно исключают друг друга, но это требование не является обязательным. Иногда встречаются ситуации, когда различные семейства будут включать объекты одного и того же класса. Например, на компьютере со средними характеристиками система может использовать драйвер мо нитора с низким разрешением (LRDD) и, одновременно, драйвер печати с высоким разрешением (HRPD). Какое семейство объектов использовать в каждом конкретном случае, определяет ся особенностями проблемной области. В нашем случае концепция объединения объ ектов в семейства строится на требованиях, которые каждый из объектов предъявля ет к системе. •
Семейство драйверов с низкой разрешающей способностью — объекты LRDD и LRPD. Это драйверы, предъявляющие низкие требования к оборудованию компьютера.
•
Семейство драйверов с высокой разрешающей способностью — объекты HRDD и HRPD. Это драйверы, предъявляющие повышенные требования к оборудованию компьютера.
Первая мысль, которая приходит в голову при обдумывании реализации, — ис пользовать для управления выбором драйверов переключатель, как показано в лис тинге 10.1. Листинг 10.1. Вариант 1. Использование переключателя для выбора типа драйвера // Фрагмент кода на языке Java class ApControl { . . . void doDraw () { . . . switch (RESOLUTION) { case LOW:
Глава 10. Шаблон Abstract Factory
149
// Использование драйвера LRDD case HIGH: // Использование драйвера HRDD } } void doPrint () { . . . switch (RESOLUTION) { case LOW: // Использование драйвера LRPD case HIGH: // Использование драйвера HRPD } } }
Конечно, это вполне работоспособное решение, но оно не лишено коекаких не достатков. Так, правило определения, какой именно драйвер использовать, смешано здесь с собственно использованием этого драйвера. В результате возникают пробле мы и со связанностью, и со связностью. •
Сильная связанность. Если изменятся правила определения допустимого разре шения (например, необходимо будет ввести средний уровень разрешения), то потребуется изменить программный код в двух местах, чтобы не разрывать существующие связи.
•
Слабая связность. Методам doDraw и doPrint приходится дважды передавать соответствующий набор параметров — оба эти метода должны самостоятельно определить, какой именно драйвер следует использовать, а затем отобразить геометрическую фигуру.
Конечно, сильная связанность и слабая связность в настоящее время не представ ляют большой проблемы, однако они, безусловно, способствуют повышению затрат на сопровождение в будущем. Кроме того, в реальной ситуации мы, вероятнее всего, столкнемся с необходимостью модифицировать код в большем количестве мест, чем показано в этом простом примере. Еще одна возможная альтернатива состоит в использовании механизма наследова ния. Мы можем создать два различных класса ApControl: один из них будет исполь зовать драйверы с низкой разрешающей способностью, а другой — с высокой. Оба класса будут производными от одного и того же абстрактного класса, что позволит воспользоваться одним и тем же программным кодом. Этот вариант решения пред ставлен на рис. 10.1. Применение наследования может дать хороший эффект в этом простом случае, однако в целом второй вариант решения также имеет много недостатков, как и пер вый вариант с переключателем. •
Комбинаторный взрыв. Для каждого из существующих семейств и для всех новых семейств, которые появятся в будущем, необходимо создавать новый конкрет ный класс (т.е. новую конкретную версию класса ApControl).
150
Часть III. Шаблоны проектирования
ÐÈÑ. 10.1. Вариант 2. Реализация вариаций с помощью механизма наследования
•
Неясное назначение. Вновь образованные классы не способны помочь нам разоб раться в том, что происходит в системе. Каждый специализированный класс жестко привязан к конкретному случаю. Чтобы обеспечить простоту сопровож дения создаваемого программного кода в будущем, необходимо приложить усилия и сделать назначение каждого фрагмента как можно более понятным. Тогда не потребуется тратить много времени на попытки вспомнить, какой участок кода какие функции выполняет.
•
Композиция объектов предпочтительней наследования. Наконец, выбранный подход нарушает важнейшее правило разработки — предпочтительнее использовать композицию объектов, а не наследование.
Наличие переключателя свидетельствует о необходимости обращения к абстракции Чаще всего наличие переключателя свидетельствует о необходимости поддержки полиморфного поведения или о неверном размещении реализации обязательств. В любом случае будет полезно подыскать более общее решение — скажем, ввести новый уровень абстракции или передать соответствующие обязательства другим объектам.
Опыт показывает, что наличие в коде переключателя часто указывает на необхо димость применения абстракции. В нашем примере объекты LRDD и HRDD являются драйверами для дисплея, а объекты LRPD и HRPD — драйверами для вывода на прин тер. Соответствующие абстракции можно назвать DisplayDriver (драйвер дисплея) и PrintDriver (драйвер принтера). На рис. 10.2 представлено концептуальное ре шение для такого подхода. Решение следует считать концептуальным, поскольку клас сы LRDD и HRDD в действительности не являются производными от одного и того же абстрактного класса.
Глава 10. Шаблон Abstract Factory
151
ÐÈÑ. 10.2. Типы драйверов и соответствующие абстракции
Замечание. На этом этапе мы можем не обращать внимания на то, что классы на схеме являются производными от различных классов, поскольку уже знаем, как мож но использовать шаблон Adapter для адаптации драйверов, чтобы создать иллюзию их принадлежности к общему абстрактному классу. В результате подобного определения объектов класс ApControl сможет использо вать классы DisplayDriver и PrintDriver без применения переключателя. Теперь понять работу класса ApControl будет намного проще, поскольку отпала необходи мость анализировать, какой именно тип драйвера он должен использовать. Другими словами, класс ApControl может обращаться к классу DisplayDriver или классу PrintDriver, не заботясь о разрешении, обеспечиваемом драйверами. Соответствующая схема представлена на рис. 10.3, а программный код на языке Java приведен в листинге 10.2. Листинг 10.2. Фрагмент программного кода. Решение задачи с помощью методов полиморфизма // Фрагмент кода на языке Java class ApControl { . . . void doDraw () { . . . myDisplayDriver.draw(); } void doPrint () { . . . myPrintDriver.print(); } }
Остается нерешенным только один вопрос — как создать соответствующие объек ты в каждом конкретном случае? Можно возложить эту задачу на класс ApControl, однако подобное решение вызо вет проблемы с поддержкой системы в будущем. Если потребуется организовать рабо ту с новым набором объектов, класс ApControl неизбежно придется модифициро вать. Однако если для создания экземпляров нужных нам объектов использовать не который специализированный "объектфабрику", можно решить проблему, связанную с появлением нового семейства объектов.
152
Часть III. Шаблоны проектирования
ÐÈÑ. 10.3. Использование драйверов классом ApControl в идеальной ситуации
В нашем примере объектфабрика будет использоваться для управления созданием требуемого семейства объектов драйверов. Объект ApControl будет обращаться к дру гому объекту — объектуфабрике, чтобы создать объекты драйверов дисплея и печати с требуемым разрешением, определяемым в зависимости от характеристик используемо го компьютера. Схема подобного взаимодействия представлена на рис. 10.4. Теперь с точки зрения объекта ApControl все выглядит предельно просто. Функ ции определения, какой именно тип драйвера и как требуется создать, возложена на объект ResFactory. Хотя перед нами попрежнему стоит задача написать программ ный код, реализующий все необходимые действия по созданию требуемых объектов, мы выполнили декомпозицию проблемы с распределением обязательств между от дельными участниками. Класс ApControl должен знать, как работать с соответст вующими случаю объектами, а класс ResFactory — решать, какие именно объекты являются соответствующими в каждом конкретном случае. Теперь можно использо вать либо различные объектыфабрики, либо одинединственный объект, содержа щий переключатель. В любом случае данный подход будет лучше прежних. Такое решение вносит в систему необходимую законченность. Назначение объекта класса ResFactory состоит лишь в том, чтобы создавать соответствующие объекты драйверов, а назначение объекта класса ApControl — в том, чтобы их использовать. Существуют различные способы избежать использования переключателя в теле класса ResFactory. Их применение позволит вносить возможные изменения, не за трагивая уже существующие объектыфабрики. Например, можно инкапсулировать возможные вариации за счет определения абстрактного класса, представляющего общую концепцию объектафабрики. В нашем примере класс ResFactory реализует два различных типа поведения (или метода). •
Создание объекта требуемого драйвера дисплея.
•
Создание объекта требуемого драйвера печати.
Глава 10. Шаблон Abstract Factory
ÐÈÑ. 10.4. Объект ApControl создает объекты драйверов с помощью объектафабрики
ResFactory
153
154
Часть III. Шаблоны проектирования
В системе абстрактный класс ResFactory может быть представлен объектом од ного из двух конкретных классов, каждый из которых является производным от этого абстрактного класса и имеет требуемые открытые методы — как показано на рис. 10.5.
ÐÈÑ. 10.5. Абстрактный класс ResFactory инкапсулирует существующие вариации
Реализация стратегии анализа при проектировании Ниже описаны три ключевые стратегии проектирования и способы их применения при использовании шаблона Abstract Factory. Стратегия анализа Найдите то, что и инкапсулируйте это
Реализация при проектировании изменяется,
В нашем примере изменчивость заключается в выборе типа объекта драйвера, который требуется использовать. Она была инкапсулирована в абстрактный класс ResFactory
Предпочтительное использование композиции, а не наследования
Вместо создания двух различных типов объектов ApControl, изменчивость вынесена в единственный абстрактный класс ResFactory, используемый классом ApControl
Проектирование интерфейсов, а не реализации
Класс ApControl знает, как потребовать от класса ResFactory создать соответствующие экземпляры объектов драйверов, но не знает как именно класс ResFactory реализует это требование
Реализация шаблона проектирования Abstract Factory В листинге 10.3 показано, как объекты шаблона Abstract Factory могут быть реали зованы в нашем примере. Листинг 10.3. Пример реализации объектов класса ResFactory на языке Java class LowResFact extends ResFactory { DisplayDriver public getDispDrvr() { return new lrdd();
Глава 10. Шаблон Abstract Factory
155
} PrintDriver public getPrtDrvr() { return new lrpd(); } } class HighResFact extends ResFactory { DisplayDriver public getDispDrvr() { return new hrdd(); } PrintDriver public getPrtDrvr() { return new hrpd(); } }
Для завершения работы над найденным решением необходимо организовать диа лог объекта класса ApControl с соответствующим объектомфабрикой (LowResFact или HighResFact), как показано на рис. 10.6. Обратите внимание на то, что класс ResFactory является абстрактным, и именно это сокрытие реализации классом ResFactory составляет суть работы данного шаблона. Отсюда и его название — Abstract Factory (абстрактная фабрика). Объекту класса ApControl предоставляется ссылка на объект либо класса LowResFact, либо класса HighResFact. Он запрашивает создание этого объекта при необходимости получить доступ к соответствующему драйверу. Объект фабрика создает экземпляр объекта драйвера, самостоятельно определяя его тип (для низкого или высокого разрешения). Причем в этом случае объекту ApControl даже нет необходимости знать, какой именно драйвер (с низкой раз решающей способностью или высокой) будет создан, так как со всеми он работает совершенно одинаково. Мы обошли вниманием один важный вопрос: классы LRDD и HRDD могут быть производными от разных абстрактных классов (это же замечание может быть справедливо и для классов LRPD и HRPD). Однако тем, кто знает шаблон Adapter, решить этот вопрос не составит труда. Можно воспользоваться общей схемой, представленной на рис. 10.6, просто добавив к ней элементы адаптации объектов драйверов (рис. 10.7). Данная реализация проекта по существу неотличима от прежней. Единственное различие состоит в том, что теперь объектыфабрики создают экземпляры объектов тех классов, которые были добавлены нами для адаптации объектов предыдущего ва рианта схемы. Это очень важный метод моделирования. Подобное совместное ис пользование шаблонов Adapter и Abstract Factory позволяет обращаться с концепту ально сходными объектами так, как будто они близкородственны друг другу, даже ес ли на самом деле это не так. Такой подход позволяет использовать шаблон Abstract Factory в самых разных ситуациях.
156
Часть III. Шаблоны проектирования
ÐÈÑ. 10.6. Промежуточное решение с использованием шаблона Abstract Factory
ÐÈÑ. 10.7. Решение с применением шаблонов Abstract Factory и Adapter
Глава 10. Шаблон Abstract Factory
157
Вот характерные особенности полученного шаблона. •
Клиентский объект знает только, к какому объекту следует обратиться для со здания требуемых ему объектов и как их использовать.
•
В классе абстрактной фабрики уточняется, экземпляры каких объектов должны быть созданы, для чего определяются отдельные методы для различных типов объектов. Как правило, объект абстрактной фабрики включает отдельный метод для каждого существующего типа объектов.
•
Конкретные объектыфабрики определяют, какие именно объекты должны создаваться.
Дополнительные замечания о шаблоне Abstract Factory Выбрать, какой именно объектфабрика должен использоваться, в действительно сти то же самое, что и определить, какое следует использовать семейство объектов. Например, в обсуждавшейся выше задаче о драйверах мы имели дело с двумя семейст вами драйверов — одно для систем с низкой разрешающей способностью, а другое для систем с высокой разрешающей способностью. Как узнать, какой набор драйверов следует выбрать? В этом случае, вероятно, потребуется обратиться к файлу конфигу рации системы, содержащему описание характеристик компьютера. В программу придется добавить несколько строк программного кода, где на основе полученной информации будет выбираться объектфабрика с требуемым набором свойств. Шаблон Abstract Factory также может быть использован совместно с подсистемами различных приложений. В этом случае объектфабрика будет пропускным пунктом для подсистем, сообщая им, какие именно объекты из их состава должны использо ваться. Как правило, главная система знает, какое семейство объектов должно ис пользоваться каждой подсистемой, поэтому до обращения к некоторой подсистеме можно будет создать требуемый экземпляр объектафабрики.
Основные характеристики шаблона Abstract Factory Назначение
Требуется организовать работу с семействами или наборами объектов для определенных объектов"клиентов (или отдельных случаев)
Задача
Необходимо создать экземпляры объектов, принадлежащих заданному семейству
Способ решения
Координация процесса создания семейств объектов. Предусматривается выделение реализации правила создания тех или иных семейств объектов из объекта"клиента, который в дальнейшем будет использовать созданные объекты, в отдельный объект
Участники
Класс AbstractFactory определяет интерфейс для создания каждого из объектов заданного семейства. Как правило, каждое семейство создается с помощью собственного конкретного уникального класса ConcreteFactory
Следствия
Шаблон отделяет правила, какие объекты нужно использовать, от правил, как эти объекты следует использовать
Реализация
Определяется абстрактный класс, описывающий, какие объекты должны быть созданы. Затем реализуется по одному конкретному классу для каждого семейства объектов (рис. 10.8). Для решения этой задачи также могут использоваться таблицы базы данных или файлы
158
Часть III. Шаблоны проектирования
ÐÈÑ. 10.8. Стандартное упрощенное представление шаблона Abstract Factory
На рис. 10.8 показано, что объект Client использует объекты, производные от двух различных серверных классов (AbstractProductA и AbstractProductB). Такой подход упрощает структуру системы, скрывая реализацию, чем упрощает ее со провождение. •
Объектклиент не знает, с какой конкретной реализацией серверного объекта он имеет дело, так как ответственность за создание серверного объекта несет объектфабрика.
•
Объектклиент не знает даже того, к какому конкретному объектуфабрике он обращается, поскольку он взаимодействует с абстрактным классом AbstractFactory, который может быть представлен объектом конкретных классов ConcreteFactory1 или ConcreteFactory2, но каким именно, объектклиент знать не может.
От объекта класса Client полностью скрыта (инкапсулирована) информация о том, какой из серверных объектов используется. Это позволяет в будущем легко вно сить изменения в процедуру совершения выбора, так как объектклиент остается не затронутым. Шаблон Abstract Factory ярко демонстрирует новый вид декомпозиции — декомпо зицию ответственности. Подобный подход позволяет разделить задачу на две функ циональные стороны.
Глава 10. Шаблон Abstract Factory
159
•
Первая сторона, использующая конкретные объекты (класс ApControl).
•
Вторая сторона, принимающая решение о том, какие именно объекты следует использовать (класс AbstractFactory).
Применение шаблона Abstract Factory целесообразно в тех случаях, когда про блемная область включает несколько семейств объектов, причем каждое из семейств используется при различных обстоятельствах. Семейства могут быть определены на основе одного или нескольких критериев. Приведем несколько примеров. •
Различные операционные системы (при создании многоплатформенных при ложений).
•
Различные требования к производительности.
•
Различные версии приложений.
•
Различные категории пользователей приложения.
Как только семейства и их члены будут идентифицированы, следует принять ре шение о методе реализации каждого конкретного случая (т.е. каждого семейства). В нашем примере для этой цели определен абстрактный класс, на который возложена ответственность за выбор конкретного типа семейства классов, для которых и будут созданы экземпляры объектов. Затем для каждого семейства от этого абстрактного класса был определен конкретный производный класс, реализующий создание экзем пляров объектов для классов — членов семейства. Иногда встречаются ситуации, когда семейства объектов существуют, но нет необ ходимости управлять созданием их экземпляров с помощью классов, порожденных специально для каждого семейства. Возможно, более подходящим окажется одно из следующих более динамичных решений. •
Можно использовать файл конфигурации, в котором будет указано, какие объекты следует использовать в каждом случае. Для создания экземпляров требуемых объектов в программе организуется переключатель, управляемый информацией из файла конфигурации.
•
Для каждого семейства объектов создается отдельная запись в базе данных, содержащая сведения об используемых объектах. При этом каждый элемент (поле) записи базы данных указывает, какой конкретный класс должен использоваться при вызове соответствующего метода абстрактного классафабрики.
При работе с языком Java концепцию использования файла конфигурации можно дополнительно расширить. Имена полей записи файла могут представлять собой имя требуемого класса. В этом случае не требуется даже иметь полное имя класса, доста точно только добавить суффикс или префикс к имени, хранящемуся в файле конфи гурации, а затем, используя класс Class языка Java, корректно создать экземпляр объ 2 екта с данным именем. В реально существующих проектах члены различных семейств не всегда являются производными от общего родительского класса. Например, в предыдущем примере с 2 Подробное описание класса Class языка Java можно найти в Eckel B. Thinking in Java, Upper Saddle River, N.J.: Prentice Hall, 2000.
160
Часть III. Шаблоны проектирования
драйверами, возможно, что классы драйверов LRDD и HRDD будут производными от разных родительских классов. В таких случаях следует так адаптировать эти классы, чтобы шаблон Abstract Factory смог работать с ними.
Применение шаблона Abstract Factory к проблеме САПР В задаче с различными САПР система должна работать с различными наборами элементов, определяемыми в зависимости от используемой версии САПР. При работе с САПР версии V1 все элементы должны быть реализованы для версии V1, а при рабо те с САПР версии V2 все элементы должны быть реализованы для версии V2. Исходя из этого требования, несложно выделить два семейства классов, для управ ления которыми следует применить шаблон Abstract Factory. Первое семейство — это классы элементов версии V1, а второе — классы элементов версии V2.
Резюме Шаблон Abstract Factory применяется, когда необходимо координировать созда ние семейств объектов. Он позволяет отделить реализацию правил создания экземп ляров новых объектов от объектаклиента, который будет эти объекты использовать. •
Прежде всего, идентифицируйте правила создания экземпляров объектов и определите абстрактный класс с интерфейсом, включающим отдельный метод для каждого из классов, экземпляры которых должны быть созданы.
•
Затем для каждого семейства создайте конкретные классы, производные от данного абстрактного класса.
•
Реализуйте в объектеклиенте обращение к абстрактному объектуфабрике с целью создания требуемых серверных объектов.
Приложение. Примеры программного кода на языке C++ Листинг 10.4. Фрагмент кода. Переключатель управления выбором драйвера // Фрагмент кода на языке C++ // Класс ApControl . . . void ApControl::doDraw () { . . . switch (RESOLUTION) { case LOW: // Использование LRDD case HIGH: // Использование HRDD } } void ApControl::doPrint () { . . . switch (RESOLUTION) { case LOW: // Использование LRPD
Глава 10. Шаблон Abstract Factory
case HIGH: // Использование HRPD } }
Листинг 10.5. Фрагмент кода. Использование полиморфизма для решения проблемы // Фрагмент кода на языке C++ // Класс ApControl . . . void ApControl::doDraw () { . . . myDisplayDriver->draw(); } void ApControl::doPrint () { . . . myPrintDriver->print();
}
Листинг 10.6. Фрагмент кода. Реализация класса ResFactory // Фрагмент кода на языке C++ class LowResFact : public ResFactory; DisplayDriver * LowResFact::getDispDrvr() { return new lrdd; } PrintDriver * LowResFact::getPrtDrvr() { return new lrpd; } class HighResFact : public ResFactory; DisplayDriver * HighResFact::getDispDrvr() { return new hrdd; } PrintDriver * HighResFact::getPrtDrvr() { return new hrpd; }
161
ЧАСТЬ IV
Практическое применение шаблонов проектирования
Введение В этой части рассказывается о новом подходе к объектноориентированному про ектированию программных систем, основанному на применении шаблонов. Дейст венность этого подхода неоднократно проверена автором на практике. Предлагае мый подход мы применим к решению проблемы САПР, предложенной в главе 3, Проблема, требующая создания гибкого кода. Предлагаемый подход подразумевает, прежде всего, попытку понять тот контекст, в котором были выделены объекты системы. Глава
Предмет обсуждения
11
∑ Обсуждение идей Кристофера Александера и методов их использования опытными разработчиками при проектировании
12
∑ Применение предлагаемого подхода к решению задачи САПР, предло женной в главе 3. ∑ Сравнение нового варианта решения с тем решением, которое было предложено в главе 4
13
∑ Подведение итогов обсуждения объектноориентированной идеологии и шаблонов проектирования. ∑ Формулирование концепции шаблонноориентированного проектирования
Глава 11. Как проектируют эксперты
165
Глава 11
Как проектируют эксперты
Введение С чего следует начинать разработку проекта? Определить все возможные детали, а затем искать способ объединить их в единое целое? Или лучше предварительно со ставить общее представление о проекте и лишь затем приступить к его детализации? А может существует и другой путь? Подход, предложенный Кристофером Александером, состоит в том, чтобы снача ла сосредоточить внимание на отношениях концептуального уровня, а затем продви гаться от общего к частному. Прежде чем принять какоелибо проектное решение, он предлагает глубоко вникнуть в контекст той проблемы, которую это решение должно преодолеть, используя шаблоны проектирования для выявления существующих в этой среде отношений. Однако Александер предлагает не просто коллекцию шабло нов, а самостоятельную методологию проектирования. Предметной областью, о ко торой он пишет, является архитектура. Ее задача состоит в проектировании зданий, в которых люди живут и работают. Но предложенные Александером принципы вполне применимы и к проектированию программного обеспечения. В этой главе мы выполним следующее. •
Обсудим подход Александера к проектированию.
•
Проанализируем, как применить этот подход к разработке программного обеспечения.
Проектирование посредством добавления различий После ознакомления с несколькими шаблонами проектирования пришло время выяснить, как они могут работать совместно. Александер полагает, что недостаточно просто описать отдельные шаблоны. Он использует их для построения новой пара дигмы проектирования. Книга Александера The Timeless Way of Building посвящена одновременно и описа нию шаблонов, и обсуждению методов их совместного использования. Это превос ходная книга, одна из моих самых любимых как с личной, так и с профессиональной точки зрения. Она помогла мне осознать то, что происходит в моей жизни, лучше по нять тот мир, в котором я живу, а также научиться создавать более качественное про граммное обеспечение. Как это стало возможным? Как может книга, посвященная проектированию зданий и городов, оказать столь глубокое влияние на разработку программного обеспечения?
166
Часть VI. Практическое применение шаблонов проектирования
Причина, как я полагаю, состоит в том, что в ней Александер представил новую пара дигму, которая будет полезна любому проектировщику. Именно эта новая парадигма проектирования и является наиболее важным и интересным аспектом его книги. К сожалению, я не могу похвастать тем, что принял предложенную Александером идеологию после первого же прочтения его книги. Первоначальной моей реакцией была такая мысль: "Это очень интересно. Возможно, все это даже имеет практический смысл". А затем я вновь возвратился к традиционным методам проектирования, кото рые использовал на протяжении многих лет. Но иногда старые пословицы все же находят себе подтверждение в жизни. Не да ром говорят: "Удача приходит к тем, кто ее ждет" или "На ловца и зверь бежит". Вско ре мне представился подходящий случай, благодаря которому все стало на свои места. Спустя несколько недель после прочтения книги Александера жизнь заставила ме ня вновь вернуться к ней. Я был занят в проекте, который не поддавался традицион ным подходам — они попросту не работали. Конечно, я мог бы предложить коекакие решения, но все они были недостаточно хороши. Все мои старые и испытанные ме тоды проектирования терпели неудачу, и я был очень расстроен. К счастью, мне хва тило мудрости попробовать новый путь, предложенный Александером, и очень скоро можно было лишь восхищаться полученными результатами. В следующей главе мы подробно обсудим все, что мной тогда было сделано. Но сначала посмотрим, какой же подход предлагает Александер. Проектирование часто представляют себе как процесс синтеза, процесс соединения элемен тов в единое целое, процесс комбинирования. Согласно этому представлению целое соз дается соединением отдельных частей, т.е. элементы являются первичными, а об 1 щая форма — вторичной. Это вполне естественно — проектировать, переходя от частного к общему, начи ная с конкретных вещей, понятных и знакомых. Когда я впервые прочитал это, то подумал: "Все это вполне соответствует моему взгляду на вещи. Сначала выяснить, что мне может пригодиться, а затем собрать все это в одно целое." Иначе говоря, при проектировании сначала идентифицируются требуемые классы, а затем уже устанавливается порядок их взаимодействия. После то го как все требуемые элементы будут собраны в одно целое, можно вернуться на шаг назад и оценить, что же у нас получилось. Но и в этом случае при переключении вни мания от частного (локального) к общему (глобальному) общая картина остается в нашем представлении состоящей из отдельных частей. В объектноориентированной разработке элементами являются объекты и классы. Именно они идентифицируются на первом этапе, после чего определяется их пове дение и интерфейсы. Однако, начав проектирование с частностей, мы, как правило, так и остаемся сосредоточенными на них до конца. Вспомним исходный вариант решения проблемы с различными версиями САПР, предложенный в главе 4, Стандартное объектноориентированное решение. Мы начали с рассуждений о различных классах, которые могут нам потребоваться: классы для представления пазов, отверстий, просечек и т.д. Поскольку необходимо было связать их с системами версий V1 и V2, предполагалось наличие двух наборов классов, спе
1 Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, New York: Oxford University Press, 1979, с. 368.
Глава 11. Как проектируют эксперты
167
циализированных для каждой из систем. И только после определения всех требуемых классов внимание было перенесено на проблему их взаимодействия. Тем не менее, простым соединением заранее определенных частей невозможно сформировать 2 чтолибо, имеющее естественный, законченный облик. Тезис Александера состоит в том, что построение целого из отдельных частей — это далеко не лучший метод проектирования. Несмотря на то что речь в книге Александера идет об архитектуре, многие при знанные авторитеты в области разработки программного обеспечения согласятся с тем, что этот тезис верен и в их области. Я попытался понять, в чем состоит суть но вого подхода к разработке. В результате в моей голове сложилась фраза, как будто бы произнесенная Александером: "Хорошее программное обеспечение не может быть создано простым соединением заранее определенных частей" (т.е. без предваритель ной оценки, насколько хорошо эти части будут подходить друг другу). Когда элементы представляют собой отдельные модули, созданные прежде общего целого, то по определению они всегда будут идентичными, и нельзя ожидать, что каждая часть будет уникальной, отвечающей своему положению в общем целом. И что еще более важно, с по мощью любой комбинации модульных элементов просто невозможно получить все то множество шаблонов, которые должны быть одновременно представлены в 3 любом месте, предназначенном для пребывания человека. Размышления Александера, связанные с модульностью, поначалу были для меня совершенно непонятны. Но в конце концов мне стало ясно, что если начать с созда ния модулей еще до уяснения того, как будет выглядеть общая картина, то модули в дальнейшем всегда будут одними и теми же, поскольку не существует никаких причин для их изменения в дальнейшем. Похоже, именно в этом и состоит основная цель концепции многократного ис пользования. Разве она не предполагает постоянного использования одних и тех же модулей? Именно так. Однако нам также требуется максимальная гибкость и устойчи вость системы. Само по себе простое создание модулей не гарантирует этого. Ознакомившись с использованием шаблонов проектирования по методологии Алек сандера, я смог создавать многократно используемые и одновременно гибкие классы с гораздо большим успехом, чем прежде. Как проектировщик, я стал намного лучше. Единственно возможный способ создать жизненное пространство, полностью отвечающее нуждам его обитателей, состоит в максимальном приспособлении каждой части общего це 4 лого к той позиции, которую она в нем занимает. В нашем случае выражение полностью отвечающее нуждам следует понимать как устойчивое и гибкое программное обеспечение. Выше упоминались слова Александера о том, что части должны быть уникальны — только в этом случае удастся реализовать все преимущества, определяемые их конкрет ным местоположением. Рассмотрим это утверждение подробнее. Каждый элемент соз дается посредством копирования эталона с последующим приспособлением новой ко
2
Там же, с. 368. Там же, с. 368, 369. 4 Там же, с. 369. 3
168
Часть VI. Практическое применение шаблонов проектирования
пии к существующему окружению, что и придает общему целому уникальный, присущий только ему характер. Рассмотрим несколько примеров из области архитектуры. •
Швейцарская деревня. Перед глазами встает деревня, состоящая из расположе нных близко друг к другу уютных коттеджей, очень похожих один на другой, но, тем не менее, каждый из них имеет чтото индивидуальное. Различия между домами вовсе не произвольны — каждый коттедж отражает финансовые воз можности своего создателя и владельца и непременно удовлетворяет требо ванию гармонично вписаться в окружающую среду. Достигнутый эффект очень хорош — деревня в целом производит впечатление уюта и комфортабельности.
•
Американский пригород. Все коттеджи выглядят как пряничные домики. Вни мание к естественному окружению построек уделяется крайне редко. Установ ившиеся понятия и стандарты всячески поощряют подобное однообразие. В результате возникает эффект полного обезличивания зданий, и общая картина не способна вызвать какихлибо приятных эмоций.
Безусловно, в данный момент применение обсуждаемого подхода к проектирова нию программного обеспечения может показаться слишком уж концептуальным. Однако сейчас достаточно понять, что для создания устойчивых и гибких программ ных систем необходимо разрабатывать их элементы (классы или объекты) с учетом того окружения (контекста) в котором они будут функционировать. Короче говоря, каждая часть обретает свою специфическую форму за счет ее нахождения в конкретном контексте более общего целого. Это есть процесс дифференциации. Проектирование рассматривается как серия этапов усложнения. Простая структура превращается в нечто более общее, контроли руемая и направляемая этим общим, а не за счет простого объединения мелких частей друг с другом. В процессе дифференциации целое порождает свои части, при этом формирование целого и его частей происходит одновременно. В целом процесс 5 дифференциации напоминает развитие эмбриона. Усложнение — что означает здесь это слово? Ведь наша цель состоит в том, чтобы сделать процесс проектирования проще, а не сложнее! Смысл его в том, что согласно подходу Александера обдумывание проекта должно начинаться с описания проблемы в простейших терминах и понятиях с последующим добавлением дополнительных особенностей (различий), в результате чего проект бу дет становиться все более и более сложным благодаря постоянному накоплению ана лизируемой информации. Это совершенно естественный процесс. Мы постоянно применяем его на практи ке. Например, представим, что требуется договориться об аудитории для проведения лекции, причем ожидается, что слушателей будет около 40 человек. Описание соот ветствующих требований, скорее всего, будет выглядеть примерно так: "Мне понадо бится комната размером 10 на 10 метров" (начинаем с простого). Далее: "Потребуются также стулья — 5 рядов по 8 стульев, установленные в виде полукруга" (добавление информации делает описание комнаты более сложным). Наконец: "Необходима так
5
Там же, с. 370.
Глава 11. Как проектируют эксперты
169
же кафедра для лектора, расположенная перед слушателями" (еще более сложное описание). Развертывание проекта в сознании его создателя, в терминах используемого им языка — это тот же самый процесс. Каждый шаблон — это оператор, дифференцирующий пространство: т.е. он созда ет различия там, где прежде их не было. В языке операторы упорядочиваются в последовательности: в соответствии с тем, как они выполняются, один за другим. В результате рождается законченная форма, общая в том смысле, что она имеет структуру, одинаковую с другими сравнимыми элементами, и специфическая в том смысле, что она уникальна в соответствии с ее собственными обстоятельствами. Язык представляет собой последовательность таких операторов, в которой каж дый оператор вносит дальнейшую дифференциацию в образ, являющийся порож 6 дением предыдущих дифференциаций. Итак, Александер утверждает, что проектирование должно начинаться с простей шей формулировки проблемы с последующей постепенной ее детализацией (усложнением) посредством добавления в эту формулировку новой информации. Вносимая информация принимает форму шаблона, поскольку, считает Александер, шаблоны определяют отношения между сущностями в проблемной области. Для примера еще раз обратимся к шаблону "Внутренний двор", обсуждавшемуся в главе 5, Первое знакомство с шаблонами проектирования. Шаблон должен описывать сущ ности, присутствующие в среде внутреннего двора, и их взаимоотношения между со бой. Такими сущностями являются следующие. •
Открытое пространство внутреннего двора.
•
Пересекающиеся пути между окружающими помещениями.
•
Внешний вид из внутреннего двора.
•
И даже люди, которые будут пользоваться этим внутренним двором.
Осмысление проблемы в терминах того, как эти объекты должны взаимодейство вать между собой, дает достаточно информации для проектирования внутреннего двора. Затем предварительный проект уточняется за счет применения других шабло нов, которые могут присутствовать в контексте шаблона "Внутренний двор", — напри мер, крыльца или веранды, выходящих во внутренний двор. Что же делает данный аналитический метод столь мощным, что он не нуждается в подтверждении моим личным опытом, интуицией и творческим потенциалом? Алек сандер утверждает, что существование подобных шаблонов есть объективная реаль ность, и они существуют независимо от отдельных личностей. Организация про странства отвечает запросам его обитателей тогда, когда она удовлетворяет их есте ственным потребностям, а не просто потому, что автором проекта является гений. Если качество проекта зависит от его соответствия естественным процессам, не должно вызывать удивления, что качественные решения сходных проблем будут вы глядеть очень похоже.
6
Там же, с. 372, 373.
170
Часть VI. Практическое применение шаблонов проектирования
Основываясь на этих рассуждениях, Александер сформулировал следующие пра вила хорошего проектирования. •
Строго по одному. Шаблоны должны применяться только по одному, последова тельно друг за другом.
•
Сначала следует формировать контекст. Сначала следует применять те шаблоны, которые создают контекст для других шаблонов.
Шаблоны определяют отношения Шаблоны, описываемые Александером, определяют отношения между сущностями в проблемной области. Для нас важны не сами шаблоны, а именно эти отношения, поскольку шаблоны просто предлагают способ их анализа.
Подход, предложенный Александером, вполне можно применить и к проектиро ванию программного обеспечения. Конечно, не буквально, а концептуально. Что мог бы сказать Александер, обращаясь к разработчикам программного обеспечения? Воз можный набор рекомендаций я поместил в табл. 11.1. Таблица 11.1. Применение методологии Александера к разработке программного обеспечения Последовательность разработки
Пояснения
Идентифицируйте шаблоны
Идентифицируйте шаблоны, присутствующие в контексте проблемы. Осмыслите проблему в терминах присутствую щих в ней шаблонов. Помните, что назначение шаблонов со стоит в определении отношений между сущностями в про блемной области
Начните с шаблонов, определяющих контекст
Идентифицируйте те шаблоны, которые создают контекст для других шаблонов. Именно с них следует начать разра ботку системы
Двигайтесь в направле нии углубления в контекст
Рассмотрите остальные шаблоны и отыщите любые другие шаблоны, которые ранее остались незамеченными. Из полу ченного набора опять выделите те шаблоны, которые опре деляют контекст для оставшихся. Повторяйте эту процедуру до исчерпания всего набора шаблонов
Оптимизируйте проект
На этом этапе тщательно проанализируйте общий контекст, полученный в результате применения шаблонов
Реализуйте полученную схему
Реализация предусматривает воплощение всех деталей, тре буемых каждым из использованных шаблонов
Глава 11. Как проектируют эксперты
171
Личные впечатления автора от использования идей Александера при проектировании программного обеспечения При первом использовании подхода Александера я воспринял его слова слишком буквально. Его концепции, сформулированные в отношении архитектуры, не всегда удается прямо применить к проектированию программного обеспечения (или другим видам проектирования). В некотором смысле при первых опытах применения шаблонов мне просто повезло, поскольку те проблемы, которые я решал, включали шаблоны проектирования, строго упорядоченные в отношении создания контекста. Однако это везение сработало и против меня, так как по наивности я решил, что так будет всегда (жизнь показала обратное). Известно, что многие ведущие специалисты в области программного обеспечения поддерживали идею разработки "языка шаблонов" и пытались найти формальный способ применения идей Александера к разработке программного обеспечения. В моем понимании это был бы такой инструмент, который позволил нам прямо применять методы Александера к разработке программного обеспечения (лично я больше не верю, что это возможно). Поскольку Александер указал, что в архитектуре шаблоны обладают заранее определенным порядком образования контекста, я полагал, что и в программном обеспечении шаблоны также имеют некоторый предопределенный порядок. Иначе говоря, один тип шаблона всегда будет создавать контекст для шаблона другого типа. Я начал пропагандировать подход Александера именно так, как я понимал его тогда, и даже учить этому других. Но по прошествии нескольких месяцев и после применения этого подхода в нескольких проектах я столкнулся с серьезными проблемами. Обнаружились ситуации, в которых заранее установленный порядок контекстов не срабатывал. Как человеку, имеющему математическое образование, мне было достаточно единственного исключения из правил для опровержения всей выдвинутой мной теории. Сложившаяся ситуация заставила меня заново критически пересмотреть весь подход к идеологии шаблонов проектирования. Ранее я всегда придерживался этого правила, но на этот раз просто забыл о нем в радостном возбуждении. Начиная с самого первого этапа, я вновь проанализировал те принципы, на которых строится метод Александера. Несмотря на то, что они по"разному проявляются в архитектуре и в проектировании программ, эти принципы все же полностью применимы к разработке программного обеспечения. Они позволяют повысить качество создаваемых проектов, ускорить их разработку и выполнить более полный анализ. Подтверждение этому я вижу каждый раз при необходимости внесения очередных изменений в созданное мной программное обеспечение.
Резюме Проектирование обычно понимается как процесс синтеза — процесс объединения отдельных частей в единое целое. При создании программного обеспечения обыч ный подход состоит в том, чтобы сначала выделить все необходимые объекты, классы и компоненты, и лишь затем подумать о том, как они будут взаимодействовать. В книге The Timeless Way of Building Кристофер Александер предложил лучший под ход, основанный на применении шаблонов проектирования. Этот подход предлагает следующее. 1. Начать с концептуального описания всей проблемы в целом, чтобы понять, ка кие требования в конечном счете должны быть удовлетворены. 2. Идентифицировать шаблоны, которые присутствуют в концептуальном описа нии проблемы. 3. Начать работу с тех шаблонов, которые создают контекст для остальных. 4. Применить эти шаблоны. 5. Повторить эту процедуру с оставшимися шаблонами, а также с любыми новыми шаблонами, которые, возможно, будут выявлены в процессе проектирования.
172
Часть VI. Практическое применение шаблонов проектирования
6. Наконец, оптимизировать проект и его реализацию в контексте, созданном за счет последовательного применения всех выявленных шаблонов проектирования. Разработчик программного обеспечения не всегда имеет возможность применить предложенный Александером подход непосредственно. Однако проектирование мето дом добавления концепций в пределах контекста, образованного за счет представления предыдущих концепций, несомненно, доступно каждому. Помните об этом при изуче нии новых шаблонов в последующих главах книги. Многие шаблоны позволяют созда вать надежное программное обеспечение, потому что они определяют контексты, в пределах которых классы, реализующие некоторое решение, могут успешно работать.
Глава 12
Решение задачи САПР с помощью шаблонов проектирования
Введение В этой главе показано, как можно применить шаблоны проектирования для реше ния проблемы поддержки в приложении различных версий САПР, обсуждавшейся в главе 3, Проблема, требующая создания гибкого кода. Здесь мы выполним следующее. •
Вспомним первый вариант решения проблемы САПР.
•
Обсудим начальный этап проектирования второго варианта решения этой проблемы. Подробности реализации будут оставлены на усмотрение читателя.
•
Сравним новый и предыдущий варианты решения проблемы САПР.
Повторный краткий обзор проблемы САПР В главе 3, Проблема, требующая создания гибкого кода, была описана проблема, свя занная с поддержкой в приложении двух версий САПР одновременно. По сути, это была первая практическая задача, для решения которой автор воспользовался кон цепцией шаблонов проектирования. Проблемная область задачи представляет собой компьютерную систему обеспече ния работы большой проектной организации, включающей, в частности, поддержку функционирования САПР. Основное требование — создать компьютерную программу, которая будет извле кать из базы данных САПР сведения об отдельных элементах деталей и передавать ус тановленной в организации экспертной системе данные, необходимые ей для эффек тивного проектирования технологических процессов их изготовления. Предполага лось, что эта программа будет изолировать экспертную систему от САПР. Проблема осложнялась тем, что используемая САПР была подвержена частым изменениям. Весьма вероятно, что одновременно будет использоваться несколько различных вер сий САПР, с каждой из которых экспертная система должна будет работать без каких либо осложнений. После предварительных опросов мной были сформулированы общие требования к создаваемой системе, приведенные в табл. 12.1 и схематически представленные на рис. 12.1.
174
Часть VI. Практическое применение шаблонов проектирования
ÐÈÑ. 12.1. Концептуальная схема предлагаемого решения Таблица 12.1. Общие требования к программе Требования
Пояснение
Получить информацию о детали от САПР и извлечь из нее описание от дельных элементов
∑ Система должна быть способна получать от САПР и анализировать информацию о дета лях, вырезаемых из металлического листа ∑ Экспертная система должна спроектировать технологическую последовательность опера ций обработки металлического листа и сге нерировать набор соответствующих инст рукций для станка с числовым программным управлением
Поддерживать обработку большого количества различных типов деталей
∑ Первоначально внимание концентрируется на деталях, изготовляемых из металлическо го листа ∑ Каждая такая деталь может состоять из мно жества элементов различных типов (пазы, отверстия, просечки, отверстия специальной формы и отверстия неправильной формы). Маловероятно, что в будущем появятся какие либо другие типы элементов
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
175
Окончание таблицы Требования
Пояснение
Поддержка нескольких версий САПР
∑ На рис. 12.1 показано, что программа должна позволять работать с САПР различных версий, причем переход на другую версию не должен требовать внесения какихлибо изме нений в экспертную систему
Переосмысление проблемы с применением шаблонов Мы уже познакомились с несколькими шаблонами проектирования и новой мето дологией проектирования, предложенной Александером. Согласно этой методологии начать работу следует с анализа самой общей картины, а затем добавлять к ней от дельные детали. Реализация этого подхода при разработке программных проектов состоит из следующих этапов. 1. Отыскать шаблоны, присутствующие в проблемной области, и проанализиро вать полученный набор. 2. Для полученного набора шаблонов выполнить такие действия: а) выбрать шаблон, который в наибольшей степени формирует контекст для всех остальных шаблонов; б) применить этот шаблон к самой высокоуровневой схеме реализации проекта; в) выявить любые дополнительные шаблоны, которые могут появиться в полу ченной схеме, и добавить их к набору, который был получен в результате предыдущего анализа; г) повторно применить указанный процесс к оставшемуся набору шаблонов, выделенных в результате анализа. 3. Внести в проект все необходимые дополнительные детали. Записать определе ние классов и их методов. Следует отметить, что этот подход применим только тогда, когда в терминах шабло нов можно составить представление о всей проблемной области в целом. К сожалению, так получается далеко не всегда. Часто шаблоны проектирования позволяют лишь на чать работу, после чего следует самостоятельно наполнить проект содержанием, иден тифицируя отношения между концепциями в проблемной области. Методы, позволяю щие решить эту задачу, строятся на анализе общности и изменчивости, но их обсужде ние выходит за рамки этой книги. Дополнительную информацию о методах анализа общности и изменчивости (Commonality/Variability Analysis, CVA) желающие могут по лучить на Webстранице по адресу http://www.netobjectives.com/dpexplained.
176
Часть VI. Практическое применение шаблонов проектирования
Переосмысление проблемы с применением шаблонов. Этап 1 В предыдущих главах в проблеме САПР были идентифицированы четыре следую щих шаблона: •
Abstract Factory;
•
Adapter;
•
Bridge;
•
Facade.
Другие шаблоны на этом этапе нам не потребуются, но я готов обсудить любые до полнительные шаблоны, которые, возможно, будут найдены читателями.
Переосмысление проблемы с применением шаблонов. Этап 2, а Мы будем последовательно применять шаблоны, выбирая их на основании того, в какой степени каждый из них создает контекст для остальных шаблонов. Для того чтобы определить, какие шаблоны создают контекст для других в обсуж даемой проблемной области, воспользуемся самой простой технологией — сравним все возможные пары шаблонов. В данном случае существует всего шесть различных пар, представленных на рис. 12.2.
ÐÈÑ. 12.2. Все возможные связи между четырьмя шаблонами
Если задача содержит еще несколько шаблонов, то подобный анализ может затя нуться надолго, что крайне нежелательно. После небольшой практики вы быстро об наружите, что многие из выделенных шаблонов могут быть легко исключены из про
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
177
цедуры сравнения с важнейшими шаблонами. Чаще всего вам придется иметь дело с пятью шаблонами или около того. В нашем случае возможны всего несколько комбинаций, рассмотреть которые не составит особого труда. Что же конкретно подразумевается под выражением "один шаблон создает кон текст для другого"? Одно из определений понятия контекст гласит, что последний представляет собой взаимосвязь условий, в которых нечто существует или проявляет ся — т.е. его среду, окружение. При обсуждении примера с внутренним двором в главе 11, Как проектируют экспер ты, Александер утверждает, что крыльцо существует в контексте внутреннего двора. Внутренний двор определяет среду или окружение, в котором присутствует крыльцо. В программной системе один шаблон часто оказывается связанным с другими шаблонами, формируя их контекст. Очень важно и полезно проанализировать, где и как каждый шаблон соотносится с другими шаблонами. Следует выявить контексты, которые этот шаблон создает или предоставляет для других шаблонов, а также те, в которых этот шаблон существует. Вероятно, в некоторых случаях найти желаемые взаимосвязи окажется невозможно, однако такой анализ, несомненно, позволит соз дать более качественный проект. Поиск контекста — это важнейший инструмент, который непременно следует до бавить к вашему набору инструментов проектирования и анализа.
Правило для выделения контекста Работая над одним из проектов, я решил проанализировать используемый мной подход к проектированию. В результате я обнаружил в собственной работе одну особенность, которая имела место всегда и практически неосознанно. Я никогда не думаю о том, как создавать экземпляры используемых в программе объектов до тех пор, пока окончательно не уясню для себя, какими эти объекты должны быть. Главное внимание уделяется отношениям между объектами, причем так, как будто они уже существуют. Предполагается, что создание таких объектов не вызовет никаких проблем — в любой момент, когда это потребуется. Причина состоит в том, что я всегда стремился минимизировать количество информации, которую необходимо держать в голове при проектировании. Как правило, подобный подход характеризуется минимальным риском. Если слишком рано начать беспокоиться о том, как будут создаваться экземпляры требуемых объектов, то это приведет лишь к снижению продуктивности разработки. Лучше подождать с этим до тех пор, пока станет окончательно ясно, что именно требуется создать. Отложим на завтра то, что может подождать, — по крайней мере, до того момента, когда дело действительно дойдет до создания экземпляров объектов! Возможно, эти рассуждения покажутся вам вполне обоснованными. Но я никогда ранее не слышал, чтобы они воспринимались как некое правило, и потому решил удостовериться в их справедливости, прежде чем возвести в ранг универсального метода. Я доверяю собственной интуиции проектировщика, но, безусловно, тоже могу ошибаться. Поэтому я выяснил мнение по данному вопросу других опытных разработчиков и должен сказать, что все они без исключения следуют этому же правилу. Все это позволяет мне рекомендовать подобный подход и читателям.
Правило. Уясните, что должна содержать разрабатываемая система, прежде чем думать о том, как это можно создать. Это утверждение совпадает с правилом выявления контекста, предложенным Александером: "Если существует шаблон проектирования, предусматривающий создание объектов, то эти объекты определяют контекст для данного шаблона".
Определяя, какой шаблон создает контекст для других, всегда следует начинать с шаблона Abstract Factory. Контекст шаблона Abstract Factory всегда определяется теми объектами, экземпляры которых требуется создавать, — как следует из приведенных ниже рассуждений.
178
Часть VI. Практическое применение шаблонов проектирования
•
Шаблон Abstract Factory требует определить набор методов создания объектов, причем реализация каждого из них будет включать возврат указателя на новый объект xxx.
•
В настоящее время еще неизвестно, каким именно будет этот объект xxx.
•
Что должен представлять собой xxx, определяется объектами, которые будут использоваться в системе.
•
Объекты, используемые в системе, определяются с помощью остальных шаблонов.
Поэтому шаблон Abstract Factory будет просто невозможно выявить, пока не станет известен набор классов, определяемых другими шаблонами. Очевидно, что шаблон Abstract Factory не является шаблоном высшего уровня (т.е. создающим контекст для всех остальных шаблонов). Следовательно, это не тот шаблон, с которого следует на чать общий анализ системы. Фактически, шаблон Abstract Factory будет последним применяемым шаблоном из всех, присутствующих в проекте (если только на первых этапах проектирования сис темы не обнаружится какойнибудь другой шаблон создания объектов — тогда оба эти шаблона будут соперничать за право оказаться последним).
Шаблоны высшего уровня ограничивают остальные шаблоны Шаблон высшего уровня (supermost) — это мой термин, обозначающий один или два шаблона, задающие контекст для всех остальных шаблонов в системе. Этот шаблон накладывает ограничения на то, что могут делать остальные шаблоны. Иначе можно было бы назвать этот шаблон, например, внешним или контекстно"задающим.
У нас осталось три пары шаблонов, которые необходимо рассмотреть: •
Adapter–Bridge;
•
Bridge–Facade;
•
Facade–Adapter.
Не имея большого опыта работы с шаблонами, довольно трудно понять, какой шаблон зависит от другого, так же, как и найти шаблон, который устанавливает кон текст для всех остальных. Когда отсутствует очевидный выбор, следует последовательно рассмотреть все комбинации шаблонов, стараясь дать ответ на следующие два вопроса. •
Можно ли утверждать, что один шаблон определяет поведение другого шаблона?
•
Можно ли утверждать что оба шаблона взаимно влияют друг на друга?
Как мы уже знаем, шаблон Adapter — это шаблон, изменяющий интерфейс класса так, что в результате получается другой интерфейс, отвечающий ожиданиям класса клиента. В нашем случае адаптируемым интерфейсом является OOGFeature. Шаблон Bridge отделяет множество конкретных вариантов абстракции от их реализации. В нашем случае абстракцией является класс Feature, а ее реализациями — системы V1 и V2. Получается, что шаблон Bridge нуждается в шаблоне Adapter для модификации интерфейса OOGFeature, отсюда можно сделать вывод о том, что Bridge будет ис пользовать шаблон Adapter.
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
179
Теперь ясно, что между шаблонами Bridge и Adapter существуют определенные отношения. Однако можно ли определить один из этих шаблонов без другого, или же они со вершенно необходимы друг другу? Внимательный взгляд на эти шаблоны подсказывает нам, как следует поступить. •
Мы можем говорить о шаблоне Bridge применительно к отделению класса
Feature от систем V1 и V2 даже без какихлибо конкретных знаний о том, как будут использоваться эти системы V1 и V2. •
Однако невозможно сказать чтолибо об использовании шаблона Adapter для модификации интерфейса программы V2, не имея информации о том, к какому именно виду его следует привести. Но без шаблона Bridge этот интерфейс не существует. Можно сделать вывод, что шаблон Adapter необходим для приведе ния интерфейса программы V2 к интерфейсу реализации, определяемому шаблоном Bridge.
Таким образом, шаблон Bridge создает контекст для шаблона Adapter. Этот вывод позволяет исключить шаблон Adapter из кандидатов на роль шаблона высшего уровня.
Взаимосвязь между созданием контекста и отношением использования Часто оказывается, что когда один шаблон использует другой, то используемый шаблон существует только в контексте того шаблона, который его использует. Вероятно, существуют и исключения из этого правила, но в большинстве случаев оно является верным.
Теперь нам осталось проанализировать только две пары шаблонов: Bridge–Facade и Facade–Adapter. Сначала рассмотрим отношения между шаблонами Bridge и Facade, поскольку в случае, если шаблон Bridge окажется первичным, необходимость рассматривать от ношения в паре Facade–Adapter просто отпадет (напомню, что сейчас мы пытаемся идентифицировать шаблон высшего уровня). Должно быть совершенно очевидно, что те рассуждения, которые мы применили к паре шаблонов Bridge–Adapter, распространяются и на пару Bridge–Facade. •
Шаблон Facade используется с целью упрощения интерфейса системы V1.
•
Но кто же будет использовать новый интерфейс? Видимо, одна из реализаций шаблона Bridge.
Можно сделать вывод, что шаблон Bridge создает контекст для шаблона Facade. А сам шаблон Bridge и является шаблоном высшего уровня. Следуя рекомендациям Александера, начнем работу с анализа проблемы в целом. Однако, возвратившись к самому началу, мы обнаружим, что контекст для шаблона Bridge все еще не определен.
180
Часть VI. Практическое применение шаблонов проектирования
Переосмысление проблемы с применением шаблонов. Этап 2, б Итак, восстановим последовательность этапов проектирования для поиска кон текста, в котором присутствует шаблон Bridge. Нам необходимо разработать систему, предназначенную для преобразования информации о деталях, выбираемой из базы данных САПР, в набор команд для станка с числовым программным управлением, по зволяющий изготовить эту деталь (рис. 12.3).
ÐÈÑ. 12.3. Концептуальное представление разрабатываемой системы
Далее представление о проекте было расширено благодаря использованию техно логии объектноориентированного проектирования. В частности, предоставление экспертной системе информации о детали будет обеспечивать абстрактный класс Model. Причем класс Model в нашем случае должен иметь две конкретные версии — по одной для каждой из используемых версий САПР. Соответствующая схема пред ставлена на рис.12.4.
ÐÈÑ. 12.4. Классы, осуществляющие генерацию набора команд для станка ЧПУ
Напомним, что нам не нужно разрабатывать экспертную систему, а следует просто воспользоваться уже готовой. Поэтому основное внимание можно сосредоточить на проектировании класса Model. Известно, что класс Model должен извлекать из базы
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
181
данных информацию об отдельных элементах детали, представленных абстрактным 1 классом Feature, как показано на рис. 12.5.
ÐÈÑ. 12.5. Схема функционирования класса Model
Похоже, что именно теперь мы можем применить шаблон Bridge. Очевидно, что в системе существует некоторое множество элементов, описываемых конкретными подклассами абстрактного класса Feature (абстракция) и несколько версий САПР (реализации). Именно эти объекты образуют контекст для шаблона Bridge. Шаблон Bridge связывает класс Feature с различными реализациями САПР. Класс Feature в нашей схеме соответствует классу Abstraction шаблона Bridge, то гда как САПР версии V1 и V2 представляют конкретные реализации его абстрактного класса Implementor. Но что можно сказать в отношении абстрактного класса Model? Может, и здесь присутствует шаблон Bridge? Безусловно, нет. Конкретные типы клас са Model можно определить с помощью механизма наследования, так как единствен ное, что изменяется в отношении абстрактного класса Model, — это используемая реализация. В данном случае для каждой из версий САПР можно создать конкретные классы, производные от класса Model (рис. 12.6). Попытка применить для класса Model шаблон Bridge приводит к получению схемы, представленной на рис. 12.7. Обратите внимание на то, что в схеме на рис. 12.7 шаблон Bridge в действительно сти отсутствует, поскольку класс Model никогда не изменяется, за исключением мо мента реализации. Что касается класса Feature, то для него действительно сущест вуют различные конкретные типы, дополнительно имеющие различные типы реали заций, поэтому шаблон Bridge здесь присутствует. Приступим к воплощению шаблона Bridge, используя класс Feature как абстрак цию и системы V1 и V2 как основу реализации. Чтобы перевести нашу задачу в терми ны шаблона Bridge, обратимся к стандартной схеме этого шаблона и заменим ее клас сы теми, которые требуются в нашем случае. На рис. 12.8 показана стандартная, уп рощенная (иногда ее называют канонической) схема шаблона Bridge.
1 Различия между классами V1Model и V2Model относительно невелики. Поэтому в дальней ших рассуждениях мы будем говорить об обобщенном классе Моdel.
182
Часть VI. Практическое применение шаблонов проектирования
ÐÈÑ. 12.6. Использование наследования для представления двух версий описания деталей
ÐÈÑ. 12.7. Использование шаблона Bridge для представления двух версий описания деталей
ÐÈÑ. 12.8. Каноническая схема шаблона Bridge
В нашем случае класс Abstraction соответствует классу Feature. Существует пять различных типов элементов: пазы, отверстия, просечки, отверстия специальной формы и отверстия неправильной формы. Реализацией являются системы версий V1 и V2. Назовем классы, представляющие эти реализации, V1Imp и V2Imp, соответст венно. Результат подстановки этих классов в каноническую схему шаблона Bridge представлен на рис. 12.9.
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
183
ÐÈÑ. 12.9. Применение шаблона Bridge к нашей задаче
На рис. 12.9 абстрактный класс Feature реализуется с помощью абстрактного класса ImpFeature, который может быть представлен конкретными классами V1Imp или V2Imp. В нашем случае класс ImpFeature должен включать интерфейс, позво ляющий классу Feature получить любую информацию, которая ему потребуется, чтобы передать классу Model те сведения, которые он запросил. Таким образом, класс ImpFeature должен содержать интерфейс, включающий следующие методы: •
метод getX для получения координаты Х элемента класса Feature;
•
метод getY для получения координаты Y элемента класса Feature;
•
метод getLength для получения длины элемента класса Feature.
Кроме того, он должен включать методы, необходимые только определенным ти пам классов Feature: •
метод getEdgeType для получения сведений о типе торца элемента класса Feature.
Замечание. Последний метод должны вызвать только те элементы, которые нужда ются в данной информации. Ниже мы обсудим, как использовать подобную контекст ную информацию при отладке кода.
Переосмысление проблемы с применением шаблонов. Этап 2, в Возможно, сейчас еще непонятно, как можно довести разработку проекта до пол ного завершения. Но не стоит заранее беспокоиться — ведь нам предстоит применить еще и другие шаблоны. Посмотрим на схему, представленную на рис. 12.9, и подумаем, присутствуют ли на ней какиенибудь другие шаблоны, которые остались неидентифицированными до настоящего момента. Лично я не вижу никаких других шаблонов. Единственная не решенная пока задача состоит в подключении нашей системы к САПР версий V1 и V2. Именно эту задачу предстоит решить с помощью шаблонов Facade и Adapter.
184
Часть VI. Практическое применение шаблонов проектирования
Переосмысление проблемы с применением шаблонов. Этап 2, г (шаблон Facade) Теперь необходимо проверить, создает ли какойлибо из оставшихся шаблонов контекст для другого шаблона. В нашем случае уже понятно, что шаблоны Facade и Adapter связаны с разными частями проекта и не зависят один от другого. Поэтому применять их можно в любом порядке. Выберем первым шаблон Facade. Результат его применения в проекте представлен на рис. 12.10.
ÐÈÑ. 12.10. Результат применения шаблонов Facade и Bridge
Применение шаблона Facade приводит к тому, что между модулем V1 и классом V1Imp, который его использует, будет вставлен промежуточный класс V1Facade. Этот класс включает реализацию методов, в выполнении которых нуждается объект V1Imp. Каждый подобный метод класса V1Facade представляет собой некоторую по следовательность вызовов функций системы V1. Тип информации, которая необходима для вызова этих функций, определяет спо соб реализации класса V1Imp. Например, при использовании системы V1 необходимо сообщить ей, какая деталь нас интересует, и указать идентификаторы ее элементов. Следовательно, все объекты класса V1Imp, использующие объект класса V1Facade, должны обладать этой информацией. Поскольку данная информация зависит от кон кретной реализации, поступать она должна независимо, т.е. эта информация не мо жет присутствовать в запросе от класса Feature. Таким образом, в системе V1 каждо му объекту класса Feature должен быть определен соответствующий ему объект класса V1Imp (для хранения специфической для этой системы информации об эле ментах). Мы еще вернемся к этому моменту и обсудим его более подробно, когда за кончим определение общей структуры проекта.
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
185
Использование специфических сведений для отладки кода Выше в этой главе уже упоминалось, что некоторые из методов на стороне реализации должны вызваться только отдельными типами объектов класса Feature. Зная, кто и что должен вызывать, можно вставить соответствующие проверки в программный код. Конечно, поступать так вовсе не обязательно, и впоследствии при изменении требований к системе подобные проверки может потребоваться удалить. Однако на первых этапах отладки данная практика может оказаться весьма полезной. Например, в нашей задаче с поддержкой различных версий САПР имеются объекты класса Feature, содержащие соответствующий объект реализации. Один из методов в объектах реализации называется getEdgeType. Его выполнение имеет смысл только тогда, когда объект класса Feature представляет паз или просечку. Никаким другим типам объектов класса Feature информация о виде торца не требуется. Если их реализация будет выполнена правильно, то метод getEdgeType будет вызываться только для элементов, представляющих собой паз или просечку. Можно организовать проверку выполнения этого правила, включив в код метода getEdgeType оператор, анализирующий тип вызывающего объекта класса Feature.
Переосмысление проблемы с применением шаблонов. Этап 2, г (шаблон Adapter) Внедрив в проект шаблон Facade, можно поместить в него и шаблон Adapter. Полученный результат представлен на рис. 12.11.
Переосмысление проблемы с применением шаблонов. Этап 2, г (шаблон Abstract Factory) Осталось рассмотреть последний шаблон — Abstract Factory. Однако можно заме тить, что теперь в этом шаблоне нет никакой необходимости. Смысл применения шаблона Abstract Factory состоял в получении гарантии, что все объекты стороны реа лизации будут иметь либо тип V1, либо тип V2, в строгом соответствии с версией ис пользуемой системы. Однако объект Model всегда будет знать тип используемой вер сии. Нет никакого смысла реализовывать данный шаблон, если какойнибудь другой объект легко может инкапсулировать в себе правила создания новых объектов. Я со хранил шаблон Abstract Factory в наборе используемых шаблонов лишь потому, что в начале проектирования у меня сложилось впечатление, что он действительно может понадобиться. Это также пример того, что предположение о наличии в проекте шаб лона, когда в действительности это не так, необязательно приводит к снижению про изводительности.
Часть VI. Практическое применение шаблонов проектирования
РИС. 12.11. Результат применения шаблонов Bridge, Facade и Adapter
186
ÐÈÑ. 12.11. Результат применения шаблонов Bridge, Facade и Adapter
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
187
Переосмысление проблемы с применением шаблонов. Этап 3 Некоторые детали проекта все еще требуют определенной проработки, и мы про должим работу, следуя рекомендации Александера проектировать с учетом контекста. Например, решая, как следует реализовать класс SlotFeature или класс V1Imp, не обходимо учитывать методы использования помещенных в проект шаблонов. Так, в нашем случае следует помнить, что благодаря шаблону Bridge методы на стороне аб стракции не зависят от стороны реализации. А это означает, что класс шаблона Abstraction (в нашем случае это класс Feature) и все производные от него кон кретные классы (SlotFeature, HoleFeature и т.д.) не содержат никакой информа ции о стороне реализации. Все сведения о реализации абстракции должны быть по мещены в класс шаблона Implementation (в нашем случае это класс ImpFeature). Вышесказанное означает, что классы, производные от абстрактного класса Feature, будут включать универсальные методы, например, getLocation или getLength, в то время как класс ImpFeature будет отвечать за конкретные способы извлечения требуе мой информации. Например, объект класса V1Imp должен знать идентификаторы эле ментов в системе V1. Поскольку каждый элемент в этой системе имеет уникальный идентификатор, это означает, что потребуется по одному объекту реализации для каж дого объекта класса Feature. Методы объекта класса V1Imp будут использовать этот идентификатор, посылая запрос к объекту класса V1Facade для получения информации об объекте. Сопоставимое решение существует и в отношении реализации для системы V2. В этом случае объекты класса V2Imp будут содержать в запросе ссылку на требуемый объект класса OOGFeature.
Сравнение полученных результатов с предыдущим решением Сравним вновь полученное решение, представленное на рис. 12.11, с нашим пер вым решением, которое еще раз показано на рис. 12.12. Хороший способ сравнить два решения состоит в том, чтобы прочитать их. Дру гими словами, диаграммы визуально представляют отношения наследования (отноше ния isa) и композиции (отношения hasa). Можно прочитать каждую диаграмму, ис пользуя, соответственно, слова "является" и "включает" для обозначения присутст вующих на ней отношений указанного типа. В первоначальном решении представление детали включало набор объектов клас са Feature. Класс Feature представляет любые элементы — пазы, отверстия, про сечки, отверстия специальной формы и отверстия неправильной формы. Элемент паз (класс SlotFeature) может быть представлен как объектом системы V1, так и объектом системы V2. Класс V1Slot использует систему V1, тогда как класс V2Slot использует класс OOGSlot. Элементотверстие (класс HoleFeature) может быть представлен как объектом системы V1, так и объектом системы V2. Класс V1Hole ис пользует систему V1, тогда как класс V2Hole использует класс OOGSlot. И так далее — утомительно, не правда ли?
Часть VI. Практическое применение шаблонов проектирования
РИС. 12.12. Первый вариант решения задачи САПР
188
ÐÈÑ. 12.12. Первый вариант решения задачи САПР
Глава 12. Решение задачи САПР с помощью шаблонов проектирования
189
А теперь попробуем прочитать новый вариант решения. Существует деталь (класс Model), включающая набор элементов (класс Feature). Класс Feature представляет любые элементы — пазы, отверстия, просечки, отверстия специальной формы и от верстия неправильной формы. Все объекты, представляющие отдельные элементы, включают объекты реализации — это реализация либо для системы версии V1, либо для системы версии V2. Реализация для системы V1 использует класс V1Facade для доступа к САПР версии V1, а реализация для системы V2 с помощью объекта класса OOGFeature адаптирует конкретные типы объектов элементов в САПР версии V2. Вот и все. Несомненно, что описание этого варианта звучит намного лучше первого.
Резюме В этой главе было показано, как стандартный подход к проектированию часто при водит к созданию систем, которые потом очень трудно сопровождать. По причине чрезмерной сосредоточенности на деталях будущей системы — ее классах — теряется общая перспектива, и за отдельными деревьями проектировщик просто не видит леса. Кристофер Александер предложил лучший путь. Применяя шаблоны проектиро вания к проблемной области, можно осмыслить задачу при различной степени дета лизации. Начиная с общей картины, мы постепенно расширяем ее новыми деталями и понятиями. Каждый шаблон добавляет новую информацию к той, которая была на коплена прежде. Выбрав шаблон, который создает самую общую картину — контекст всей системы, мы последовательно добавляем к нему другие важные шаблоны. Подобным образом удается разработать такую структуру приложения, которую невозможно увидеть, сосредото чившись на одних только классах. Такой подход учит нас проектировать от контекста, а не предпринимать малоэффективные попытки соединить в общее целое отдельные де тали, которые были предварительно идентифицированы в проблемной области. Вспомните двух плотников, о которых шла речь в главе 5, Первое знакомство с шаб лонами проектирования. Они пытались сделать выбор между различными способами соединения деревянных деталей. Контекст, вот что должно формировать структуру проекта. В процессе проектирования мы часто погружаемся в детали и забываем об общем контексте системы. Детали затуманивают общую картину, заставляя нас сосре доточиться на мелких, локальных решениях. Шаблоны же позволяют нам подняться над множеством деталей и действительно учесть особенности контекста в принимае мых решениях. Используя шаблоны, можно обнаружить и учесть все факторы, дейст вующие в проблемной области, а также воспользоваться наработками других проек тировщиков. Поэтому применение шаблонов помогает создавать устойчивые, эффек тивные и удобные в сопровождении системы.
190
Часть VI. Практическое применение шаблонов проектирования
Глава 13
Обработка возможных вариаций с помощью шаблонов проектирования Введение В предыдущих главах было показано, каким образом шаблоны проектирования мо гут применяться как на локальном, так и на глобальном уровнях. На локальном уровне они демонстрируют, как решить определенную проблему в рамках контекста соответ ствующего шаблона. На глобальном уровне шаблоны представляют схему взаимосвя зей компонентов приложения. Один из способов освоения шаблонов проектирова ния состоит в изучении наиболее эффективных методов их использования как на гло бальном, так и на локальном уровнях, которые в совокупности представляют собой прекрасный инструмент решения проблем. Другой способ изучения шаблонов проектирования заключается в ознакомлении с теми закономерностями, принципами и алгоритмами, которые положены в их осно ву. Полученные знания помогут вам существенно развить свои способности как ана литика и проектировщика. Изучение этих принципов и алгоритмов поможет найти выход даже в тех ситуациях, когда требуемые шаблоны проектирования еще не созда ны, поскольку набор строительных блоков, необходимых для решения проблемы, уже будет вам известен. В этой главе мы выполним следующее. •
Познакомимся с принципом открытостизакрытости, положенным в основу многих шаблонов проектирования.
•
Обсудим принцип проектирования от контекста, который является важнейшим звеном идеологии шаблонов Александера.
•
Рассмотрим принцип включения вариаций.
Принцип открытостиNзакрытости Очевидно, что программное обеспечение должно быть расширяемым. Однако всякое изменение программного обеспечения связано с риском внесения ошибок. Для устранения этой дилеммы Бертран Мейер (Bertrand Meyer) предложил принцип 1 открытостизакрытости . Упрощенно данный принцип можно сформулировать так:
1 Meyer B. ObjectOriented Software Construction, Upper Saddle River, N.J.: Prentice Hall, 1997, с. 57.
192
Часть VI. Практическое применение шаблонов проектирования
"Модули, методы и классы должны быть открыты для расширения, но закрыты для 2 модификации". Другими словами, программное обеспечение следует проектировать таким образом, чтобы можно было расширять его возможности, не изменяя то, что уже существует. Как бы противоречиво это не звучало поначалу, мы, тем не менее, уже встречались с подобными примерами. При обсуждении шаблона Bridge была продемонстрирована возможность добавления новых реализаций (т.е. расширения возможностей про граммного обеспечения) без модификации какоголибо из существующих классов.
Принцип проектирования от контекста Александер рекомендует проектировать, отталкиваясь от контекста, сначала созда вая общую картину, а потом переходя к проектированию деталей образующих ее эле ментов. Большинство шаблонов следуют именно такому подходу, одни в большей степе ни, другие в меньшей. Из тех четырех шаблонов, с которыми мы уже познакомились, шаблон Bridge является наиболее ярким примером применения данного правила. Взгляните еще раз на схему шаблона Bridge, приведенную в главе 9, Шаблон Bridge (рис. 9.13). Принимая решение о том, как проектировать классы на стороне реализа ции, учитывайте их контекст — т.е. каким образом классы, производные от класса Abstraction, будут их использовать. Например, если разрабатывается система, предназначенная для вывода изображе ния разнообразных геометрических фигур на оборудование различного типа, ей обяза тельно потребуется несколько различных типов классов реализаций, для чего целесо образно использовать шаблон Bridge. Общая схема шаблона Bridge свидетельствует, что классы геометрических фигур будут использовать классы реализации (т.е. различные версии графических программ) через общий интерфейс. В данном случае проектирова ние от контекста, как рекомендует Александер, означает, что сначала следует рассмот реть, что представляют собой фигуры, которые необходимо изобразить — т.е. выяснить, что, собственно, предстоит отображать. Именно эти требования будут определять по ведение классов стороны реализации. Так, от этих классов (графических программ), безусловно, потребуется способность отображать линии, окружности и т.д. Применение методов анализа общности/изменчивости в приложении к тому кон тексту, в пределах которого существуют интересующие нас классы, позволяет выявить различные случаи их использования (прецеденты), как существующие, так и потенци альные. В результате можно будет принять обоснованное решение о желательном уров не обобщения (генерализации) на стороне реализации, основываясь на предполагае мых затратах, которых потребует поддержка того или иного уровня обобщения. Такой подход часто позволяет найти более общее решение для стороны реализации, чем это ожидалось поначалу, при этом требующее минимальных дополнительных издержек. Например, для отображения геометрических фигур, на первый взгляд, вполне достаточно линий и окружностей. Однако давайте зададим себе вопрос: "Отображе ние каких фигур не может быть выполнено с помощью только прямых линий и дуг окружностей?". Ответ очень прост — это эллипсы. Теперь нам потребуется сделать выбор между следующими вариантами.
2 Прекрасную статью “The OpenClosed Principle” Роберта Мартина (Robert C. Martin) можно найти на Webстранице http://www.netobjectives.com/dpexplained.
Глава 13. Обработка возможных вариаций...
193
•
Дополнительно к линиям и окружностям реализовать отображение эллипсов.
•
Учитывая, что эллипс является обобщением понятия окружности, реализовать отображение эллипсов вместо окружностей.
•
Отказаться от реализации отображения эллипсов, если связанные с этим издержки не компенсируются полученными преимуществами.
Приведенный выше пример иллюстрирует еще одну важную концепцию проектиро вания — наличие возможности реализовать чтолибо вовсе не означает, что это обяза тельно должно быть выполнено. Мой опыт работы с шаблонами проектирования пока зывает, что они позволяют очень хорошо изучить характеристики проблемной области. Однако я крайне редко учитываю все обнаруженные ситуации и чаще всего отказываюсь от написания кода для поддержки тех из них, которые в данный момент еще не пред ставлены на практике. Тем не менее, проектирование от контекста с применением шаб лонов позволяет предвидеть и учесть появление возможных изменений, создавая сис тему, продуманно разделенную на классы и заранее приспособленную к ожидаемым из менениям. Шаблоны проектирования помогают понять, в каких местах возможны изменения, но не дают конкретных указаний о том, какими они будут. Однако хорошо продуманный интерфейс будет не только эффективно работать с уже существующими вариациями, но позволит учесть потребности новых потенциальных требований. Шаблон Abstract Factory — еще один хороший пример проектирования от контек ста. Вполне очевидно, что объектфабрика некоторого типа будет использоваться для координации процесса создания семейств (или наборов) экземпляров объектов. Однако существует несколько различных способов реализовать эту задачу (табл. 13.1). Таблица 13.1. Возможные варианты реализации шаблона Abstract Factory Вариант
Описание
Использование произ водных классов
Классическая реализация шаблона Abstract Factory требует определения производных классов для каждого из сущест вующих наборов объектов. Это несколько громоздкий вари ант, но он имеет весомые преимущества, позволяя добавлять новые классы, не затрагивая ни один из уже существующих
Использование одного объекта с переключателями
Если согласиться на изменение класса Abstract Factory по ме ре необходимости, то можно просто создать один объект, который будет включать все правила. Хотя этот подход про тиворечит принципу открытостизакрытости, все правила будут реализованы в одном месте, и такую систему будет дос таточно легко обслуживать
Использование файла конфигурации и пере ключателей
Это более гибкий способ по сравнению с предыдущим, но здесь так же время от времени потребуется вносить измене ния в программный код
Использование файла конфигурации совмест но с механизмом RTTI
Механизм RTTI (RunTimeTypeIdentification — определение типа во время выполнения) включает средства создания эк земпляров объектов с выбором их типа на основании имени объекта, помещенного в строковую переменную. Реализации этого типа присуща максимальная гибкость, так как новые классы и новые комбинации могут быть добавлены в систему без изменения какоголибо программного кода
194
Часть VI. Практическое применение шаблонов проектирования
Какие же рекомендации можно дать по выбору оптимального варианта реализа ции шаблона Abstract Factory? Ответ прост — решение нужно принимать исходя из то го контекста, в котором он присутствует. Каждый из четырех вариантов реализации имеет свои преимущества в зависимости от следующих факторов. •
Вероятность будущих изменений.
•
Важность сохранения неизменности существующей системы.
•
Доступность для модификации классов, входящих в отдельные семейства (кто является их создателем — вы или другая группа программистов).
•
Используемый язык программирования.
•
Наличие и доступность базы данных или файла конфигурации.
Безусловно, этот список не полон, так же, как и список вариантов реализации. Однако каждому должно быть понятно, что попытка решить, как следует реализовать шаблон Abstract Factory, без учета того, как создаваемая система будет использоваться (т.е. без понимания контекста), выглядит, по меньшей мере, глупо.
Как принимаются проектные решения Делая выбор между альтернативными вариантами реализации, многие разработчики стремятся получить ответ на вопрос: "Какая из возможных реализаций является лучшей?". Однако на самом деле так ставить вопрос нельзя. Проблема заключается в том, что очень редко одна реализация бывает лучше другой во всех отношениях. Предпочтительнее рассматривать каждую альтернативу в отдельности, задаваясь следующим вопросом: "При каких обстоятельствах данная альтернатива будет лучше, чем другие?". Затем следует ответить на такой вопрос: "Какие из этих обстоятельств более всего характерны для заданной проблемной области?". При необходимости легко можно остановиться и вернуться на шаг назад. Подобный подход заостряет внимание проектировщика на возможных вариациях и проблемах масштабирования системы в заданной проблемной области.
Шаблон Adapter иллюстрирует принцип проектирования от контекста потому, что сам он почти всегда обнаруживается в пределах контекста. По определению шаблон Adapter применяется для приведения существующего интерфейса к некоторому дру гому интерфейсу. Напрашивается следующий вопрос: "Как узнать, к какому виду сле дует привести существующий интерфейс?". Как правило, до окончательного форми рования контекста (определения, к какому именно классу требуется адаптация) отве тить на этот трудно. Выше уже обсуждалось, как шаблон Adapter может быть использован для адапта ции класса к роли, предусмотренной для него шаблоном. В частности, при решении проблемы поддержки в приложении нескольких версий САПР потребовалось адапти ровать определенную существующую реализацию к виду, необходимому для использо вания ее в шаблоне Bridge. Шаблон Facade в терминах контекста очень напоминает шаблон Adapter. Чаще всего он присутствует в контексте других шаблонов или классов. Следовательно, пре жде чем приступать к разработке интерфейса, необходимо подождать, пока не будет установлено, кто же его будет использовать. При первых попытках использования шаблонов проектирования я полагал, что всегда можно отыскать шаблон, который создает контекст для других шаблонов. Во всяком случае, Александеру в его книге всегда удавалось сделать это, правда он
Глава 13. Обработка возможных вариаций...
195
имел дело с шаблонами в области архитектуры. Поскольку множество людей прини мало участие в обсуждении проблемы создания языка шаблонов для разработки про граммного обеспечения, я также задумался: "Почему бы и мне не попробовать?". Ведь кажется абсолютно очевидным, что шаблоны Adapter и Facade всегда будут опреде ляться в контексте чегото другого. Однако это оказалось не так. Те разработчики программного обеспечения, кото рые, как и я, занимаются преподаванием, обладают одним большим преимуществом. Оно состоит в том, что преподаватели принимают участие в гораздо большем количе стве проектов, чем обычные разработчики. В начале моей преподавательской дея тельности, связанной с шаблонами проектирования, я полагал, что в последователь ности определения контекста шаблоны Adapter и Facade всегда рассматриваются по сле других шаблонов, не связанных с созданием объектов. Чаще всего именно так и происходит. Однако некоторые системы включают требование создания некоторого специфического интерфейса. В этом случае шаблон Facade или Adapter (единствен ный из многих шаблонов, присутствующих в системе) может оказаться в системе шаб лоном высшего уровня.
Принцип включения вариаций Несколько человек независимо указали на определенное сходство всех моих про ектов, с которыми им приходилось сталкиваться. Дело в том, что иерархия наследо вания классов в моих проектах редко содержит более двух уровней в глубину. Так происходит потому, что мои проекты имеют типичную для шаблонов проектирова ния структуру из двух уровней для основных и абстрактных классов. (Правда, сущест вуют и исключения — например, шаблон Decorator, обсуждаемый в главе 15, использу ет три уровня классов.) Основная причина заключается в том, что при проектировании я придерживаюсь правила никогда не создавать классов, включающих несколько подверженных вариаци ям элементов, которые так или иначе связаны между собой. Обсуждавшиеся нами выше шаблоны демонстрируют различные способы эффективного включения вариаций. Шаблон Bridge являет собой превосходный пример включения вариаций. Все реа лизации, присутствующие в шаблоне Bridge, различны, но доступ к ним организуется через общий интерфейс. Новые реализации легко могут быть осуществлены в преде лах этого интерфейса. Шаблон Abstract Factory включает вариации в отношении наборов или семейств тех объектов, экземпляры которых создаются. Существует несколько различных пу тей реализации этого шаблона. Хочу особо обратить ваше внимание на то, что даже если первоначально был выбран некоторый вариант реализации, а позднее было ус тановлено, что имеется другой, лучший путь, реализация шаблона может быть изме нена без оказания какоголибо влияния на остальные части системы (интерфейс фаб рики остается неизменным, меняется только способ ее реализации). Таким образом, сам принцип построения шаблона Abstract Factory скрывает все вариации в отноше нии того, как объекты создаются. Шаблон Adapter — это инструмент, обеспечивающий использование различных по происхождению объектов через общий интерфейс. Это часто бывает необходимо в отношении интерфейсов, вызываемых из многих шаблонов. Таким образом, шаблон Adapter предназначен для сокрытия вариаций в интерфейсах классов.
196
Часть VI. Практическое применение шаблонов проектирования
Шаблон Facade, как правило, не включает изменений. Однако практика дает мно жество примеров использования шаблона Facade для работы с конкретными подсис темами. В этом случае при появлении новой подсистемы для нее строится собствен ный шаблон Facade с тем же самым интерфейсом. Этот новый класс представляет со бой комбинацию шаблонов Facade и Adapter. Однако, если изначально эти шаблоны использовались для упрощения, то теперь они позволяют сохранить прежний интер фейс и избежать изменения существующих клиентских объектов. Подобное примене ние шаблона Facade позволяет скрыть вариации в используемых подсистемах. Однако шаблоны предназначены не только для включения вариаций. Они также определяют отношения между отдельными вариациями. Подробнее об этом речь пойдет в следующих главах. В отношении шаблона Bridge можно дополнительно ука зать, что он не только определяет и включает вариации в абстракции и реализации, но и определяет отношения между ними.
Резюме В этой главе мы обсудили, как в шаблонах проектирования иллюстрируется при менение двух мощных стратегий проектирования: •
проектирование от контекста;
•
включение вариаций в классы.
Использование этих стратегий позволяет отложить принятие решения до тех пор, пока не будут выявлены все возможные варианты. Тщательный анализ контекста ре шаемой задачи позволяет найти лучшие проектные решения. Посредством включения вариаций в классы удается учесть потенциальные изме нения, которые могут остаться необнаруженными, если предварительно не взглянуть на проект с более общей точки зрения. Это особенно важно для таких проектов, ко торые не обеспечиваются всеми требуемыми ресурсами в полном объеме (другими словами, для всех проектов). Корректное включение вариаций позволяет ограни читься реализацией только тех функций, которые требуются на текущий момент, не ставя под угрозу сохранение качества проекта в будущем. Не стоит забывать, что по пытки выявить все потенциальные вариации и обеспечить их поддержку в системе обычно ведут не к созданию лучшей системы, а к краху проекта вообще. Это явление обычно называют параличом от анализа.
ЧАСТЬ V
Обработка вариаций с применением шаблонов проектирования В этой части В этой части книги мы рассмотрим еще один практический пример. В данном слу чае требования к системе будут анализироваться поочередно, без их общего предва рительного обсуждения. Будем считать, что существует некоторая уже готовая и функционирующая система, по отношению к которой выдвигаются новые требова ния. Появление каждого нового требования заставляет выполнить поиск наилучшего варианта изменения уже существующего программного кода. Такой подход позволит нам последовательно познакомиться с несколькими новыми шаблонами проектиро вания, по одному для каждого изменения требований. Глава
Предмет обсуждения
14
Шаблон Strategy. Обработка изменений в алгоритме и бизнесправилах
15
Шаблон Decorator. Динамическое добавление функциональности до или по сле уже существующих правил поведения объекта
16
Шаблон Singleton и шаблон DoubleChecked Locking. Гарантированное соз дание не более одного экземпляра объекта заданного класса даже в многопо точном окружении
17
Шаблон Observer. Извещение одной части системы о событии, произошед шем в другой ее части
18
Шаблон Template Method. Решение проблемы существования нескольких различных прецедентов, использующих одну и ту же процедуру, но с отли чающейся последовательностью выполнения отдельных ее этапов
19
Шаблон Factory Method. Передача функции выбора класса объекта, экземп ляр которого будет создан, производным классам
20
Метод матрицы анализа. Выявление множества присутствующих в проблем ной области вариаций и отображение их в шаблоны
Для каждого нового требования, предъявляемого к системе, мы рассмотрим такие возможные варианты реализации, как: •
использование переключателей в программном коде;
•
специализация на основе механизма наследования;
•
инкапсуляция изменений с последующим включением данного объекта или организацией ссылки на него.
Обсуждение этих альтернатив поможет нам уяснить, что среди многих шаблонов существует определенное сходство: обычно различные шаблоны предусматривают обработку вариаций и удовлетворение новых требований в одном и том же стиле.
Глава 14. Шаблон Strategy
199
Глава 14
Шаблон Strategy
Введение В этой главе мы познакомимся с новым практическим примером, взятым из облас ти электронной коммерции. В уже существующую систему поддержки розничных продаж через Internet будет предложено внести определенное изменение. Решая по ставленную задачу, мы познакомимся с шаблоном Strategy (стратегия). В каждой по следующей главе, вплоть до главы 20, Матрица анализа, наш новый пример будет по следовательно усложняться. Здесь мы выполним следующее. •
Обсудим оптимальный подход к обработке новых требований к существующей системе.
•
Познакомимся с новым учебным примером.
•
Опишем шаблон Strategy и рассмотрим, как его можно использовать для обработки новых требований в нашем примере.
•
Проанализируем ключевые особенности шаблона Strategy.
Оптимальный подход к обработке новых требований Как в обычной жизни, так и при проектировании программных приложений каждо му из нас неоднократно приходилось искать оптимальный подход к решению задачи или устранению проблемы. Большинство из нас на собственном опыте знает, что реше ния, обеспечивающие моментальную выгоду, часто приводят к серьезным затруднениям в будущем. Например, каждый водитель знает, что после определенного пробега масло в автомобиле обязательно следует поменять. Конечно, нет необходимости менять масло через каждые 5 000 км, однако откладывать полную смену масла в двигателе до достиже ния пробега в 50 000 км нельзя (иначе необходимость замены масла отпадет сама собой — автомобиль просто выйдет из строя!). Другой пример — представьте себе пись менный стол. Очень удобно хранить все нужные бумаги и канцелярские принадлежно сти под рукой, прямо на его поверхности. Однако это удобство будет сохраняться не долго, и через некоторое время стол окажется завален грудами бумаг, папок, книг и тому подобного, в которых найти чтолибо будет просто невозможно. Кстати, различные ка тастрофы чаще всего являются отдаленным следствием псевдооптимальных решений, принятых на скорую руку, без учета длительной перспективы.
200
Часть V. Обработка вариаций с применением шаблонов проектирования
К большому сожалению, многие специалисты в области разработки программного обеспечения все еще не усвоили этот урок. Множество проектов разрабатывалось с ориентацией на решение только конкретных, сиюминутных задач без учета пробле мы сопровождения системы в будущем. Существует несколько предпосылок, способ ствующих сохранению тенденции к игнорированию таких важнейших долгосрочных аспектов созда6ваемых проектов, как простота его сопровождения или удобство вне сения изменений. Чаще всего указывают на следующие причины. •
Очень сложно предвидеть, как требования к системе будут изменяться в будущем.
•
Если заняться выяснением, как требования к системе будут изменяться в будущем, этап анализа не закончится никогда.
•
Если начать писать создаваемое программное обеспечение так, чтобы оно позволяло легко добавлять в него новую функциональность, проектирование не закончится никогда.
•
У нас на это нет ни времени, ни денег.
Существуют две крайности. •
Чрезмерное углубление в анализ и разнообразные аспекты проектирования, что обычно называют "параличом от анализа".
•
Принятие скороспелого решения с немедленным переходом к кодированию без какоголибо анализа долгосрочных аспектов проекта. В результате, очень скоро потребуется начать новый проект, так как подобная недальновидность порождает слишком много проблем.
Поскольку руководство обычно больше интересуется сроками сдачи системы, а не вопросами ее сопровождения, то такой результат не удивителен. После недолгого размышления становится совершенно очевидным, что именно удерживает разработ чиков программного обеспечения от проведения анализа возможных альтернатив. Не вызывает сомнения, что большинство из них просто убеждены в том, что проек тирование с поддержкой возможных будущих изменений непременно потребует до полнительных затрат. Но это утверждение вовсе необязательно будет справедливо. В действительности, истинно как раз обратное утверждение. Если взглянуть на систему в целом и попы таться определить, как она будет изменяться в будущем, обычно удается найти лучшее проектное решение, реализация которого потребует фактически такого же количест ва времени, как и при обычной практике проектирования. Именно такой подход к проектированию — с предварительным анализом и учетом возможных изменений — мы используем при обсуждении приведенного ниже учебного примера. Здесь очень важно еще раз обратить ваше внимание на то, что мы будем зара нее ожидать появления изменений, и задача наша состоит в том, чтобы выяснить, где они будут происходить, а не в том, чтобы определить, какими именно они будут. Дан ный подход основан на принципах, предложенных в книге "банды четырех". •
1
"Проектируйте интерфейс, а не его реализацию."
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 18.
Глава 14. Шаблон Strategy
201
•
"Отдавайте предпочтение композиции объектов, а не наследованию классов."
•
"Выясните, что может быть подвержено изменениям в вашем проекте." Этот подход — прямая противоположность фокусированию внимания на причинах, способных вызвать перепроектирование системы. Вместо того, чтобы искать то, что способно привести к изменению проекта, следует отобрать то, для чего нужно обеспечить возможность изменения в будущем без необходимости перепроектирования. Суть подхода заключается в инкапсуляции изменяемого на концептуальном уровне, что является лейтмотивом многих шаблонов 3 проектирования."
2
Я настоятельно вам рекомендую, столкнувшись с необходимостью изменения про граммного кода в связи с появлением новых требований, проанализировать возмож ность применения приведенных ниже стратегий. Использование этих стратегий не приведет к существенному удорожанию проекта или его реализации, но позволит по лучить значительные преимущества в будущем. Однако я не предлагаю следовать этим стратегиями вслепую. Всегда можно оце нить качество альтернативного варианта, проверив, насколько полно он отвечает по казателям хорошего объектноориентированного проекта. Такой же, в сущности, подход был использован при выведении шаблона Bridge в главе 9, Шаблон Bridge, где мы сравнивали качество альтернативных вариантов на основании того, какой из них лучше соответствует принципам объектноориентированного программирования.
Исходные требования к учебному проекту Предположим, что перед нами поставлена задача разработать систему поддержки электронной розничной торговли в пределах Соединенных Штатов Америки. Общая структура приложения включает объектконтроллер, предназначенный для обработ ки поступающих запросов на продажу. Он определяет момент поступления запроса на формирование заказа и передает поступивший запрос объекту выписки счета для дальнейшей обработки. Общий вид исходного варианта системы представлен на рис. 14.1.
ÐÈÑ. 14.1. Схема обработки заказа в системе роз ничной электронной торговли
2 3
Там же, с. 20. Там же, с. 29.
202
Часть V. Обработка вариаций с применением шаблонов проектирования
Класс SalesOrder имеет следующие функции. •
Предоставление графического интерфейса для заполнения заказа.
•
Вычисление суммы налога.
•
Обработка заказа и печать накладной на продажу.
Некоторые из этих функций, вероятно, следует реализовать с помощью других объектов. Например, класс SalesOrder не обязательно должен непосредственно за ниматься выводом на печать, скорее его назначение состоит в хранении информации о поступившем заказе на продажу. Отдельные объекты класса SalesOrder для распе чатки накладной на продажу могут обращаться к одному и тому же объекту класса SalesTicket.
Обработка новых требований Предположим, что в процессе работы над этим приложением было выдвинуто но вое требование об изменении способа начисления налога. Теперь система должна бу дет поддерживать начисление налога и для заказов тех клиентов, которые находятся за пределами Соединенных Штатов. Как минимум, потребуется поддержка новых правил начисления таких налогов. Как организовать в системе поддержку этих новых правил? Можно попробовать еще раз воспользоваться уже существующим классом SalesOrder и обрабатывать данную ситуацию просто как новый вид коммерческого заказа с отличающимся набо ром правил налогообложения. Например, для обработки заказов из Канады можно создать новый класс с именем CanadianSalesOrder, производный от класса SalesOrder, в котором переопределяются правила налогообложения. Данный вари ант решения представлен на рис. 14.2.
ÐÈÑ. 14.2. Возможный вариант схемы обработки заказа в системе розничной электронной торговли
Глава 14. Шаблон Strategy
203
Напомню, что шаблоны проектирования постоянно демонстрируют применение фундаментального правила идеологии шаблонов: "Отдавайте предпочтение компози 4 ции объектов, а не наследованию классов". В решении на рис. 14.2 использован пря мо противоположный подход! Другими словами, изменение в налоговых правилах обрабатывается здесь с использованием механизма наследования посредством опре деления производного класса, включающего поддержку новых правил. Какое другое решение можно предложить? Следуя изложенному выше правилу, нужно попытаться найти то, что является в проекте изменяемым, и инкапсулировать 5 эту концепцию. Требуемый результат достигается в два этапа. 1. Найти то, что изменяется, и инкапсулировать это в соответствующий специа лизированный класс. 2. Поместить этот класс в другой класс. В нашем примере уже известно, что изменяемой концепцией являются правила на логообложения. Инкапсуляция в этом случае подразумевает создание абстрактного класса, определяющего решение поставленной задачи на концептуальном уровне, с по следующим созданием конкретных производных классов для каждого из существующих вариантов. Другими словами, необходимо создать абстрактный класс CalcTax, опреде ляющий интерфейс, требуемый для решения поставленной задачи, а затем создать кон кретные классы, производные от него, для каждой существующей версии. Схематично данное решение представлено на рис. 14.3.
ÐÈÑ. 14.3. Инкапсуляция правил налогообложения
Теперь на общей схеме приложения вместо наследования классов используется композиция. Имеется в виду, что вместо создания различных версий класса обработ ки заказа (с помощью механизма наследования) включение вариации выполнено с помощью композиции объектов. Существует только один класс SalesOrder, который содержит объект класса CalcTax, предназначенный для сокрытия и обработки воз можных вариаций (рис. 14.4).
4 5
Там же, с. 20. Там же, с. 29.
204
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 14.4. Вариант схемы приложения, в котором предпочтение отдается композиции, а не наследованию
UMLNдиаграммы Язык UML позволяет определять параметры в методах. Для этого параметр и его тип указываются в скобках, входящих в описание метода. В частности, на рис. 14.4 показано, что метод taxAmount() имеет три параметра:
•
itemSold (тип Salable);
•
qty (тип double);
•
price (тип double).
Все эти параметры являются входными, что обозначено ключевым словом in. Возвращаемое методом TaxAmount значение также имеет тип double.
Таким образом, нами определен общий интерфейс для объектов класса CalcTax. Вероятно, можно было бы определить класс Saleable, предназначенный для хране ния характеристик товара (и правил его налогообложения). Объект SalesOrder мог бы передать объект этого класса объекту CalcTax наряду с данными о заказанном ко личестве и установленной ценой. Этой информации классу CalcTax было бы вполне достаточно. Дополнительное преимущество такого подхода состоит в повышении связности в системе, поскольку теперь расчет суммы налога выполняется в специализированном классе. Еще одно преимущество состоит в том, что при добавлении нового варианта правил налогообложения достаточно будет определить еще один класс, производный от класса CalcTax, включающий реализацию этих правил. Наконец, в новой версии проще обеспечить передачу ответственности. В вариан те, основанном на использовании механизма наследования, класс TaskController
Глава 14. Шаблон Strategy
205
должен будет самостоятельно определять, какой тип класса SalesOrder следует ис пользовать. В новом варианте для этой цели можно использовать как класс TaskController, так и класс SalesOrder. В последнем случае потребуется подклю чить некоторый объект конфигурации, который и будет определять, какой именно вариант начисления налога следует использовать в каждом случае (вероятно, тот же самый объект будет использоваться и классом TaskController). Данный вариант схемы приложения показан на рис. 14.5.
ÐÈÑ. 14.5. Объект SalesOrder обращается к объекту Configuration, чтобы определить, какой тип объекта класса CalcTax следует использовать
Такой подход позволяет бизнесправилам изменяться независимо от объекта SalesOrder, который эти правила использует. Обратите внимание на то, что эта схема хорошо работает как для уже существующих вариаций, так и для любых вариа ций, которые могут появиться в будущем. По существу, подобная инкапсуляция се мейства алгоритмов в абстрактном классе (CalcTax) с последующим попеременным выбором одного из членов этого семейства и представляет собой шаблон Strategy.
Назначение шаблона проектирования Strategy Согласно определению "банды четырех" назначение шаблона Strategy состоит в следующем. Определение семейства алгоритмов, инкапсуляция каждого из них и обеспечение их взаимозаменяемости. Шаблон Strategy позволяет менять выбранный алгоритм 6 независимо от объектовклиентов, которые его используют.
6
Там же, с. 315.
206
Часть V. Обработка вариаций с применением шаблонов проектирования
В основу шаблона Strategy положено несколько принципов. •
Объекты обладают обязательствами.
•
Различные специфические реализации этих обязательств проявляются за счет использования полиморфизма.
•
Существует потребность в управлении несколькими различными реализациями того, что концептуально является одним и тем же алгоритмом.
•
Следует считать хорошим стилем проектирования отделение существующих в проблемной области поведений друг от друга — т.е. уменьшение связанности между ними. Это позволяет вносить изменения в класс, ответственный за некоторое поведение, без какоголибо неблагоприятного воздействия на другие классы.
Основные характеристики шаблона Strategy Назначение
Позволяет использовать различные бизнес"правила или алгоритмы в зависимости от контекста
Задача
Выбор алгоритма, который следует применить, в зависимости от типа выдавшего запрос клиента или обрабатываемых данных. Если используется правило, которое не подвержено изменениям, нет необходимости обращаться к шаблону Strategy
Способ решения
Отделение процедуры выбора алгоритма от его реализации. Это позволяет сделать выбор на основании контекста
Участники
Следствия
Реализация
•
Класс Strategy определяет, как будут использоваться различные алгоритмы
•
Конкретные алгоритмы
•
Класс Context использует конкретные классы ConcreteStrategyX посредством ссылки на конкретный тип абстрактного класса Strategy. Классы Strategy и Context взаимодействуют с целью реализации выбранного алгоритма (в некоторых случаях классу Strategy требуется посылать запросы классу Context). Класс Context пересылает классу Strategy запрос, поступивший от его класса"клиента
•
Шаблон Strategy определяет семейство алгоритмов
•
Это позволяет отказаться от использования переключателей и/или условных выражений
•
Вызов всех алгоритмов должен осуществляться стандартным образом (все они должны иметь один и тот же интерфейс). Взаимодействие между классами ConcreteStrategyX и Context может потребовать введения в класс Context дополнительных методов типа getState
классы
ConcreteStrategyX
реализуют
эти
различные
Класс, который использует алгоритм (Context), включает абстрактный класс (Stragegy), обладающий абстрактным методом, определяющим способ вызова алгоритма. Каждый производный класс реализует один требуемый вариант алгоритма.
Замечание. Метод вызова алгоритма не может быть абстрактным, если требуется реализовать некоторое поведение, принимаемое по умолчанию.
Замечание. В исходном варианте шаблона Strategy ответственность за выбор конкретной реализации возлагается на объект"клиент — т.е. на контекст шаблона Strategy
Глава 14. Шаблон Strategy
207
ÐÈÑ. 14.6. Стандартное упрощенное представление шаблона Strategy
Дополнительные замечания о шаблоне Strategy Однажды, когда на занятиях по изучению шаблонов проектирования обсуждался пример с системой электронной торговли, ктото в аудитории задал мне вопрос: "А знаете ли вы, что в Англии люди по достижении определенного возраста не платят налог при покупке продовольственных товаров?". Эта информация была для меня но востью, и вполне естественно, что интерфейс объекта CalcTax не позволял обраба тывать подобные ситуации. Существует по меньшей мере три способа решения дан ной проблемы. 1. Передавать сведения о возрасте покупателя от объекта класса Customer объек ту класса CalcTax и использовать их по мере необходимости. 2. Выбрать более общий подход: передавать объекту класса CalcTax весь объект класса Customer в целом и опрашивать его в случае необходимости. 3. Еще повысить уровень обобщения, передавая объекту класса CalcTax ссылку на объект класса SalesOrder (т.е. его указатель this), и позволить объекту класса CalcTax опрашивать его. Хотя для обработки подобной ситуации обязательно потребуется изменить классы SalesOrder и CalcTax, совершенно очевидно, как это можно сделать. Маловероятно, что выполнение требуемых действий вызовет появление в системе какихлибо проблем. Формально шаблон Strategy представляет собой средство инкапсуляции алгорит мов. Однако на практике он может использоваться для инкапсуляции правил факти чески любого вида. В общем случае, если на этапе анализа требований становится из вестно о существовании различных бизнесправил, применяемых в различных ситуа циях, обязательно следует рассмотреть возможность применения шаблона Strategy, эффективно обрабатывающего вариации такого рода. Шаблон Strategy требует, чтобы алгоритмы (бизнесправила) были инкапсулиро ваны вне класса, который их использует (Context). Это означает, что информация, необходимая для выполнения этих алгоритмов, должна быть передана или получена какимто иным способом.
208
Часть V. Обработка вариаций с применением шаблонов проектирования
Единственный серьезный недостаток, который я обнаружил в шаблоне Strategy, — это большое количество дополнительных классов, которые потребуется создавать. В случае, когда овчинка стоит выделки, можно предложить несколько рекомендаций по уменьшению количества требуемой работы, если имеется контроль над всеми стратегиями. В подобных случаях при использовании языка C++ можно создать файл заголовка абстрактной стратегии, содержащий имена всех файлов заголовков кон кретных классов стратегий. Кроме того, создается cppфайл абстрактной стратегии, содержащий программный код всех конкретных стратегий. При использовании языка Java в классе абстрактной стратегии создаются внутренние классы, содержащие код конкретных стратегий. Однако этого не следует делать, если отсутствует возможность контроля над всеми стратегиями — т.е. если некоторые алгоритмы будут реализовать другие программисты.
Резюме Шаблон Strategy — это способ определить семейство алгоритмов. Концептуально все эти алгоритмы решают одну и ту же задачу, но различаются между собой способом ее решения. Мы рассмотрели пример, в котором используется семейство алгоритмов начисле ния налогов. В системе поддержки международной электронной розничной торговли необходимо использовать различные алгоритмы начисления налогов для покупателей из различных стран. Шаблон Strategy позволяет инкапсулировать эти правила в одном абстрактном классе, от которого производится требуемое семейство конкретных классов. За счет определения различных вариантов выполнения алгоритма как производ ных от одного абстрактного класса главный модуль (в нашем примере это класс SalesOrder) сможет не принимать во внимание, какую именно версию алгоритма он фактически использует. Такой подход упрощает подключение новых вариантов алго ритма, но, одновременно, требует некоторых мер по управлению ими. Об этом мы поговорим позже, в главе 20, Матрица анализа.
Глава 15
Шаблон Decorator
Введение В этой главе мы продолжим обсуждение нового примера — приложения поддерж ки электронной розничной торговли, начатое в главе 14, Шаблон Strategy. Здесь мы выполним следующее. •
Сформулируем новое требование к создаваемой системе, связанное с добавле нием заголовка и нижнего колонтитула к выводимой на печать накладной.
•
Убедимся, что использование шаблона Decorator позволяет удовлетворить новые требования с необходимой гибкостью.
•
Рассмотрим, как шаблон Decorator может использоваться для управления вводомвыводом (особенно при работе с языком Java).
•
Проанализируем ключевые особенности шаблона Decorator.
•
Познакомимся с моим опытом применения шаблона Decorator на практике.
Несколько дополнительных деталей На рис. 14.2 представлена исходная структура нашего учебного проекта. На рис. 15.1 эта структура изображена более подробно. Здесь показано, что объект класса SalesOrder для вывода на печать накладной использует объект класса SalesTicket. В главе 14 мы решили, что для начисления налога на поступивший заказ объект класса SalesOrder будет использовать объект класса CalcTax. Для выполнения опе раций печати объект класса SalesOrder будет обращаться к объекту класса SalesTicket, предназначенному для вывода на печать накладной. В результате полу чилась прекрасная и продуманная схема приложения. Предположим, что в процессе создания приложения выдвигается новое требова ние, заключающееся в том, что в накладную, выводимую классом SalesTicket, необ ходимо добавить заголовок и нижний колонтитул. Как можно реализовать это новое требование? Если предполагается, что создавае мая система будет использоваться только одной компанией, то вопрос решается очень легко — достаточно добавить код вывода заголовка и колонтитула в класс SalesTicket, как показано на рис. 15.2. В этом варианте решения функции управления печатью возложены непосредст венно на класс SalesTicket и реализованы в виде флажков, указывающих, следует ли печатать заголовок и/или нижний колонтитул.
210
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 15.1. Класс SalesOrder использует класс SalesTicket
ÐÈÑ. 15.2. Класс SalesOrder использует класс SalesTicket с различными параметрами
Глава 15. Шаблон Decorator
211
Такое решение работает вполне удовлетворительно, если не приходится иметь де ло со слишком большим количеством параметров или если заголовки накладных не изменяются. Если же существует множество различных типов заголовков и нижних колонтиту лов, и при печати каждой накладной используется только один из возможных вариан тов их сочетания, целесообразно перейти к схеме с одним шаблоном Strategy для за головка и другим шаблоном Strategy для нижних колонтитулов. А как поступить, если одновременно потребуется напечатать несколько типов за головка и/или нижнего колонтитула? Или если порядок вывода заголовков и/или нижних колонтитулов будет изменяться? Очевидно, что количество возможных ком бинаций в этом случае будет возрастать чрезвычайно быстро. В ситуациях, подобных данной, пригодится шаблон Decorator (декоратор). Вместо определения новых методов, он предлагает управлять добавленной функционально стью посредством формирования цепочки связанных функций, упорядоченных в требуемой последовательности. Шаблон Decorator отделяет динамическое построе ние такой цепочки от объектаклиента, который ее использует (в нашем случае им яв ляется объект класса SalesOrder).
Шаблон Decorator Согласно определению "банды четырех" назначение шаблона Decorator состоит в следующем. Динамическое подключение дополнительных обязательств к объекту. Шаблон Decorator предоставляет гибкую альтернативу практике определения подклассов с 1 целью расширения функциональности. Шаблон Decorator функционирует посредством создания цепочки объектов, кото рая начинается с объектовдекораторов, отвечающих за выполнение новых функций, и заканчивается исходным объектом (рис. 15.3).
ÐÈÑ. 15.3. Цепочка объектов в шаблоне Decorator
На рис. 15.4 представлена диаграмма классов шаблона Decorator, соответствующая цепочке объектов, показанных на рис. 15.3. Каждая цепочка начинается с объекта класса Component (это может быть ConcreteComponent или Decorator). За каждым объектом класса Decorator следует либо другой объект Decorator, либо исходный объект класса ConcreteComponent. Заканчивается цепочка всегда объектом класса ConcreteComponent. 1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 315.
212
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 15.4. Диаграмма классов шаблона Decorator
Например, на рис. 15.4 объект класса ConcreteDecoratorB выполняет собствен ный метод Operation(), а затем вызывает метод Operation() объекта класса Decorator. Этот вызов объекта ConcreteDecoratorB завершает выполнение мето да Operation() класса Component.
Применение шаблона Decorator к нашему примеру В нашем учебном примере классом ConcreteComponent является класс SalesTicket, а конкретными представителями классадекоратора служат заголовки и нижние колонтитулы. На рис. 15.5 показано, как шаблон Decorator может быть ис пользован в проектируемой системе. Диаграмма объектов на рис. 15.6 демонстрирует применение шаблона Decorator в случае печати документа с одним заголовком и одним нижним колонтитулом. Каждый объектдекоратор как бы "упаковывает" исходный объект в новую функ цию. Отдельный объектдекоратор выполняет свою добавленную функциональность либо перед выполнением функциидекоратора (заголовок), либо после ее выполне ния (нижний колонтитул). Проще всего понять, как все это работает, если обратиться к программному коду с конкретным примером и внимательно проанализировать его (листинг 15.1).
Глава 15. Шаблон Decorator
ÐÈÑ. 15.5. Формирование документа с заголовком и нижним колонтитулом
ÐÈÑ. 15.6. Пример диаграммы объектов шаблона Decorator
Листинг 15.1. Реализация шаблона Decorator на языке Java class SalesTicket extends Component { public void prtTicket () { // Здесь размещается код, выполняющий печать накладной } abstract class Decorator extends Component { private Component myComp; public Decorator (Component myC) { myComp = myC; } public void prtTicket () {
213
214
Часть V. Обработка вариаций с применением шаблонов проектирования
if (myComp != null) myComp.prtTicket(); } } class Header1 extends Decorator { public void prtTicket () { // Здесь размещается код, выполняющий печать Заголовка 1 super.prtTicket(); } } class Header2 extends Decorator { public void prtTicket () { // Здесь размещается код, выполняющий печать Заголовка 2 super.prtTicket(); } } class Footer1 extends Decorator { public void prtReport () { super.prtTicket(); // Здесь размещается код, выполняющий печать Колонтитула 1 } } class Footer2 extends Decorator { public void prtReport () { super.prtTicket(); // Здесь размещается код, выполняющий печать Колонтитула 2 } } class SalesOrder { void prtTicket () { Component myST; // Получение цепочки, состоящей из нескольких объектов-декораторов // и объекта SalesTicket, созданной другим объектом, знающим, // как это делается. При необходимости это может выполняться в // конструкторе, чтобы избежать выполнения при каждом вызове. myST = Configuration.getSalesTicket() // Печать накладной с требуемыми заголовками и колонтитулами myST.prtTicket(); } }
Предположим, что накладная должна иметь следующий вид: Заголовок 1 Текст накладной Колонтитул 1
Тогда метод Configuration.getSalesTicket возвратит следующее: return(new Header1(new Footer1(new SalesTicket())); Это означает, что сначала создается объект SalesTicket, затем объект Footer1 и, наконец, объект Header1. Теперь предположим, что накладная должна иметь такой вид:
Глава 15. Шаблон Decorator
215
Заголовок 1 Заголовок 2 Текст накладной Колонтитул 1
В этом случае метод Configuration.getSalesTicket должен возвращать сле дующее: return(new Header1(new Header2 (new Footer1(new SalesTicket()))); В результате, первым создается объект SalesTicket, затем объекты Footer1 и Header2 и, наконец, объект Header1. Таким образом, шаблон Decorator позволяет разделить проблему на две подзадачи. •
Каким образом реализовать объекты, включающие новые функциональные возможности.
•
Как организовать объекты для выполнения их функциональности в каждом конкретном случае.
Подобный подход позволяет отделить реализацию объектовдекораторов от объ екта, определяющего способ их использования. В результате связность в системе воз растает, поскольку каждый из объектовдекораторов отвечает только за выполнение той функции, которую он добавляет, и может не заботиться о том, каким образом он сам будет включен в цепочку объектов.
Еще один пример: организация вводавывода информации Очень часто шаблон Decorator используется для организации потокового ввода вывода данных. Прежде чем обсуждать возможности использования данного шаблона для указанной цели, целесообразно вспомнить некоторые особенности потокового вво давывода данных. Мы ограничимся рассмотрением операции ввода, поскольку вывод данных организуется аналогично (при ясном понимании процесса ввода не составит труда разобраться с тем, как он работает в обратном направлении). Любой заданный входной поток имеет строго один источник, но может предусматривать произвольное количество действий (включая нуль), которые должны быть выполнены в этом входном потоке. Например, чтение данных может выполняться из следующих источников: •
файла;
•
сетевого соединения с последующей расшифровкой поступающего потока;
•
файла с последующей разархивацией поступающих данных;
•
строковой переменной;
•
файла с последующей разархивацией и расшифровкой данных.
В зависимости от того, какие данные были посланы (или сохранены), возможна любая комбинация действий. Перефразируем только что сказанное: любой источник данных может быть "декорирован" произвольной комбинацией действий. Некоторые
216
Часть V. Обработка вариаций с применением шаблонов проектирования
из возможных типов источников данных и действий по их обработке для входящего потока представлены в табл. 15.1. Таблица 15.1. Источники данных и действия по их обработке для входного потока Источник
Обработка
Строковая переменная
Буферированный ввод
Файл
Проверка контрольной суммы
Сетевое соединение (TCP/IP)
Разархивирование
Последовательный порт
Расшифровка данных (любым способом)
Параллельный порт
Избирательные фильтры (любым способом)
Клавиатура
Разработчики, использующие объектноориентированные языки программирова ния, могут воспользоваться тем преимуществом, что объекты источников и реализа ции операций (действий) могут быть порождены от общего абстрактного класса. Ка ждому объекту реализации операции можно назначить источник или предшествую щий объект операции непосредственно в его конструкторе. Цепочка действий в этом случае строится в процессе создания экземпляров входящих в нее объектов (каждому объекту передается ссылка на последующий объект). Классы объектов источников яв ляются производными от абстрактного класса ConcreteComponent (см. рис. 15.4), в то время как классы объектов операций играют роль объектовдекораторов. Обратите внимание на то, что класс ConcreteComponent в данном случае используется иначе, так как здесь он является абстрактным. Например, чтобы реализовать поведение "читать данные из файла, разархивиро вать, а затем расшифровать их", необходимо выполнить следующее. 1. Построить цепочку из объектовдекораторов, выполнив следующие действия: а) создать экземпляр объекта чтения данных из файла; б) передать ссылку на него в конструктор объекта, выполняющего разархива цию данных; в) передать ссылку на объект разархивации в конструктор объекта, выпол няющего расшифровку данных. 2. Считать, разархивировать и расшифровать данные. Все требуемые операции выполняются для объектаклиента совершенно прозрачно. Объектклиент про сто знает, что существует некий объект, выполняющий требуемую обработку входного потока данных, с которым он и взаимодействует. Если объектуклиенту потребуется считать входной поток данных из другого источника, строится новая цепочка — посредством создания экземпляра объекта требуемого источника с подключением таких же объектов реализации необходи мых операций.
Глава 15. Шаблон Decorator
217
Как разобраться с множеством потоков языка Java Язык Java печально известен запутанным множеством разнообразных входных потоков и связанных с ними классов. Понять назначение этих классов гораздо проще в контексте шаблона Decorator. Все классы, которые являются производными непосредственно от класса java.io.InputStream (ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, SequenceInputStream и StringBufferInputStream), выполняют роль декорируемых объектов. Все классы"декораторы являются производными от класса FilterInputStream (как напрямую, так и косвенно). Обращение к шаблону Decorator позволяет понять, почему язык Java требует объединения подобных объектов в цепочку при создании их экземпляров. Такой подход позволяет программистам создавать любое количество комбинаций из различных возможных функций обработки.
Дополнительные замечания о шаблоне Decorator Чтобы воспользоваться всей мощью шаблона Decorator, необходимо полностью отделить создание цепочки объектов от объектаклиента, использующего ее. Для дос тижения этой цели чаще всего применяются объектыфабрики классов, создающие цепочку экземпляров объектов на основании некоторой переданной им информации о требуемой конфигурации.
Основные характеристики шаблона Decorator Назначение
Динамическое подключение к объекту дополнительных обязательств
Задача
Объект, который предполагается использовать, выполняет основные функции. Однако может потребоваться добавить к нему некоторую дополнительную функциональность, которая будет выполняться до или после основной функциональности объекта.
Замечание. Базовые классы в языке Java широко используют шаблон Decorator для организации обработки операций ввода"вывода Способ решения
Позволяет расширить функциональность объекта без определения соответствующих подклассов
Участники
Класс ConcreteComponent — это тот класс, в который с помощью шаблона Decorator добавляется новая функциональность. В некоторых случаях базовая функциональность предоставляется классами, производными от класса ConcreteComponent. В подобных случаях класс ConcreteComponent является уже не конкретным, а абстрактным. Абстрактный класс Component определяет интерфейс для использования всех этих классов
Следствия
Добавляемая функциональность реализуется в небольших объектах. Преимущество состоит в возможности динамически добавлять эту функциональность до или после основной функциональности объекта ConcreteComponent.
Замечание. Хотя объект"декоратор может добавлять свою функциональность до или после функциональности исходного объекта, цепочка создаваемых объектов всегда должна заканчиваться объектом класса ConcreteComponent Реализация
Создается абстрактный класс, представляющий как исходный класс, так и новые, добавляемые в класс функции. В классах"декораторах новые функции вызываются в требуемой последовательности — до или после вызова последующего объекта
218
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 15.7. Стандартное упрощенное представление шаблона Decorator
Я использовал шаблон Decorator для того, чтобы "упаковывать" тестируемый объ ект в классы проверки пред и постусловий. Этот подход дал очень хорошие результа ты. В процессе тестирования первый объект в цепочке выполняет проверку некото рого набора предварительных условий, а затем обращается к следующему объекту в цепочке. Сразу после вызова тестируемого объекта аналогичный объект выполняет проверку любого набора постусловий. Если при каждом прогоне предусматривается выполнять различные тесты, следует описать каждый тест в разных объектах декораторах, а затем объединить их в цепочку в соответствии с тем набором тестов, который планируется выполнить.
Резюме Шаблон Decorator предлагает эффективный способ динамического добавления дополнительной функциональности к уже существующей. Для этой цели он преду сматривает построение цепочки объектов, которая в совокупности реализует требуе мое поведение. Первый объект этой цепочки вызывается объектомклиентом, кото рый ничего не знает о процедуре создания вызываемой им цепочки. За счет отделе ния процедуры создания цепочки от процедуры ее использования удается оградить объектклиент от какихлибо изменений в связи с появлением новых требований по расширению функциональности.
Глава 15. Шаблон Decorator
Приложение. Примеры программного кода на языке C++ Листинг 15.2. Фрагмент кода. Реализация шаблона Decorator class SalesTicket : public Component { public: void prtTicket(); } SalesTicket::prtTicket() { // Здесь размещается код, выполняющий печать накладной } class Decorator : public Component { public: virtual void prtTicket(); Decorator( Component *myC); private: Component *myComp; } Decorator::Decorator( Component *myC) { myComp= myC; } Decorator::prtTicket() { if (myComp != 0) myComp -> prtTicket(); } class Header1 : public Decorator { public: void prtTicket(); } Header1::prtTicket () { // Здесь размещается код, выполняющий печать Заголовка 1 Decorator::prtTicket(); } class Header2 : public Decorator { public: void prtTicket(); } Header2::prtTicket () { // Здесь размещается код, выполняющий печать Заголовка 2 Decorator::prtTicket(); } class Footer1 : public Decorator { public: void prtTicket(); } Footer1::prtTicket () { Decorator::prtTicket(); // Здесь размещается код, выполняющий печать Колонтитула 1 } class Footer2 : public Decorator { public: void prtTicket(); } Footer2::prtTicket () { Decorator::prtTicket();
219
220
Часть V. Обработка вариаций с применением шаблонов проектирования
// Здесь размещается код, выполняющий печать Колонтитула 2 } SalesOrder::prtTicket () { Component *myST; // Получение цепочки, состоящей из нескольких объектов-декораторов // и объекта SalesTicket, созданной другим объектом, знающим, // как это делается. При необходимости это может выполняться в // конструкторе, чтобы избежать выполнения при каждом вызове. myST = Configuration.getSalesTicket() // Печать накладной с требуемыми заголовками и колонтитулами myST -> prtTicket(); }
Глава 16
Шаблоны Singleton и DoubleChecked Locking
Введение В этой главе мы продолжим обсуждение нашего учебного примера — приложения поддержки электронной розничной торговли, начатое в главах 14, Шаблон Strategy, и 15, Шаблон Decorator. Здесь мы выполним следующее. •
Познакомимся с шаблоном Singleton.0
•
Рассмотрим ключевые особенности шаблона Singleton.
•
Обсудим один из вариантов шаблона Singleton, называемый шаблоном Double Checked Locking.
•
Познакомимся с моим опытом применения шаблона Singleton на практике.
Шаблоны Singleton и DoubleChecked Locking очень просты и широко известны. Оба эти шаблона используются для получения гарантий, что будет создан только один объект определенного класса. Различие между этими двумя шаблонами состоит в том, что шаблон Singleton используется в однопоточных приложениях, а шаблон Double 1 Checked Locking предназначен для использования в многопоточной среде.
Назначение шаблона проектирования Singleton Согласно определению "банды четырех" назначение шаблона Singleton (одиночка) состоит в следующем. Получение гарантий, что будет создан только один экземпляр объекта данного 2 класса, и предоставление глобальной точки доступа к нему. Шаблон Singleton выполняет свои функции, определяя специальный метод, пред назначенный для создания экземпляра требуемого объекта.
1 Если вам неизвестно, что такое многопоточное приложение, не волнуйтесь — сейчас доста точно будет сконцентрировать все внимание на шаблоне Singleton. 2 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 127.
222
Часть V. Обработка вариаций с применением шаблонов проектирования
•
Когда этот метод вызывается, он проверяет, был ли экземпляр требуемого объекта создан ранее. Если это так, то метод просто возвращает ссылку на этот объект. Если же объект еще не существует, метод создает требуемый экземпляр и возвращает ссылку на вновь созданный объект.
•
Для получения гарантий, что существует только один способ создания экзе мпляров объектов данного класса, тип конструктора этого класса следует определить как защищенный или закрытый.
Применение шаблона Singleton к нашему примеру В главе 14 правила начисления налогов были инкапсулированы в объекты шаблона Strategy. Для этой цели были определены конкретные классы, производные от абст рактного класса CalcTax, — по одному для каждого возможного варианта начисления налога. А это означает, что одни и те же экземпляры объектов будут многократно ис пользоваться, раз за разом вызываясь различными объектамиклиентами. Из соображений производительности системы нежелательно при каждом обра щении заново создавать требуемый экземпляр объекта и уничтожать его после завер шения работы с ним. В то же время малоэффективным будет и решение создать все возможные типы объектов начисления налогов непосредственно при запуске систе мы, особенно если количество вариантов налогообложения достаточно велико. (Не забывайте, что приложение может содержать большое количество и других стра тегий.) Оптимальным решением будет создавать экземпляр объекта каждого типа только тогда, когда это действительно необходимо, и при условии, что может быть создан только один такой экземпляр. Проблема состоит в том, что желательно не создавать дополнительный объект, который будет хранить информацию о тех объектах, экземпляры которых уже были созданы. Гораздо лучше сделать сами объекты (представляющие отдельные страте гии) ответственными за соблюдение правила создания только одного их экземпляра. В этом и состоит назначение шаблона Singleton. Он позволяет создать экземпляр объекта указанного класса только один раз, не запрашивая у объектаклиента сведе ний о том, существует уже требуемый экземпляр объекта или нет. Пример реализации шаблона Singleton приведен в листинге 16.1. В этом примере определяется метод (getInstance), который будет создавать только один экземпляр объекта класса USTax. Шаблон Singleton предохраняет систему от того, чтобы кто либо еще мог создать экземпляр объекта класса USTax, просто объявляя его конструк тор закрытым, а это означает, что никакой другой объект не сможет получить доступ к этому конструктору. Листинг 16.1. Реализация шаблона Singleton на языке Java class USTax { private static USTax instance; private USTax(): public static USTax getInstance() { if (instance == null) instance = new USTax(); return instance; } }
Глава 16. Шаблоны Singleton и DoubleChecked Locking
223
Основные характеристики шаблона Singleton Назначение
Необходимо иметь в системе только один экземпляр объекта заданного класса, не используя никаких глобальных объектов, предназначенных для контроля над созданием этих экземпляров
Задача
Нескольким различным объектам"клиентам требуется обращаться к одному и тому же объекту и иметь гарантии, что объектов этого типа в системе будет не более одного
Способ решения
Гарантированное однократное выполнение метода"конструктора
Участники
Объекты"клиенты получают доступ к методу getInstance() класса Singleton исключительно через его метод instance()
Следствия
Объекты"клиенты могут не интересоваться тем, существует ли уже экземпляр объекта класса Singleton. Контроль над созданием своих экземпляров возложен на сам класс Singleton
Реализация
•
В класс добавляется закрытая статическая переменная"член класса, содержащая ссылку на соответствующий объект (ее начальное значение NULL)
•
В класс добавляется открытый статический метод, который создает экземпляр объекта данного класса, если значение упомянутой выше переменной"члена класса равно NULL (и помещает в нее указатель на созданный объект). Метод возвращает значение этого экземпляра класса
•
Статус конструктора класса устанавливается защищенным или закрытым, так что никто не сможет самостоятельно создать экземпляр объекта данного класса в обход его механизма статического конструктора
ÐÈÑ. 16.1. Стандартный упрощенный вид шаблона Singleton
Возможный вариант — шаблон DoubleChecked Locking Этот шаблон применяется только в случае создания многопоточных приложений. Читатель, не интересующийся этой темой, может безболезненно пропустить этот раздел. Изложение материала предполагает, что читатель имеет базовое представле ние об особенностях многопоточных приложений, в том числе о синхронизации. В многопоточной среде применение шаблона Singleton связано с определенными проблемами.
224
Часть V. Обработка вариаций с применением шаблонов проектирования
Предположим, что два вызова метода getInstance сделаны точно в одно и то же время. Это может закончиться очень плохо. Рассмотрим подробно то, что может произойти в подобной ситуации. 1. Первый поток проверяет, был ли уже создан требуемый экземпляр объекта. Если нет, то управление передается той части программного кода, которая от вечает за создание экземпляров объектов этого класса. 2. Однако прежде чем создание первого экземпляра объекта будет завершено, вто рой поток также выполняет проверку значения статического указателя на равен ство значению NULL. Поскольку первый поток еще не закончил создание экземп ляра объекта, значение статической переменной попрежнему будет равно NULL. Поэтому второй поток также передаст управление той части программного кода, которая отвечает за создание экземпляров объектов этого класса. 3. В результате оба потока создадут по одному экземпляру объекта класса Singleton, и в системе окажется два подобных объекта. Является ли это проблемой? Может быть, да, а может быть, и нет. •
Если класс Singleton не хранит сведений о своем состоянии, эта ситуация может не вызывать в системе какихлибо проблем.
•
В языке Java проблема будет состоять лишь в том, что несколько лишних байт памяти используется бесполезно.
•
В языке C++ эта ситуация может вызвать утечку памяти, поскольку при завершении программа будет удалять только один объект класса Singleton, тогда как создано их было два.
•
Если объект класса Singleton хранит какиелибо сведения о своем состоянии, могут иметь место очень коварные и трудноуловимые ошибки. Например: − если объект устанавливает соединение, фактически будет установлено два соединения (по одному для каждого из созданных объектов); − если в объекте используется счетчик, в системе будут существовать два разных счетчика.
Обнаружить подобные ошибки очень трудно. Вопервых, повторное создание объ ектов маловероятно и обычно не имеет места. Вовторых, может быть совершенно непонятно, что же происходит со счетчиками, когда одни объектыклиенты будут об ращаться к одному объекту класса Singleton, а остальные — к другому. На первый взгляд может показаться, что все, что необходимо сделать, — это син хронизировать выполнение проверки, был ли уже создан объект класса Singleton. Единственная проблема заключается в том, что подобная синхронизация может вы звать появление в системе узкого места, резко замедляющего работу программы, по скольку все потоки вынуждены будут ждать в общей очереди права на проверку суще ствования объекта класса Singleton. Можно предположить, что достаточно будет поместить код синхронизации в ветвь после успешного выполнения проверки if (instance == null). Однако это реше ние не будет работать, поскольку синхронизация потоков уже после того, как оба они убедились в наличии значения NULL в статической переменной, опятьтаки приведет
Глава 16. Шаблоны Singleton и DoubleChecked Locking
225
к созданию двух объектов класса Singleton, но создаваться они будут уже строго по очередно. Решение данной проблемы состоит в том, чтобы выполнить синхронизацию после проверки статической переменной на NULL, а затем осуществить повторную проверку, чтобы удостовериться, что экземпляр объекта данного класса все еще не создан. При мер соответствующего кода приведен в листинге 16.2. Данный метод получил название 3 doublechecked locking (блокировка с двойной проверкой) . Идея состоит в том, чтобы из бежать ненужной блокировки. В данном случае синхронизация выполняется после пер вой проверки, так что выполнение ее не приведет к появлению в системе узкого места. Основные достоинства метода блокировки с двойной проверкой состоят в следующем. •
Удается избежать излишней блокировки процессов за счет помещения запроса на создание нового экземпляра объекта в процедуру проверки другого условия.
•
Обеспечивается поддержка многопоточной среды.
Листинг 16.2. Фрагмент реализации шаблона DoubleChecked Locking на языке Java class USTax extends CalcTax { private USTax instance; private USTax () { } private synchronized static void doSync () { // Это только для синхронизации } public USTax getInstance() { if (instance == null) { USTax.doSync(); if (instance == null) instance= new USTax(); } return instance; } }
Дополнительные замечания о шаблонах Singleton и DoubleChecked Locking Если известно, что объект обязательно потребуется, и соображения повышения производительности не заставляют отложить создание экземпляра объекта до тех пор, пока он действительно понадобится, проще всего определить статическую пере менную, содержащую ссылку на этот объект. В многопоточных приложениях класс Singleton, как правило, должен быть оп ределен с учетом проблем безопасности работы отдельных потоков (поскольку один и тот же объект может быть доступен многим потокам). Это означает, что класс не дол
3 Martin R., Riehle D., Buschmann F. Pattern Language of Program Design Reading, MA: AddisonWesley, 1998, с. 363.
226
Часть V. Обработка вариаций с применением шаблонов проектирования
жен включать какихлибо данныхчленов класса и использовать только такие пере менные, область видимости которых не выходит за рамки метода.
Резюме Шаблоны Singleton и DoubleChecked Locking обычно используются в тех случаях, когда необходимо иметь гарантии, что в системе будет создан только один экземпляр объекта заданного класса. Шаблон Singleton используется в однопоточных приложе ниях, а шаблон DoubleChecked Locking предназначен для использования в многопо точном окружении.
Приложение. Примеры программного кода на языке C++ Листинг 16.3. Реализация шаблона Singleton Class USTax { public: static USTax* getInstance(); private: USTax(); static USTax* instance; } USTax::USTax () { instance= 0; } USTax* USTax::getInstance () { if (instance== 0) { instance= new USTax; } return instance; }
Листинг 16.4. Фрагмент кода реализации шаблона DoubleChecked Locking class USTax : public CalcTax { public: static USTax* getInstance(); private: USTax(); static USTax* instance; }; USTax::USTax () { instance= 0; } USTax* USTax::getInstance () { if (instance== 0) { // здесь выполняется синхронизация if (instance== 0) { } } return instance; }
Глава 17
Шаблон Observer Введение В этой главе мы продолжим обсуждение нашего учебного примера — приложения поддержки электронной розничной торговли, начатое в главах 14–16. Здесь мы выполним следующее. •
Рассмотрим схему классификации шаблонов.
•
Познакомимся с шаблоном Observer в процессе обсуждения дополнительных требований, предъявленных к нашему учебному примеру.
•
Применим данный шаблон в системе поддержки электронной торговли.
•
Обсудим характеристики этого шаблона.
•
Рассмотрим ключевые особенности шаблона Observer.
•
Познакомимся с моим опытом применения шаблона Observer на практике.
Категории шаблонов Количество уже существующих шаблонов достаточно велико. Поэтому для их упо рядочения "банда четырех" предложила разделить все шаблоны на три категории, как 1 показано в табл. 17.1. Таблица 17.1. Категории шаблонов Категория
Назначение
Примеры в этой книге
Для чего используются
Структурные
Связывают между со бой существующие объекты
∑ Facade (глава 6)
Приведение интерфейсов
∑ Adapter (глава 7) ∑ Bridge (глава 9) ∑ Decorator (глава 15)
Поведенческие
Определяют способы проявления гибкого (изменяющегося) поведения
∑ Strategy (глава 14)
Связывание реа лизации с абст ракцией Сокрытие вариаций
1 Gamma, E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 10
228
Часть V. Обработка вариаций с применением шаблонов проектирования
Окончание таблицы Категория
Назначение
Примеры в этой книге
Для чего используются
Создающие
Управляют созданием экземпляров объектов
∑ Abstract Factory (глава 10)
Создание экзем пляров объектов
∑ Singleton (глава 16) ∑ DoubleChecked Locking (глава 16) ∑ Factory Method (глава 19)
Впервые приступив к изучению шаблонов проектирования, я очень удивился тому, что шаблоны Bridge и Decorator были отнесены к категории структурных, а не пове денческих шаблонов. На первый взгляд, не вызывало сомнения, что они используют ся для реализации различных типов поведения. Но, как выяснилось впоследствии, я просто неправильно понял систему классификации "банды четырех". Структурные шаблоны предназначены для связывания между собой уже существующих функций. Работая с шаблоном Bridge, мы, как правило, начинаем с функций абстракции и реа лизации, а затем связываем их между собой. Шаблон Decorator имеет дело с уже суще ствующим функциональным классом и предназначен для "декорирования" его допол нительными функциями. Я пришел к заключению, что имеет смысл выделить еще одну, четвертую, катего рию шаблонов. К ней я отношу шаблоны, основное назначение которых — уменьшить связанность объектов друг с другом, и, следовательно, повысить возможности мас штабируемости системы и ее гибкость. Я назвал эту категорию развязывающими шабло нами. Поскольку по классификации "банды четырех" большинство подобных шабло нов принадлежат к категории поведенческих, их можно было бы считать подмноже ством этой категории. Я решил выделить их в четвертую категорию только потому, что намеревался в этой книге отразить мой собственный взгляд на шаблоны проекти рования, сосредоточив внимание на мотивах их определения — в данном случае это развязывание объектов в системе. Я не буду более задерживаться на обсуждении различных аспектов классификации шаблонов, поскольку это требует достаточно глубокого понимания принципов их по строения и работы. В этой главе мы познакомимся с шаблоном Observer, который можно считать луч шим примером развязывающего шаблона среди всех прочих. По классификации "банды четырех" шаблон Observer относится к группе поведенческих.
Предъявление новых требований к системе электронной торговли Предположим, что при разработке нашего приложения поддержки электронной торговли было выдвинуто очередное новое требование. Выяснилось, что необходимо выполнять перечисленные ниже действия всякий раз, когда к системе подключается новый клиент.
Глава 17. Шаблон Observer
•
Послать клиенту вежливое приглашение по электронной почте.
•
Проверить адрес клиента по списку почтовых отделений.
229
Все ли требования теперь учтены? Возможны ли какиелибо изменения в будущем? Если существует обоснованная уверенность в том, что теперь все требования к системе уже известны, можно решить новую проблему, просто внедрив программный код организации отправки приглашения и проверки адреса в класс Customer, как по казано на рис. 17.1.
ÐÈÑ. 17.1. Жесткое кодирование поведения объекта
Например, можно расширить метод класса Customer, который вносит сведения о новом клиенте в базу данных, добавив в него выполнение запросов к объектам, вы полняющим создание и отправку пригласительного письма и проверку адреса по спи ску почтовых отделений. В этом случае обязательства будут распределены между классами так, как показано в табл. 17.2. Таблица 17.2. Исходное распределение обязательств между классами Класс
Обязательства
Customer
При подключении к системе нового клиента этот объект об ращается к другим объектам с требованием выполнить соот ветствующие действия
WelcomeLetter
Генерирует приветственное письмо клиентам и сообщает им, что сведения о них помещены в систему
AddrVerification
Проверяет адрес любого клиента, который будет ему указан
Метод жесткого кодирования поведения прекрасно работает, но только первое время. К сожалению, требования к системе с течением времени всегда изменяются. Можно быть абсолютно уверенным в том, что появление новых требований не заста вит себя долго ждать, а это потребует внесения изменений в поведение объектов класса Customer. Например, может потребоваться организовать отправку пригласи тельных писем различным компаниям, при этом придется создавать отдельный объ ект класса Customer для каждой компании. Очевидно, что должно существовать луч шее решение.
230
Часть V. Обработка вариаций с применением шаблонов проектирования
Шаблон Observer Согласно определению "банды четырех" назначение шаблона Observer (наблюдатель) состоит в следующем. Определение зависимости типа "один ко многим" между объектами, выполненное таким образом, что при изменении состояния одного объекта все зависимые объ 2 екты были бы уведомлены об этом и обновлены автоматически. Весьма распространена ситуация, при которой существует набор объектов, которые должны получать уведомление всякий раз, когда происходит некоторое событие. Необ ходимо, чтобы это уведомление выполнялось автоматически. Кроме того, желательно обойтись без внесения изменений в оповещающий объект при каждом изменении в на боре объектов, получающих уведомления. (В противном случае ситуация походила бы на требование внесения изменений в радиопередатчик всякий раз, когда в городе появ ляется новый автомобиль с радиоприемником.) Таким образом, мы должны снизить уровень связанности между оповещающим и извещаемыми объектами. Шаблон Observer относится к наиболее широко используемым шаблонам. Иначе 3 его иногда называют Dependents (зависимости) или PublishSubscribe (публикацияпод писка) Он является аналогом процесса извещения в технологии COM. В языке Java данный шаблон реализован с помощью интерфейса Observer и класса Observable (подробнее об этом речь пойдет ниже). В экспертных системах, использующих базы правил, шаблон Observer часто реализуется в демонах правил.
Применение шаблона Observer в системе электронной торговли Наш подход к решению проблемы состоит в том, чтобы найти то, что может быть изменяемым в системе, а затем попытаться инкапсулировать эти вариации. В нашем учебном примере изменяемым может быть следующее. •
Различные виды объектов. Существует список объектов, которые необходи мо уведомить об изменении состояния системы. Эти объекты, как правило, принадлежат к различным классам.
•
Различные интерфейсы. Поскольку извещаемые объекты принадлежат к раз личным классам, они, вероятнее всего, будут обладать различными интерфейсами.
Прежде всего, выявим все объекты, которые должны получать уведомления. Назо вем их наблюдателями (оbserver), так как они находятся в состоянии ожидания неко торого события, которое должно произойти. Желательно, чтобы все эти объекты имели одинаковый интерфейс. Если интер фейсы не будут одинаковыми, придется модифицировать наблюдаемый субъект — т.е. тот объект, который инициирует ожидаемое событие (например, объект класса Customer), чтобы он мог обращаться к объектамнаблюдателям различного типа.
2 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 293. 3 Там же, с. 293.
Глава 17. Шаблон Observer
231
Если все объектынаблюдатели будут одного типа, то субъект легко сможет уведо мить каждый из них. Чтобы достичь этой цели можно поступить следующим образом. •
В языке Java, вероятно, лучше всего использовать интерфейс (чтобы повысить гибкость или просто по необходимости).
•
В языке C++ можно использовать одиночное или множественное наследо вание — исходя из конкретных обстоятельств.
Обычно требуется, чтобы объектынаблюдатели знали, за кем они должны наблю дать, а субъект был бы освобожден от необходимости знать, какие именно объекты наблюдатели от него зависят. Чтобы достичь этого, необходимо предоставить каждо му из объектовнаблюдателей возможность зарегистрироваться у субъекта. Поскольку все объектынаблюдатели имеют один и тот же тип, в класссубъект необходимо доба вить два метода. •
Метод attach(Observer). Добавляет заданный объект класса Observer к списку объектовнаблюдателей данного субъекта.
•
Метод detach(Observer). Удаляет заданный объект класса Observer из списка объектовнаблюдателей данного субъекта.
Теперь, когда класс Subject может регистрировать объекты класса Observer, очень просто известить всех зарегистрировавшихся объектовнаблюдателей о насту плении ожидаемого события. Для этого в каждом конкретном типе класса Observer реализуется метод update (обновить). В классе Subject реализуется метод notify, который просматривает список зарегистрировавшихся объектовнаблюдателей и вы зывает метод update каждого из них. Метод update объектовнаблюдателей должен включать программный код обработки наступления ожидаемого события. Однако просто уведомить каждый объектнаблюдатель о наступлении события бу дет недостаточно. Объектунаблюдателю может понадобиться дополнительная ин формации о событии, а не просто извещение о том, что оно произошло. Поэтому в класссубъект следует добавить еще один или несколько методов, позволяющих объ ектамнаблюдателям получить ту информацию, в которой они нуждаются. Это реше ние представлено на рис. 17.2.
ÐÈÑ. 17.2. Реализация взаимодействия классов Customer и Observer
232
Часть V. Обработка вариаций с применением шаблонов проектирования
На рис. 17.2 классы взаимодействуют между собой следующим образом. 1. Объекты класса Оbserver подключаются к классу Customer при их инициали зации. Если объекту класса Оbserver необходима дополнительная информа ция от субъекта (объект класса Customer), он должен передать вызываемому объекту ссылку на свой метод update. 2. Каждый раз при добавлении нового объекта класса Customer, его метод notify вызывает все зарегистрированные объекты класса Оbserver. Каждый объект класса Observer вызывает метод getState (класс Customer) для получения информации о вновь добавленном клиенте — это необходимо ему, чтобы выяснить, какие действия следует предпринять. Замечание. Обычно для получения не обходимой информации требуется несколько методов. Обратите внимание на то, что в нашем примере для добавления и удаления опо вещаемых объектов в списке используются статические методы. Причина в том, что объектынаблюдатели должны быть уведомлены обо всех новых клиентах (т.е. созда ваемых объектах класса Customer). Вместе с уведомлением наблюдателю передается ссылка на вновь созданный объект описания клиента. Код реализации описанного выше подхода представлен в листинге 17.1 (частично). Листинг 17.1. Реализация шаблона Observer на языке Java class Customer { static private Vector myObs; static { myObs= new Vector(); } static void attach(Observer o){ myObs.addElement(o); } static void detach(Observer o){ myObs.remove(o); } public String getState () { // Здесь могут вызываться другие методы, // предоставляющие требуемую информацию } public void notifyObs () { for (Enumeration e = myObs.elements(); e.hasMoreElements() ;) { ((Observer) e).update(this); } } } abstract class Observer { public Observer () { Customer.attach( this); } abstract public void update(Customer myCust); } class POVerification extends Observer { public AddrVerification () {
Глава 17. Шаблон Observer
233
super(); } public void update ( Customer myCust) { // Здесь выполняется проверка адресов. // Дополнительная информация о клиенте может быть // получена с помощью метода myCust } } class WelcomeLetter extends Observer { public WelcomeLetter () { super(); } public void update (Customer myCust) { // Здесь выполняется отправка приветственного письма. // Дополнительная информация о клиенте может быть // получена с помощью метода myCust } }
Данный подход позволяет добавлять новые объектынаблюдатели без какоголибо влияния на любые уже существующие классы. Он также позволяет поддерживать в системе низкий уровень связанности. Организованная подобным образом система работает так, как если бы все объекты в ней отвечали только сами за себя. Что произойдет в случае предъявления к системе нового требования? Предполо жим, возникла необходимость посылать письмо с купонами тем клиентам, которые находятся в пределах 20 миль от одного из складов компании. Чтобы реализовать это требование, достаточно просто определить новый класс наблюдатель, выполняющий отправку купонов. Он должен отправлять купоны только для тех новых клиентов, которые расположены не далее указанного расстояния от ближайшего из складов компании. Назовем этот класс BrickAndMortar и определим его как один из объектовнаблюдателей класса Customer. Данное решение представ лено на рис. 17.3.
ÐÈÑ. 17.3. Добавление нового объекта'наблюдателя BrickAndMortar
234
Часть V. Обработка вариаций с применением шаблонов проектирования
Иногда класс, который должен стать новым наблюдателем, может уже существо вать в системе. В этом случае, вероятно, будет предпочтительнее не вносить в него никаких изменений. Существует простой путь решить эту проблему: достаточно адап тировать существующий класс с помощью уже знакомого нам шаблона Adapter. При мер подобного решения приведен на рис. 17.4.
Класс Observable — замечание для разработчиков, использующих язык Java Шаблон Observer настолько полезен, что один из пакетов языка Java включает его встроенную реализацию. В данном случае шаблон составлен из класса Observable и интерфейса Observer. Класс Observable выполняет роль субъекта из описания схемы шаблона, представленного в книге "банды четырех". В качестве методов attach, detach и notify в языке Java используются, соответственно, методы addObserver, deleteObserver и notifyObservers (в Java используется и метод update). Язык Java также предоставляет несколько других методов, упрощающих работу с данными классами. (Дополнительные сведения об API языка Java для классов Observer и Observable можно найти в Internet по адресу http://java.sun.com/j2se/1.3/docs/api/ index.html.)
Основные характеристики шаблона Observer Назначение
Определяет зависимость типа "один ко многим" между объектами таким образом, что когда состояние одного объекта изменяется, все зависимые от него объекты автоматически уведомляются об этом и обновляются
Задача
Необходимо направить уведомления о том, что некоторое событие имело место согласно изменяющемуся списку объектов
Способ решения
Объекты"наблюдатели (класс Observer) делегируют ответственность по контролю за появлением некоторого события центральному объекту (класс Subject)
Участники
Объект класса Subject знает всех своих объектов"наблюдателей, потому что они регистрируются у него. Объект класса Subject должен уведомить все объекты класса Observer о наступлении интересующего события. Объекты класса Observer отвечают как за регистрацию себя у объекта класса Subject, так и за получение от него необходимой им информации после поступления уведомления
Следствия
Объекты класса Subject могут сообщить объектам класса Observer о наступлении событий, которые неинтересны некоторым из них, если последние обрабатывают только часть всех возможных событий (подробнее об этом мы поговорим ниже). Дополнительный обмен информацией может иметь место в случае, если после получения извещения от объекта класса Subject объектам класса Observer потребуются некоторые дополнительные данные
Реализация
•
Те объекты (класс Observer), которым необходимо знать о наступлении некоего события, подключаются к другому объекту (класс Subject), который наблюдает за наступлением событий или непосредственно инициирует требуемое событие
•
Когда событие происходит, объект класса Subject сообщает объектам класса Observer о том, что событие имело место
•
Для реализации единого интерфейса у всех объектов различных типов класса
Observer иногда используется шаблон Adapter
235
РИС. 17.4. Реализация классовнаблюдателей с помощью шаблона Adapter
Глава 17. Шаблон Observer
ÐÈÑ. 17.4. Реализация классов'наблюдателей с помощью шаблона Adapter
236
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 17.5. Стандартный упрощенный вид шаблона Observer
Дополнительные замечания о шаблоне Observer Следует заметить, что вовсе не обязательно использовать шаблон Observer всякий раз, когда между объектами возникает зависимость. Например, вполне очевидно, что в процессе подготовки накладной на заказ, объект, отвечающий за начисление нало га, должен быть извещен каждый раз, когда в накладную добавляется новая строка, — чтобы сумма налога была пересчитана заново. Применение шаблона Observer в дан ном случае — это не лучший подход: последовательность операций известна заранее, и вряд ли чтолибо когдалибо будет в нее добавлено. Когда зависимости между объек тами фиксированы, применение шаблона Observer, скорее всего, только добавит в систему сложности. Если список объектов, которые должны быть уведомлены о наступлении опреде ленных событий, подвержен изменениям или зависит от конкретных условий, ис пользование шаблона Observer, напротив, принесет большую пользу. Изменения в списке уведомляемых объектов могут быть вызваны как появлением новых требова ний к системе, так и динамическим появлением или удалением отдельных экземпля ров объектов. Шаблон Observer также может быть полезен, если система будет функ ционировать в различных условиях или у различных заказчиков, каждый из которых имеет собственный список объектовнаблюдателей. Определенные объектынаблюдатели могут также обрабатывать только некоторые из возникающих событий. Вспомним пример с объектом BrickandMortar. В подоб ной ситуации объектнаблюдатель должен самостоятельно отфильтровать не интере сующие его события. Появление избыточных уведомлений может быть исключено посредством переда чи ответственности за фильтрование рассылаемых уведомлений в адрес объекта клас са Subject. Наилучший способ реализовать это решение — применить шаблон Strategy для проверки необходимости отправки уведомлений. В этом случае каждый
Глава 17. Шаблон Observer
237
объектнаблюдатель должен предоставить объекту класса Subject корректную стра тегию принятия подобного решения непосредственно при своей регистрации. Иногда объекты класса Subject могут вызывать метод update объектов наблюдателей для передачи им информации. Такой подход помогает обойтись без обратных запросов от объектовнаблюдателей к объекту класса Subject. Однако час то оказывается, что разным объектамнаблюдателям требуется различная информа ции. В подобном случае вновь можно воспользоваться шаблоном Strategy. На этот раз объект класса Strategy используется для вызова метода update объектов наблюдателей. И вновь объектынаблюдатели должны предоставить для использова ния объекту класса Subject соответствующие объекты Strategy.
Резюме Изучая шаблон Observer, мы определили, какой объект в системе позволит наи лучшим образом справиться со вновь появляющимися изменениями. Для шаблона Observer объект, который инициирует событие (объект класса Subject), часто ока зывается не в состоянии оповестить все объекты, которые должны быть извещены о наступлении этого события. Для решения проблемы создается интерфейс Observer и устанавливается правило, согласно которому все объектынаблюдатели обязаны за регистрировать себя у объекта класса Subject. В этой главе наше внимание было сосредоточено на шаблоне Observer, поэтому полезно будет указать несколько общих концепций объектноориентированного про ектирования, используемых в этом шаблоне (табл. 17.3). Таблица 17.3. Концепции ООП, используемые в шаблоне Observer Концепция
Пояснение
Объекты отвечают сами за себя
Существуют различные виды объектовнаблюдателей, но все они получают необходимую им информацию от объекта класса Subject, а затем выполняют необходимые действия согласно их назначению
Абстрактные классы
Абстрактный класс Observer представляет концепцию объек тов, которые нуждаются в получении уведомления. Он предос тавляет классу Subject (субъекту) общий интерфейс для рас сылки всех уведомлений
Полиморфизм и инкапсуляция
Субъект не знает, с каким именно типом объекта класса
Observer он в данный момент взаимодействует. По существу, класс Observer инкапсулирует конкретные разновидности классовнаблюдателей. Это означает, что если в будущем поя вятся новые виды классовнаблюдателей, то это не потребует внесения какихлибо изменений в класс Subject
238
Часть V. Обработка вариаций с применением шаблонов проектирования
Приложение. Пример программного кода на языке C++ Листинг 17.2. Реализация шаблона Observer class Customer { public: static void attach(Observer *o); static void detach(Observer *o); String getState(); private: Vector myObs; void notifyObs(); } Customer::attach(Observer *o){ myObs.addElement(o); } Customer::detach(Observer *o){ myObs.remove(o); } Customer::getState () { // Могут использоваться другие методы, // предназначенные для получения дополнительной информации } Customer::notifyObs () { for (Enumeration e = myObs.elements(); e.hasMoreElements() ;) { ((Observer *) e)-> update(this); } } } class Observer { public: Observer(); void update(Customer *mycust)=0; // Создает эту абстракцию } Observer::Observer () { Customer.attach( this); } class AddrVerification : public Observer { public: AddrVerification(); void update( Customer *myCust); } AddrVerification::AddrVerification () { } AddrVerification::update (Customer *myCust) { // Здесь выполняется проверка адресов. // Для получения дополнительной информации о клиенте // может использоваться метод myCust } class WelcomeLetter : public Observer { public: WelcomeLetter();
Глава 17. Шаблон Observer
void update( Customer *myCust); } WelcomeLetter::update( Customer *myCust) { // Рассылка приветственного письма. // Для получения дополнительной информации о клиенте // может использоваться метод myCust }
239
240
Часть V. Обработка вариаций с применением шаблонов проектирования
Глава 18
Шаблон Template Method
Введение В этой главе мы продолжим обсуждение нашего второго учебного примера — при ложения поддержки электронной розничной торговли, начатое в главах 14–17. Здесь мы выполним следующее. •
Познакомимся с шаблоном Template Method при обсуждении новых требо ваний, предъявленных к нашему учебному примеру — системе поддержки электронной розничной торговли.
•
Выясним назначение шаблона Template Method.
•
Обсудим характеристики шаблона Template Method.
•
Познакомимся с моим опытом применения шаблона Template Method на практике.
Предъявление новых требований к системе электронной торговли Предположим, что в процессе создания приложения заказчик еще раз выдвинул но вое требование — обеспечить поддержку системой двух типов СУБД, а именно, Oracle и SQL Server. Обе эти системы используют язык SQL (Structured Query Language — струк турированный язык запросов), стандартный язык работы с реляционными БД, упро щающий работу с ними. Однако несмотря на то, что имеется общий стандарт этого язы ка, существуют некоторые различия в его реализации в разных СУБД. Будем считать, что выполнение запроса к базе данных включает следующую после довательность действий. 1. Подготовка команды CONNECT (установить соединение). 2. Отправка команды CONNECT в базу данных. 3. Подготовка команды SELECT (выбрать). 4. Отправка команды SELECT в базу данных. 5. Получение возвращаемого набора данных. Конкретные реализации баз данных, о которых идет речь, отличаются друг от дру га, поэтому процедуры подготовки команд для них также слегка различаются.
242
Часть V. Обработка вариаций с применением шаблонов проектирования
Шаблон Template Method Назначение шаблона Template Method (шаблонный метод) состоит в том, чтобы помочь выделить (абстрагировать) некоторый общий процесс из нескольких различ ных процедур. В работе "банды четырех" назначение шаблона Template Method сформулировано следующим образом. Определение алгоритма выполнения операции, с передачей некоторых ее этапов для выполнения в подклассах. Переопределение этапов алгоритма без изменения 1 его структуры. Другими словами, несмотря на то, что процедуры установки соединений и выпол нения запросов к базам данных в СУБД Oracle и SQL Server отличаются в деталях, концептуально эти процессы одинаковы. Шаблон Template Method позволяет вы явить существующие точки соприкосновения и поместить их в один абстрактный класс, тогда как все различия инкапсулируются в порожденных классах. Шаблон Template Method помогает организовать и управлять выполнением действий, общих в различных процессах.
Применение шаблона Template Method в системе электронной торговли В нашем учебном примере различия в процедурах доступа к базам данных разных СУБД проявляются в особенностях реализации выполняемых этапов (рис. 18.1).
ÐÈÑ. 18.1. Использование шаблона Template Method для организации выполнения запросов к БД
1 Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995, с. 325.
Глава 18. Шаблон Template Method
243
В классе QueryTemplate создается метод с именем doQuery, предназначенный для выполнения запросов к базе данных. В качестве аргументов ему передается имя базы данных и текст запроса. Метод DoQuery включает код выполнения приведенной выше последовательности из пяти этапов и организует вызов виртуальных методов (formatConnect и formatSelect) для выполнения действий, которые должны быть реализованы различными способами. Метод DoQuery реализован следующим образом. Как следует из рис. 18.1, прежде всего необходимо подготовить текст команды CONNECT, предназначенной для уста новки соединения с базой данных. Хотя абстрактный класс (QueryTemplate) знает, что эта команда должна быть подготовлена, ему неизвестно, как это можно выпол нить на практике. Конкретный код форматирования данной команды предоставляет ся в соответствующем производном классе. Такой же подход применяется и при под готовке текста команды SELECT. Шаблон Template Method управляет выполнением указанной процедуры, поскольку вызов методов осуществляется с помощью ссылки, указывающей на один из производ ных классов. Следовательно, несмотря на то, что объект класса QueryControl содер жит ссылку типа QueryTemplate, в действительности он ссылается на объект класса OracleQT или SQLSvrQT. Следовательно, когда метод doQuery вызывается в любом из этих объектов, при разрешении ссылки на метод сначала просматриваются методы в соответствующем производном классе. Допустим, что объект класса QueryControl ссылается на объект класса OracleQT. Поскольку в классе OracleQT метод DoQuery класса QueryTemplate не переопределяется, вызывается именно этот метод. Данный метод выполняется вплоть до вызова в нем метода formatConnect. Поскольку изна чально вызов метода DoQuery поступил к объекту класса OracleQT, выполняется метод formatConnect именно этого объекта, т.е. объекта класса OracleQT. Затем управление возвращается в метод doQuery объекта QueryTemplate и выполняется следующий уча сток кода, общий для всех запросов, — пока не потребуется обработать еще один тип ва риаций, инкапсулированный в методе formatSelect. И вновь вызываемый метод на ходится в том самом объекте, на который ссылается объект класса QueryControl (в данном случае это объект класса OracleQT). Если впоследствии потребуется организовать работу с новым типом СУБД, шаблон Template Method предоставит для этой цели готовую структуру (шаблон), которую ос танется только заполнить. Достаточно будет создать новый конкретный класс, произ водный от абстрактного класса QueryTemplate, и реализовать в нем выполнение всех специфических для данной СУБД запросов.
Дополнительные замечания о шаблоне Template Method Иногда в классе используется несколько различных шаблонов Strategy. Впервые увидев диаграмму классов шаблона Template Method, я решил, что шаблон Template Method — это просто совокупность шаблонов Strategy, работающих совместно. Однако это потенциально опасное (и обычно ошибочное) представление. Применение сразу нескольких шаблонов Strategy, соединенных между собой, встречается достаточно редко и просто нежелательно, поскольку уменьшает гибкость системы. Шаблон Template Method рекомендуется применять в тех случаях, когда имеют место различные в реализации, но концептуально сходные процессы. Вариации для
244
Часть V. Обработка вариаций с применением шаблонов проектирования
каждого из процессов объединяются, поскольку они связаны с конкретным процес сом. В приведенном выше примере было показано, что если команду CONNECT потре бовалось отформатировать для базы данных Oracle, то и команды SELECT также будут форматироваться для этой базы данных Oracle.
Основные характеристики шаблона Template Method Назначение
Определение алгоритма выполнения операции с передачей некоторых ее этапов для выполнения в подклассах. Переопределение этапов алгоритма без изменения его структуры
Задача
Существует процедура или последовательность выполняемых этапов, которая является неизменной на верхнем уровне детализации, но ее отдельные этапы могут иметь различные реализации на более низком уровне детализации
Способ решения
Допускается определение изменяющихся этапов с сохранением основного процесса неизменным
Участники
Шаблон Template Method состоит из абстрактного класса AbsrtactClass, включающего определение основного метода TemplateMethod (рис. 18.2), и абстрактных методов PrimitiveOperation, которые должны переопределяться в производных классах. Каждый конкретный класс ConcreteClass, производный от абстрактного класса, должен включать реализацию всех абстрактных методов шаблона, специфическую для конкретной вариации процесса
Следствия
Шаблон TemplateMethod обеспечивают хорошую платформу для достижения многократного использования кода. Он также будет полезен для получения гарантий, что все необходимые этапы будут реализованы. Шаблон предусматривает компоновку всех переопределяемых этапов в один конкретный класс ConcreteClass и поэтому должен использоваться лишь тогда, когда все эти вариации будут проявляться вместе и только вместе
Реализация
Создается абстрактный класс, в котором основная выполняемая процедура реализуется с использованием абстрактных методов. Эти абстрактные методы должны быть реализованы в подклассах, обеспечивающих выполнение каждого этапа общей процедуры. Если выполняемые этапы изменяются независимо, каждый из них может быть реализован с использованием шаблона Strategy
ÐÈÑ. 18.2. Стандартное упрощенное представление ша' блона Template Method
Глава 18. Шаблон Template Method
245
Резюме Иногда в приложении необходимо организовать выполнение нескольких вариан тов одной и той же процедуры. Набор операций этих процедур является общим на концептуальном уровне, но реализация отдельных операций в каждом случае может отличаться от других. Например, процедуры выполнения SQLзапросов в базе данных для различных СУБД на концептуальном уровне одинаковы, но некоторые дета ли (например, способ установки соединения с базой данных) могут различаться. Шаблон Template Method позволяет определить общую последовательность вы полняемых этапов, а затем при необходимости переопределить способ реализации отдельных этапов в общей последовательности.
246
Часть V. Обработка вариаций с применением шаблонов проектирования
Глава 19
Шаблон Factory Method Введение В этой главе мы продолжим обсуждение нашего второго учебного примера — при ложения поддержки электронной розничной торговли, начатое в главах 14–18. Здесь мы выполним следующее. •
Познакомимся с шаблоном Factory Method в свете новых требований, предъяв ленных к нашему учебному примеру — системе поддержки электронной рознич ной торговли.
•
Выясним назначение шаблона Factory Method.
•
Обсудим характеристики шаблона Factory Method.
•
Познакомимся с моим опытом применения шаблона Factory Method на практике.
Предъявление новых требований к системе электронной торговли В главе 18, Шаблон Template Method, мы оставили без внимания вопрос, как создать экземпляры объектов, необходимых для работы с базой данных в текущем контексте. Возлагать ответственность за создание этих объектов на объектклиент может ока заться нежелательным. Предпочтительней было бы, чтобы этим занимался непосред ственно класс QueryTemplate. В главе 18 каждый конкретный класс, производный от абстрактного класса QueryTemplate, предназначен для определенной СУБД. Следовательно, на каждый такой конкретный класс можно возложить ответственность за создание экземпляра объекта, работающего с соответствующей СУБД. Это решение можно считать весьма удачным, но при условии, что в системе с объектами взаимодействия с СУБД будет работать только класс QueryTemplate (и производные от него классы). Схематиче ски это решение представлено на рис. 19.1. На рис. 19.1 показано, что метод doQuery из шаблона Template Method использует метод makeDB для создания экземпляра объекта, работающего с соответствующей СУБД. Класс QueryTemplate ничего не знает о том, экземпляр какого именно объек та работы с СУБД будет создан. Ему известно лишь то, что объект подобного типа не пременно должен быть создан, и этот класс предоставляет необходимый интерфейс для данного процесса. Обязанность знать, какой именно тип объекта следует создать, возлагается на классы, производные от абстрактного класса QueryTemplate. Таким образом, на данном уровне создание объекта взаимодействия с СУБД требуемого типа осуществляется в методе производного класса.
248
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 19.1. Шаблон Template Method (метод doQuery) использует шаблон Factory Method (метод makeDB)
Поскольку в данном решении фигурирует метод, предназначенный для создания экземпляра объекта, этот шаблон получил имя Factory Method (методфабрика).
Открытые или защищенные методы? Обратите внимание на то, что на рис. 19.1 методы makeDB являются защищенными (об этом говорит символ # перед именем метода). В этом случае только сам класс QueryTemplate и все классы, производные от него, имеют доступ к данным методам. Если необходимо разрешить доступ к этим методам и объектам других классов, отличных от QueryTemplate, следует объявить их с типом public. Это другой, достаточно распространенный способ использования шаблона Factory Method. В данном случае ответственность за принятие решения относительно того, объект какого именно типа будет создан, также возлагается на конкретный производный класс.
Шаблон Factory Method Шаблон Factory Method позволяет эффективно распределить ответственность за создание экземпляров объектов. В работе "банды четырех" назначение шаблона Factory Method сформулировано следующим образом. Определить интерфейс для создания экземпляра объекта с предоставлением про изводным классам права принимать решение о том, какой именно тип объекта бу дет создан. Шаблон Factory Method позволяет абстрактному классу возложить обя 1 занности по созданию экземпляра объекта на его производные классы.
1 Gamma, E., Helm, R., Johnson, R., Vlissides, J., Design Patterns: Elements of Reusable ObjectOriented Software, Reading, MA : AddisonWesley, 1995, с. 325.
Глава 19. Шаблон Factory Method
249
Дополнительные замечания о шаблоне Factory Method В классическом варианте реализации шаблона Abstract Factory присутствует абст рактный класс, определяющий методы создания семейств объектов. Для каждого из семейств объектов определяется производный класс, отвечающий за создание экзем пляров объектов данного семейства. Каждый метод, определенный в абстрактном классе, а затем переопределенный в производном классе, отвечает требованиям шаб лона Abstract Factory. Иногда полезно создать иерархическую структуру классов, параллельную структу ре существующих классов, с делегированием новой иерархии некоторых обяза тельств. В этом случае важно, чтобы каждый объект в исходной иерархии был спосо бен создать экземпляр объекта соответствующего класса параллельной иерархии. Для этого можно использовать шаблон Factory Method. Шаблон Factory Method обычно используется при формировании рабочей среды. Поскольку рабочая среда определяется на абстрактном уровне, то здесь обычно от сутствуют какиелибо сведения в отношении правил создания экземпляров конкрет ных объектов. Право принятия решений о создании экземпляров конкретных объек тов делегируется пользователю данной рабочей среды.
Основные характеристики шаблона Factory Method Назначение
Определить интерфейс для создания объекта с передачей производным классам права принимать решение, экземпляр какого именно класса следует создать
Задача
Классу требуется создать экземпляр объекта конкретного класса, производного от другого класса, но точно не известно, какого именно. Шаблон Factory Method позволяет порожденному классу принимать подобное решение
Способ решения
Производный класс принимает решение, экземпляр какого класса требуется создать и как это следует делать
Участники
Класс Product представляет собой интерфейс для того типа объекта, который создается с помощью шаблона Factory Method. Класс Creator представляет собой интерфейс, который определяет шаблон Factory Method
Следствия
Для создания экземпляра определенного класса ConcreteProduct необходимо определить конкретный класс ConcreteCreator, производный от абстрактного класса
Creator Реализация
Использование метода абстрактного класса, который также является абстрактным (чисто виртуальным в языке C++). Программный код абстрактного класса ссылается на этот метод, когда требуется создать экземпляр объекта конкретного производного класса, но не известно, какого именно
250
Часть V. Обработка вариаций с применением шаблонов проектирования
ÐÈÑ. 19.2. Стандартное упрощенное представление шаблона Factory Method
Резюме Шаблон Factory Method — один из самых простых и понятных шаблонов, который очень широко используется. К нему прибегают, когда необходимо перенести реали зацию правил создания экземпляра объекта в некоторый производный класс. В таких случаях наиболее эффективное решение состоит в том, чтобы поместить реализацию метода в тот класс, который отвечает за требуемое поведение.
Глава 20
Матрица анализа
Введение В этой главе мы продолжим обсуждение нашего второго учебного примера — при ложения поддержки электронной розничной торговли, начатое в главах 14–19. Теперь, когда мы обсудили полный набор шаблонов по отдельности, пришло вре мя вернуться на шаг назад и вновь обратиться к одной из самых серьезных проблем в разработке программного обеспечения — обработке вариаций, присутствующих в данной проблемной области. Шаблоны проектирования могут оказать большую по мощь в выявлении и эффективном управлении этими вариациями. Здесь мы выполним следующее. •
Рассмотрим проблему вариаций, имеющих место в реальном мире.
•
Обсудим ту часть нашего учебного примера (системы поддержки электронной розничной торговли), которая связана с наиболее сложной проблемой обра ботки вариаций в приложении. По ходу решения этой проблемы мы по знакомимся с методом создания матрицы анализа — простейшим вариантом таблицы принятия решений, который может с пользой применяться для обна ружения и обработки имеющихся вариаций в концепциях. Затем мы проведем параллель между этим подходом и концепциями, выдвинутыми Кристофером Александером и Джимом Коплином.
•
Познакомимся с моим опытом применения метода создания матрицы анализа на практике.
Вариации в реальном мире В реальном мире проблемы редко бывают простыми или легко устранимыми. За исключением наиболее тривиальных случаев, любая проблема всегда кажется очень сложной, а возникающие вариации не поддаются систематизации. Каждая про блема воспринимается как западня, ведущая к разрушению столь тщательно разрабо танной нами модели. Например, как правило, поступающие в больницу пациенты сначала направляются в приемное отделение. Но когда сложившаяся ситуация создает угрозу жизни пациен та, он направляется непосредственно в отделение скорой помощи, тем самым нару шая обычный порядок. Подобные вариации, имеющие место в реальном мире, вос принимаются как разнообразные особые случаи, которые создаваемая система долж на уметь обрабатывать.
252
Часть V. Обработка вариаций с применением шаблонов проектирования
Понятно, что все это создает дополнительные проблемы для любого аналитика. Могут ли шаблоны проектирования помочь более эффективно обрабатывать вариации? Воспользуемся следующим подходом. Внесем некоторое изменение в систему, а за тем используем аналитические методы для выявления тех шаблонов, которые следует включить в проект. Данный подход предполагает выполнение следующих этапов. 1. Идентификация наиболее важных функций для одного случая и систематиза ция их в виде матрицы. Обозначим каждую функцию той концепцией, которую эта функция представляет. 2. Распространение этого подхода на все остальные случаи с расширением матрицы по мере необходимости. Каждый случай обрабатывается независимо от других. 3. Расширение матрицы анализа за счет добавления новых концепций. 4. Использование строк для идентификации правил. 5. Применение столбцов для идентификации особых случаев. 6. Идентификация шаблонов проектирования на основании проведенного анализа. 7. Разработка проекта на концептуальном уровне.
Вариации в учебном примере — система международной электронной торговли Предположим, что создаваемая нами система поддержки электронной розничной торговли должна обрабатывать заказы в нескольких странах мира. Чтобы упростить условия, примем, что этих стран только две — США и Канада. Проанализировав предъявляемые к системе новые требования, можно обнаружить несколько обстоя тельств, которые будут изменяться в зависимости от страны (табл. 20.1). Таблица 20.1. Обстоятельства, зависящие от места жительства клиента Вариант
Процедура
США
∑ Стоимость доставки вычисляется по тарифам компании UPS ∑ Для проверки адресов используются американские почтовые правила ∑ Вычисляется налог на основании цены товара или услуги, в зависимо сти от места проживания ∑ Суммы рассчитываются в долларах США
Канада
∑ Стоимость доставки вычисляется по тарифам ведущей канадской ком пании доставки грузов ∑ Для проверки адресов используются канадские почтовые правила ∑ Вычисляется налог на основании цены на товар или услуги в зависимо сти от налоговых правил конкретной канадской провинции ∑ Суммы рассчитываются в канадских долларах
Вариации, связанные с вновь поставленной задачей, не слишком сложны. Ее ре шение кажется вполне очевидным. Действительно, указанная проблема проста. Но
Глава 20. Матрица анализа
253
она позволяет продемонстрировать метод работы с вариациями, который я много кратно использовал. Мой метод прост, и он легко может быть распространен на мно жество задач в реальном мире. Я назвал этот метод "Матрица анализа". На данной стадии анализа наша цель состоит в том, чтобы найти те концепции, ко торые изменяются, определить точки соприкосновения и обнаружить недостающие требования. Концепции соответствуют конкретным требованиям для каждого особо го случая. Обсуждение вопросов проектирования и реализации мы отложим до более поздних стадий. Начнем с рассмотрения первого случая. Проанализируем каждую функцию, которая должна быть реализована, и обозначим концепцию, которую она представляет. Поместим такую функцию в отдельную строку матрицы, а название представляемой ей концепции — в крайний слева столбец. Рассмотрим данный процесс подробно, начиная с табл. 20.2. Таблица 20.2. Заполнение матрицы анализа — первая концепция США Вычисление стоимости доставки
Вычисляется по тарифам компании UPS
Теперь обработаем следующую часть информации: "Для проверки адресов исполь зуются американские почтовые правила". Добавим в матрицу еще одну строку и по местим в нее данную информацию, как показано в табл. 20.3. Таблица 20.3. Заполнение матрицы анализа — вторая концепция США Вычисление стоимости доставки
Вычисляется по тарифам компании UPS
Проверка адреса
Используются американские почтовые правила
Далее поместим в матрицу остальные концепции для первого случая, как показано в табл. 20.4. Таблица 20.4. Заполнение матрицы анализа. Законченный первый вариант — продажа в США США Вычисление стоимости доставки
Вычисляется по тарифам компании UPS
Проверка адреса
Используются американские почтовые правила
Расчет налогов
Рассчитываются государственные и местные налоги США
Валюта
Доллары США
Теперь перейдем к следующему конкретному случаю, а затем к другим, добавляя столбец для каждого нового варианта и заполняя каждую новую ячейку той информа цией, которая у нас имеется. Законченная матрица с учетом второго специального случая показана в табл. 20.5.
254
Часть V. Обработка вариаций с применением шаблонов проектирования
Таблица 20.5. Заполнение матрицы анализа. Законченный второй вариант — продажа в Канаде США
Канада
Вычисление стои мости доставки
Вычисляется по тарифам ком пании UPS
Вычисляется по тарифам веду щей канадской компании дос тавки грузов
Проверка адреса
Используются американские почтовые правила
Используются канадские поч товые правила
Расчет налогов
Рассчитываются государствен ные и местные налоги США
Рассчитываются государствен ные и местные налоги Канады
Валюта
Доллары США
Канадские доллары
В процессе построения матрицы анализа часто обнаруживаются недостающие требования к системе. Эту информацию следует использовать для расширения облас ти анализа. Подобная неопределенность может возникать изза того, что поступившая от заказчика информация была неполной. В одном случае заказчик может упоминать какието конкретные требования, а в другом опустить их. Например, при формулиро вании требований по работе с клиентами из США может не упоминаться ограничение по максимальному весу заказа, тогда как для клиентов из Канады такое ограничение может быть установлено — скажем, на уровне 31,5 кг. После сравнения набора требо ваний для каждого конкретного случая необходимо заполнить пустые ячейки в мат рице анализа. В нашем случае следует обратиться к пользователям, занимающимся клиентами из США, и задать им конкретный вопрос о максимально допустимом весе заказа (кстати, подобного ограничения может просто не существовать). Со временем могут появиться новые прецеденты (например, может потребоваться добавить в систему поддержку клиентов из Германии). Всякий раз, когда в одном из прецедентов обнаруживается новая концепция, в матрицу анализа следует добавить новую строку, даже если соответствующая информация для всех остальных случаев пока неизвестна. Сказанное выше иллюстрируется данными в табл. 20.6. Таблица 20.6. Расширение матрицы анализа. Добавлен третий вариант — продажа в Германии США
Канада
Германия
Вычисление стоимости дос тавки
Вычисляется по тарифам компании UPS
Вычисляется по тари фам ведущей канад ской компании достав ки грузов
Вычисляется по тарифам ведущей немецкой компании доставки грузов
Проверка адреса
Используются аме риканские почто вые правила
Используются канад ские почтовые правила
Используются гер манские почтовые правила
Расчет налогов
Рассчитываются го сударственные и ме стные налоги США
Рассчитываются госу дарственные и мест ные налоги Канады
Рассчитываются го сударственные на логи Германии
Валюта
Доллары США
Канадские доллары
Евро
Формат даты
мм/дд/гггг
мм/дд/гггг
дд/мм/гггг
Глава 20. Матрица анализа
255
Несколько слов о работе с заказчиком Обширный опыт общения с заказчиками позволил мне понять несколько важных моментов.
•
Как правило, заказчики знают проблемную область очень хорошо (более того, они знают ее лучше, чем я когда" либо буду знать).
•
Чаще всего заказчики не воспринимают предметную область на концептуальном уровне, как это принято у разработчиков. Напротив, они говорят о конкретных проявлениях тех или иных частных случаев.
•
Заказчики часто используют определение "всегда", но на самом деле следовало бы говорить "обычно".
•
Также часто они используют определение "никогда", подразумевая "редко".
•
Заказчики часто утверждают, что рассказали обо всех возможных ситуациях, тогда как в действительности речь шла только о том, что случается обычно.
Подводя итог, можно сказать, что я полностью доверяю объяснениям заказчиков, когда они дают ответы на заданные мной конкретные вопросы, но с большим сомнением отношусь к их общим рассуждениям. Я всегда стараюсь вести диалог с заказчиком на максимально конкретном уровне. Даже те из заказчиков, которые полагают, что они способны осмыслить задачу на концептуальном уровне, в попытках оказать помощь разработчику часто переоценивают свои возможности.
Теперь, когда все концепции выявлены, как следует поступить с полученными знаниями? С чего начать, чтобы можно было перейти к вопросам реализации? Еще раз посмотрим на матрицу анализа, представленную в табл. 20.6. Первая строка называется "Вычисление стоимости доставки" и включает ячейки со значениями "Вычисляется по тарифам компании UPS", "Вычисляется по тарифам ведущей канадской компании доставки грузов", "Вычисляется по тарифам ведущей немецкой компании дос тавки грузов". В этой строке одновременно представлены следующие концепции. •
Общее правило реализации "Вычисления стоимости доставки".
•
Конкретный набор правил, который должен быть реализован, — указана компания, тарифами которой следует пользоваться при вычислении стоимости доставки заказа в каждой из стран.
Таким образом, каждая строка включает описание конкретных способов реализа ции соответствующей обобщенной концепции. В нашем примере две строки (валюта и формат даты) могут быть обработаны непосредственно на уровне объектов. Например, денежные расчеты могут обрабатываться с помощью объектов, включаю щих данные типа currency (валюта). Многие языки программирования поддержи вают обработку различных национальных форматов даты с помощью специальных библиотек. В табл. 20.7 представлен концептуальный способ обработки каждой стро ки матрицы анализа. А что представляют собой столбцы матрицы анализа? Они включают набор кон кретных реализации для каждого отдельного случая, представленного данным столб цом. Эту мысль наглядно иллюстрирует табл. 20.8.
256
Часть V. Обработка вариаций с применением шаблонов проектирования
Таблица 20.7. Правила реализации конкретных строк
Таблица 20.8. Правила реализации конкретных столбцов
Глава 20. Матрица анализа
257
Например, в первом столбце представлены конкретные реализации для отдель ных аспектов процедуры обработки заказа для клиента, проживающего в Соединен ных Штатах. Каким же образом можно перейти от понятий, собранных в таблице, к шаблонам проектирования? Еще раз обратимся к табл. 20.7. В каждой ее строке представлен конкретный способ реализации концепции, указанной в крайнем слева столбце. •
В строке "Вычисление стоимости доставки" значения "Вычисляется по тарифам компании UPS" и "Вычисляется по тарифам ведущей канадской компании доставки грузов" в действительности дают ответ на вопрос, "Как следует вычислять стоимость доставки заказа". Алгоритм требуемых вычислений инкапсулирован в ячейке "Вычисление стоимости доставки", а конкретными правилами его использования являются значения "Вычисляется по тарифам компании UPS", "Вычисляется по тарифам ведущей канадской компании доставки грузов" и "Вычисляется по тарифам ведущей немецкой компании доставки грузов".
•
Следующие две строки также представляют собой объединение концепций и различных правил, описывающих их конкретные реализации.
•
Последние две строки представляют классы, которые могут согласованно использоваться в масштабах всего приложения и будут проявлять разное поведение в зависимости от страны, в которой находится клиент.
Следовательно, каждую из первых трех строк можно воспринимать как шаблон Strategy. Этот подход иллюстрируется в табл. 20.9. Таким образом, объекты в первой строке могут быть реализованы в виде шаблона Strategy, инкапсулирующего правило "Вычисление стоимости доставки". Аналогично можно проанализировать и столбцы матрицы. Каждый из столбцов описывает правила, которые используются в некотором конкретном случае. Таким образом, столбцы представляют семейства объектов, необходимых для обработки та кого случая. Все это напоминает структуру шаблона Abstract Factory, что иллюстриру ет табл. 20.10. Таким образом, выяснив, что в нескольких строках представлен шаблон Strategy, а каждый столбец представляет семейство объектов шаблона Abstract Factory, можно приступить к разработке концептуального решения для проекта нашего учебного приложения. Один из возможных вариантов концептуальной схемы проекта пред ставлен на рис. 20.1.
Дополнительные замечания На практике, в матрице анализа может быть обнаружен почти каждый из рассмот ренных нами шаблонов, использующих полиморфизм (например, Bridge, Decorator, Template Method и Observer). Кроме того, в матрице анализа могут использоваться шаблоны Composite (композит), Proxy (полномочия), Chain of Responsibility (цепь от ветственности), Command (команда), Iterator (итератор), Mediator (посредник) и Visitor (посетитель).
258
Часть V. Обработка вариаций с применением шаблонов проектирования
Таблица 20.9. Реализация правил с использованием шаблона Strategy
Таблица 20.10. Реализация правил с использованием шаблона Abstract Factory
259
РИС. 20.1. Концептуальная схема проекта с использованием шаблонов Strategy и Abstract Factory
Глава 20. Матрица анализа
ÐÈÑ. 20.1. Концептуальная схема проекта с использованием шаблонов Strategy и Abstract Factory
260
Часть V. Обработка вариаций с применением шаблонов проектирования
Например, пусть в нашем примере с приложением поддержки системы электрон ной розничной торговли будет добавлено требование выводить на печать накладную с указанием о существовании нескольких разновидностей этого документа: •
для клиентов из США накладная содержит только заголовок;
•
для клиентов из Канады накладная содержит заголовок и нижний колонтитул;
•
для клиентов из Германии накладная содержит два различных нижних колонтитула.
Данная информация может быть отражена в отдельной строке матрицы анализа, причем каждой стране будет соответствовать свой формат печатаемого документа. Реализацию представленных в этой строке новых требований можно осуществить с помощью шаблона Decorator. Несмотря на то что матрица анализа редко охватывает все аспекты некоторой проблемной области, я считаю этот метод весьма полезным, по крайней мере, для большинства известных мне проблемных областей. Особенно полезным он будет в ситуации, когда существует столь большое количество специальных случаев, что про сто невозможно удержать их все в голове, не утеряв общей картины. На самом деле все не так просто. Очень редко различные группы требований пре доставляются аналитикам или разработчикам в скольконибудь согласованном виде. Однако это лишь незначительно усложняет процедуру создания матрицы анализа. В подробных ситуациях я беру очередную функцию и стараюсь найти в левом столбце ту концепцию, вариацией которой она является. Если такая концепция будет обнару жена, данная функция помещается в соответствующую строку. Если требуемая кон цепция отсутствует в матрице анализа, необходимо создать новую строку. В чрезвычайно сложных случаях построение матрицы анализа оказывается един ственным действенным методом овладеть ситуацией. В моей практике однажды встретился клиент, в требованиях которого особые случаи исчислялись десятками. Каждый особый случай представлял собой независимо разработанную систему управ ления документами. Проблема состояла в том, чтобы объединить все эти системы управления документами в единое целое. Количество особых случаев было настолько велико (матрица насчитывала почти сотню строк или даже более того), что представ лялось просто невозможным осмыслить всю систему в целом. У аналитиков не было подходящего концептуального решения, охватывающего все существующие аспекты проблемы. Они могли лишь выделить общие правила и случаи, являющиеся исключе ниями. Составляя матрицу анализа, я рассмотрел каждый случай отдельно, затем вы делил общие данные и варианты поведения (они были описаны в крайнем слева столбце матрицы). После этого мне осталось лишь выполнить их реализацию с по мощью шаблонов проектирования.
Глава 20. Матрица анализа
261
Резюме Учет вариаций в концепциях можно считать одной из самых больших трудностей, с которыми сталкивается аналитик. В этой главе предложен простой инструмент ана лиза, который может оказаться весьма полезным при решении проблем подобного типа. Я назвал этот инструмент матрицей анализа, и могу сказать, что его идея осно вана он на концепциях, предложенных Кристофером Александером и Джимом Коп лином. Приведенный здесь пример применения этого инструмента для решения от носительно простой задачи демонстрирует, как можно выявить типы шаблонов, свойственных данной проблемной области. Хотя данный инструмент может быть чрезвычайно полезен для обработки вариаций и описания характеристик проблем ной области, я не претендую на то, что он охватывает все аспекты проектирования.
ЧАСТЬ VI
Завершение и начало
В этой части В этой части мы продолжим обсуждение нового подхода к объектноориентиро ванному проектированию. В частности, будет рассмотрен вопрос о том, как этот но вый подход применялся при проектировании и реализации шаблонов проектирова ния. В завершении приводится обширный список рекомендуемой литературы. Глава
Предмет обсуждения
21
Обзор действующих сил и взаимосвязей в шаблонах проектирования в кон тексте нового подхода к объектноориентированному проектированию
22
Предлагается литература и указываются другие ресурсы, полезные для дальнейшего изучения
Глава 20. Шаблоны проектирования и новый взгляд...
263
Глава 21. Шаблоны проектирования и новый взгляд...
265
Глава 21
Шаблоны проектирования и новый взгляд на объектно ориентированное проектирование
Введение Завершая чтение книги, всегда полезно задать себе вопрос, что нового удалось из нее почерпнуть. В этой книге авторами была предпринята попытка дать читателю лучшее и, возможно, новое понимание принципов объектноориентированного проектирования, достигнутое за счет изучения шаблонов проектирования и разъяснения того, как эти шаблоны проектирования раскрывают объектноориентированную парадигму. В этой главе мы выполним следующее. •
Познакомимся с новым взглядом на принципы объектноориентированного проектирования, базирующимся на понятии шаблонов проектирования.
•
Выясним, как шаблоны проектирования могут помочь разработчику инкапсули ровать реализацию.
•
Обсудим метод анализа общности/изменчивости и выясним, как шаблоны проектирования помогают понять назначение абстрактных классов.
•
Узнаем, как выполнить декомпозицию проблемной области на основе сущес твующих в ней обязательств.
•
Рассмотрим методы определения отношений между объектами.
•
Еще раз вернемся к методу проектирования от контекста с использованием шаблонов проектирования.
В конце главы читателю будут предложены некоторые выводы и обобщения, осно ванные на практическом опыте авторов.
266
Часть VI. Завершение и начало
Перечень принципов объектноориентированного проектирования В ходе обсуждения шаблонов проектирования упоминалось несколько важных принципов объектноориентированной парадигмы. Подводя итог, эти принципы можно сформулировать следующим образом. •
Объекты обладают четко определенными обязательствами.
•
Объекты отвечают только за собственное поведение.
•
Инкапсуляция подразумевает любой вид сокрытия, а именно: − сокрытие данных; − сокрытие класса (за абстрактным классом или интерфейсом); − сокрытие реализации.
•
Выявление вариаций в поведении и данных с помощью анализа общности/из менчивости.
•
Проектирование интерфейсов, а не реализации.
•
Понимание наследования как метода концептуализации вариаций, а не меха низма определения особых версий уже существующих объектов.
•
Исключение взаимосвязанности различных вариаций в одном и том же классе.
•
Стремление к поддержанию низкой связанности в системе.
•
Стремление к поддержанию высокой связности в объектах.
•
Повсеместное соблюдение правила "однажды и только однажды" в отношении реализации обязательств.
Как шаблоны проектирования инкапсулируют реализацию Для некоторых из представленных в этой книге шаблонов проектирования харак терно то, что они скрывают детали реализации от объектаклиента. Например, шаблон Bridge скрывает от объектаклиента подробности реализации классов, производных от класса Absraction. Кроме того, интерфейс Implementation скрывает семейство классов реализации для класса Absraction и его производных классов. В шаблоне Strategy от объектаклиента скрыта реализация каждого класса ConcreteStrategy. Это правило справедливо для большинства шаблонов, описанных "бандой четырех", — все они позволяют скрыть конкретную реализацию. Ценность подобного сокрытия реализации состоит в том, что благодаря ему шаб лоны позволяют легко добавлять новые реализации, так как объектыклиенты ничего не знают о том, как работают уже существующие реализации.
Глава 21. Шаблоны проектирования и новый взгляд...
267
Анализ общности/изменчивости и шаблоны проектирования В главе 9, Шаблон Bridge, было показано, как с помощью анализа общности/измен чивости можно вывести шаблон Bridge. Аналогичным образом могут быть выведены и многие другие шаблоны, включая Strategy, Iterator, Proxy, State, Visitor, Template Method и Abstract Factory. Однако более важно то, сколько шаблонов может быть при менено за счет проведения анализа общности/изменчивости, поскольку поиск общно стей в проблемной области помогает обнаружить присутствие в ней шаблонов. Например, в отношении шаблона Bridge, можно начать анализ с рассмотрения не скольких конкретных требований к системе: •
вычерчивание квадрата с помощью первой графической программы;
•
вычерчивание окружности с помощью второй графической программы;
•
вычерчивание прямоугольника с помощью первой графической программы.
Знание шаблона Bridge позволяет выделить в этих конкретных случаях две общности: •
графические программы;
•
отображаемые геометрические фигуры.
Аналогично, знание шаблона Strategy подсказывает, что если существует несколько различных правил, то следует искать в них возможную общность, которую затем мож но будет инкапсулировать. Но изучение шаблонов на этом не заканчивается. Необходимо продолжать читать литературу. Шаблонам посвящены различные дискуссии, проводимые на основании практического опыта анализа и проектирования. Шаблоны предоставляют команде разработчиков единый инструментарий для обсуждения проблемы, а также позволя ют включить в создаваемый код лучшие практические решения, найденные другими программистами.
Декомпозиция проблемной области в обязательства Анализ общности/изменчивости позволяет найти концептуальное решение (общность) и подготовить решение на уровне реализации (каждая конкретная вариа ция). Если учитывать только общности и объекты, использующие их, то можно по дойти к решению проблемы с другой стороны — с помощью метода декомпозиции в обязательства. Например, в шаблоне Bridge проблемная область рассматривается как состоящая из двух различных типов сущностей (абстракций и реализаций). Следовательно, не стоит ограничивать себя только объектноориентированной декомпозицией (т.е. раз ложением проблемной области на объекты), будет полезно также попробовать раз ложить проблемную область в обязательства, что может оказаться даже проще. В этом случае возможно предварительно определить объекты, которые потребуются для реализации найденных обязательств, и лишь затем переходить к обычной объ ектной декомпозиции.
268
Часть VI. Завершение и начало
Выше сказанное — это всего лишь расширение правила, которое я уже упоминал вы ше: проектировщик не должен беспокоиться о том, как будут создаваться экземпляры объектов, пока не станет известно, в каких именно объектах он нуждается. Это правило можно понимать как требование декомпозиции поставленной задачи на две части: •
определить, какие объекты необходимы;
•
принять решение, как будут создаваться экземпляры этих объектов.
Те или иные шаблоны часто подсказывают нам, как можно выполнить декомпози цию обязательств. Например, шаблон Decorator демонстрирует, как обеспечить гиб кое комбинирование объектов, если после декомпозиции проблемная область вклю чает набор основных обязательств, которые используются всегда (класс ConcreteComponent), и некоторое множество вариаций, используемых от случая к случаю (классыдекораторы). Шаблон Strategy демонстрирует декомпозицию проблемы на объект, который использует правила, и собственно используемые правила.
Отношения внутри шаблона Должен заметить, что на проводимых мной занятиях я иногда позволяю себе не которую вольность, приводя определенную цитату из книги Александера. После того как две трети дня были посвящены обсуждению того, насколько хороши шаблоны проектирования, я беру в руки его книгу Timeless Way of Building, открываю последнюю страницу и говорю так. В этой книге 549 страниц. На странице 545, которая, очевидно, одна из самых по следних в книге, Александер говорит следующее: "На этой заключительной стадии 1 шаблоны уже не важны…". Я делаю паузу и замечаю: "Было бы прекрасно, если бы он сказал об этом в начале книги — это могло бы сэкономить нам уйму времени". Затем я продолжаю цитировать 2 книгу: "Шаблоны учат нас быть восприимчивыми к реальности". И заканчиваю я такими словами: "Если вы прочтете книгу Александера, то пойме те, что реальность — это отношения и движущие силы, описываемые шаблонами". Шаблоны предоставляют нам способ говорить об этой реальности. Однако для нас важны вовсе не шаблоны сами по себе. Это же справедливо и по отношению к шабло нам проектирования в области разработки программного обеспечения. Шаблон описывает движущие силы, мотивы и отношения для определенной про блемы в определенном контексте и предлагает подход к ее решению. Например, шаб лон Bridge представляет отношения между классами, производными от определенной абстракции, и их возможными реализациями. Шаблон Strategy описывает отношения между следующими элементами: •
классом, который использует один из наборов алгоритмов (Context);
•
членами этого набора алгоритмов (классы стратегий);
1 Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, NY: Oxford University Press, 1979, с. 545. 2 Там же, с. 545.
Глава 21. Шаблоны проектирования и новый взгляд...
•
269
классомклиентом, который использует класс Context и определяет, какой из алгоритмов следует использовать.
Шаблоны и проектирование от контекста При обсуждении проблемы САПР в начале этой книги было показано, как шаблоны проектирования используются, если внимание сосредоточено на контексте, который они создают друг для друга. Шаблоны проектирования, функционирующие совместно, помогают найти оптимальную структуру приложения. Полезно будет указать, что мно гие шаблоны представляют собой типичные примеры проектирования от контекста. Например: •
шаблон Bridge указывает на необходимость определять классы стороны реали зации в контексте классов, производных от класса Abstraction;
•
шаблон Decorator требует проектировать классыдекораторы в пределах контекста исходного компонента;
•
шаблон Abstract Factory требует определять создаваемые семейства объектов в пределах контекста проблемы в целом, что позволяет установить, экземпляры каких именно классов должны быть реализованы.
Фактически, разработку с использованием интерфейсов и полиморфизма в общем случае можно рассматривать как один из видов проектирования по контексту. Взгля ните на рис. 21.1, который представляет собой повторение рис. 8.4. Обратите внима ние на то, что интерфейс абстрактного класса определяет тот контекст, в пределах которого должны быть реализованы все производные от него классы.
ÐÈÑ. 21.1. Взаимосвязи между анализом общности/изменчивости, уровнями детализации и аб' страктными классами
270
Часть VI. Завершение и начало
Дополнительные замечания При изучении шаблонов проектирования будет весьма полезно рассмотреть сле дующие факторы и концепции. •
Какие реализации этот шаблон скрывает? Это позволит впоследствии изменять их.
•
Какие общности представлены в этом шаблоне? Это поможет идентифици ровать их.
•
Какими обязательствами обладают объекты в этом шаблоне? Это может упростить выполнение декомпозиции обязательств.
•
Каковы отношения между заданными объектами? Это предоставит инфор мацию о движущих силах, представленных этими объектами.
•
Предоставляет ли сам шаблон некоторый пример проектирования по кон тексту? Это позволит лучше понять, почему применение шаблонов является хорошим стилем проектирования.
Резюме В этой главе подведен общий итог обсуждению нового подхода к объектно ориентированному проектированию и показано, как шаблоны проектирования про являются себя в этом. Рассматривая шаблоны проектирования, полезно ответить на вопросы, перечисленные ниже. •
Что инкапсулируют шаблоны проектирования?
•
Как в них используется анализ общности/изменчивости?
•
Как в таких шаблонах выполняется декомпозиция обязательств в проблемной области?
•
Как они определяют отношения между объектами?
•
Как они иллюстрируют проектирование по контексту?
Глава 22
Библиография Эта книга — всего лишь введение. Введение в шаблоны проектирования, объектно ориентированное проектирование и другие более мощные способы проектирования прикладных программных систем. Хотелось бы надеяться, что она обогатила вас оп ределенными навыками, которые позволят вам реализовать на практике этот мощ ный и полезный стиль творческого мышления. Каким может быть следующий этап в освоении идей, изложенных в этой книге? Чтобы помочь вам найти ответ на этот вопрос, в заключительной главе приведен ан нотированный список источников, рекомендуемых к серьезному изучению. В этой главе вы найдете следующее. •
Адреса Webсайтов, дополняющих материал этой книги.
•
Рекомендации для: − желающих углубить свои знания о шаблонах проектирования; − разработчиков на языке Java; − разработчиков на языке С++; − программистов на языке COBOL, которые хотят изучить объектноориен тированную технологию; − всех желающих освоить новую мощную технологию разработки програм много обеспечения, называемую XP (eXtreme Programming — экстремальное программирование).
•
И, наконец, список книг, которые оказали влияние на меня лично и помогли понять, что жизнь — это нечто большее, чем программирование, и что гармоничное развитие личности поможет каждому стать действительно хорошим программистом.
Webсайт поддержки данной книги Webсайт поддержки данной книги расположен по адресу: http://www.netobjectives.com/dpexplained На этом сайте можно найти такую дополнительную информацию о шаблонах про ектирования. •
Примеры программного кода, ответы на часто задаваемые вопросы, материалы дискуссий, организованные по отдельным главам книги.
•
Материалы дискуссий по проблемам рефакторинга.
272
Часть VI. Завершение и начало
•
Сводные данные о шаблонах проектирования, представленные в удобном ссылочном формате.
•
Описание курсов по изучению шаблонов проектирования и других близких тем.
Там же можно найти форму, чтобы отправить нам ваши комментарии и вопросы, касающиеся данной книги. Кроме того, нами издается электронный журнал (ezine), посвященный шаблонам проектирования и общим аспектам объектноориентированного проектирования. Для оформления подписки достаточно послать по электронной почте письмо с вашим именем и названием компании по адресу
[email protected] со словом "subscribe" в поле subject (тема).
Рекомендуемая литература по шаблонам проектирования и объектноориентированной технологии Я рекомендую к прочтению следующие книги и справочные руководства по объ ектноориентированному программированию и языку UML. •
Fowler M. Refactoring: Improving the Design of Existing Code, Reading, MA: Addison Wesley, 2000. Это наиболее полная известная мне работа о рефакторинге.
•
Fowler M., Scott K. UML Distilled: A Brief Guide to the Standard Object Modeling Language, 2nd Edition, Reading, MA: AddisonWesley, 2000. Этот источник я считаю лучшим для изучения языка UML. Данная книга будет полезной для начинающих, а также может использоваться как справочное руководство. Лично я пользуюсь ею постоянно.
•
Meyer B. ObjectOriented Software Construction, Upper Saddle River, N.J.: Prentice Hall, 1997. Невероятно насыщенная книга, написанная одним из наиболее выдающихся умов в нашей области.
Предмет, называемый "шаблонами проектирования", продолжает развиваться и уг лубляться. Изучать его можно на различных уровнях и со многих точек зрения. Я реко мендую следующие книги и справочные пособия, которые помогут вам на этом пути. •
Alexander C., Ishikawa S., Silverstein M. The Timeless Way of Building, New York, NY: Oxford University Press, 1979. Это моя любимая книга как в профессиональном, так и в личном плане. Она одновременно интересна и глубока. Если вы прочитаете только одну книгу из данного списка, это должна быть именно она.
•
Alexander C., Ishikawa S., Silverstein M. A Pattern Language: Towns/Buildings/Construction, New York, NY: Oxford University Press, 1977.
•
Alexander C., Ishikawa S., Silverstein M. Notes on Synthesis of Form, New York, NY: Oxford University Press, 1970.
Глава 22. Библиография
•
273
Coplien J. MultiParadigm Design for C++, Reading, MA: AddisonWesley, 1998. Главы 2–5 этой книги следует прочитать даже тем, кто разрабатывает программное обеспечение на языке, отличном от C++. Данная книга описывает метод анализа общности/изменчивости лучше, чем какаялибо другая. На Web сайте нашей книги доступна интерактивная версия докторской диссертации Джима Коплина, которая представляет собой эквивалент этой книги.
•
Gamma E., Helm R., Johnson R., Vlissides J. Design Patterns: Elements of Reusable Object Oriented Software, Reading, MA: AddisonWesley, 1995. Это издание продолжает оставаться лучшей из всех изданных книг о шаблонах проектирования. Ознакомление с ней является обязательным для каждого разработчика, работающего на языке C++.
•
Gardner K. Cognitive Patterns: ProblemSolving Frameworks for Object Technology, New York, NY: Cambridge University Press, 1998. Это взгляд на шаблоны проектирования с точки зрения науки о познании и искусственного интеллекта. На доктора Гарднера также оказала большое влияние книга Александера, приведенная первой в этом списке.
•
Schmidt D., Stal M., Rohnert H., Busehmann F. PatternOriented Software Architecture, Vol. 2, New York, NY: John Wiley, 2000. Книга об использовании шаблонов проектирования в многопоточных и распре деленных средах.
•
Vlissides J. Pattern Hatching, Reading, MA: AddisonWesley, 1998. Это отличная книга о шаблонах проектирования для подготовленных читателей. Иллюстрирует несколько способов организации совместной работы шаблонов. Предварительно я рекомендую прочитать данную книгу и книгу "банды четырех".
Рекомендуемая литература для программистов на языке Java Когда я только начинал изучение языка Java, моими любимыми книгами были сле дующие. •
Eckel B. Thinking in Java, 2nd Edition, Upper Saddle River, N.J.: Prentice Hall, 2000. Это одна из лучших книг о языке Java из числа изданных. Электронную версию этой книги можно найти по адресу http://www.eckelobjects.com/DownloadSites.
•
Horstmann C. Core Java 2, Volume 1, Fundamentals, Palo Alto: Pearson Education, 1999. (Русский перевод этой книги Кея Хорстманна, Java 2. Библиотека профес сионала. Том 1. Основы, готовится к выпуску издательским домом "Вильямс" в третьем квартале 2002 г.) Еще одна хорошая книга для изучения основ языка Java.
274
Часть VI. Завершение и начало
Каждый язык программирования имеет собственный набор компонентов для реа лизации шаблонов проектирования. Если речь идет о языке Java, то я могу пореко мендовать следующие книги. •
Coad P. Java Design, Upper Saddle River, N.J.: Prentice Hall, 2000. Если вы считаете себя профессиональным разработчиком на языке Java, то я настоятельно рекомендую вам прочитать эту книгу. Здесь описывается большинство принципов и стратегий, которые будут весьма полезны при использовании шаблонов проектирования, несмотря на то, что собственно шаблоны в ней не упоминаются.
•
Grand M. Patterns in Java, Vol. 1, New York, NY: John Wiley, 1998. Если вы программируете на языке Java, эта книга также принесет вам большую пользу. В ней приведены интересные примеры программного кода и исполь зуется язык UML. Однако, по нашему мнению, обсуждение движущих сил и мотиваций в книге "банды четырех" более полезно, чем то, которое представ лено в этой книге. Тем не менее, изучение отличного набора предлагаемых примеров представляет большую ценность, особенно для тех, кто практически работает с языком Java.
•
Информацию об API языка Java для классов Observer и Observable можно найти по адресу http://java.sun.com/j2se/1.3/docs/api/index.html.
Особого внимания в языке Java требует работа с потоками. Для углубленного изу чения этой проблемы я рекомендую следующие издания. •
Hollub A. Taming Java Threads, Berkeley, CA: APress, 2000.
•
Hyde P. Java Thread Programming: The Authoritative Solution, Indianapolis, IN: SAMS, 1999.
•
Lea D. Concurrent Programming in Java: Design Principles and Patterns, Second Edition, Reading, MA: AddisonWesley, 2000.
Рекомендуемая литература для программистов на языке C++ Для тех, кто программирует на языке C++ в среде UNIX, я нахожу необходимым ознакомиться со следующей книгой. •
Stevens W. Advanced Programming in the UNIX Environment, Reading, MA: Addison Wesley, 1992. Это обязательный ресурс для каждого разработчика на языке C++ в среде UNIX.
Рекомендуемая литература для программистов на языке COBOL Программистам, работающим с языком COBOL, которые хотят изучить объектно ориентированное проектирование, можно порекомендовать следующий источник. •
Levey R. Reengineering Cobol with Objects, New York, NY: McGrawHill, 1995.
Глава 22. Библиография
275
Полезная книга для программистов, работающих на языке COBOL и стремя щихся освоить объектноориентированное проектирование.
Рекомендуемая литература для изучения технологии экстремального программирования Для тех, кто хочет ознакомиться с технологией экстремального программирова ния (XP) или повысить свое мастерство в этой области, можно рекомендовать сле дующие два источника. •
http://www.netobjectives.com/xp Наш собственный Webсайт, посвященный экстремальному программированию и включающий статьи и курсы лекций по технологии XP.
•
Beck K. Extreme Programming Explained: Embrace Change, Reading, MA: Addison Wesley, 2000. Это издание заслуживает внимания каждого, кто имеет отношение к разработ ке программного обеспечения, даже если он не планирует применять XP на практике. Я выбрал в этой книге 30 или около того страниц, содержащих, по моему мнению, важнейшие положения этой методологии, и поместил их список на нашем XPсайте.
В настоящее время мы активно разрабатываем собственный метод проектирова ния программного обеспечения, который мы назвали PatternAccellerated Software Engineering. Он интегрирует несколько методов анализа и проектирования. Подроб ности можно найти по адресу http://www.netobjectives.com/pase.
Рекомендуемая литература по общим вопросам программирования Данная книга отражает мою собственную философию, позволяет заглянуть в себя и увидеть, как каждому можно усовершенствоваться самому и улучшить свою работу. •
Hunt A., Thomas D. The Pragmatic Programmer: From Journeyman to Master, Reading, MA: AddisonWesley, 2000. Это одна из тех прекрасных книг, которые я читаю по несколько страниц в день. Когда я встречаю упоминание о чемто, что я уже использую, это укрепля ет мою самооценку. Когда же я нахожу чтото новое, то получаю прекрасную возможность поучиться.
Наши любимые книги Лично я полагаю, что лучший разработчик программного обеспечения — это не тот, кто живет и дышит одним лишь программированием. Пожалуй, именно способность думать и слушать, целостность и глубина личности, а также творческий подход — вот то, что, по моему мнению, присуще действительно хорошему разработчику. Такой человек
276
Часть VI. Завершение и начало
более коммуникабелен. Он способен находить полезные идеи в других дисциплинах (мы, например, успешно применили в своей практике достижения из области архитек туры и антропологии). Системы, созданные такими разработчиками, ориентированы на людей, для которых эти системы, собственно, и предназначаются. Многие студенты спрашивают нас о том, что мы любим читать, что оказало влия ние на формирование наших взглядов и развитие наших личностей. Вот то, что мож но было бы ответить на этот вопрос. Алан рекомендует следующие издания. •
Grieve B. The Blue Day Book: A Lesson in Cheering You Up, Kansas City, KA: Andrews McMeel Publishing, 2000. Это забавная и восхитительная книга. Обращайтесь к ней всякий раз, когда чувствуете себя не в своей тарелке.
•
Hill N. Think and Grow Rich, New York, NY: Ballantine Books, 1960. "Богатство" означает здесь не только деньги — данное слово обозначает все то, чем вы желаете обладать в своей жизни. Эта книга оказала большое влияние на мой личный успех и успех моего бизнеса.
•
Kundtz D. Stopping: How to Be Still When You Have to Keep Going, Berkeley, CA: Conari Press, 1998. Книга учит, как перестать быть трудоголиком. Она является прекрасным руководством в том, как отказаться от постоянного беспокойства и научиться наслаждаться жизнью, всегда добиваясь поставленных целей.
•
Mandino O. The Greatest Salesman in the World, New York, NY: Bantam Press, 1968. Я прочитал и применил положения из этой книги на практике несколько лет назад. Это помогло мне найти в жизни тот путь, который я всегда хотел найти. Если вы обратитесь к данной книге, я настоятельно рекомендую делать все то, о чем говорится в свитках Хафида, а не ограничиваться лишь их прочтением (вы поймете, что я имею в виду, когда будете читать эту книгу).
•
Pilzer P. Unlimited Wealth: The Theory and Practice of Economic Alchemy, Crown Publish ers, 1990. Эти книга представляет как новую парадигму ресурсов и источников благосостояния, так и рекомендации, как ими можно воспользоваться. Она будет крайне полезна каждому, кто живет в наш век информации.
•
Remen R. My Grandfather’s Blessings: Stories of Strength, Refuge, and Belonging, New York, NY: Riverhead Books, 2000. Прекрасная книга, отражающая молитвы каждого достойного человека.
Джим рекомендует следующие издания. •
Buzan T., Buzan B. The Mind Map Book: How to Use Radiant Thinking to Maximize Your Brain’s Untapped Potential, New York, NY: Dutton Books, 1994. Эта книга оказала революционное влияние на мой стиль преподавания, общения с людьми, мышления и ведения записей. Невероятно мощная техника. Я пользуюсь ей ежедневно.
Глава 22. Библиография
•
277
Cahill T. How the Irish Saved Civilization, New York, NY: Doubleday, 1995. Если в ваших жилах течет хоть немного ирландской крови, вы почувствуете гордость.
•
Dawson C. Religion and the Rise of Western Culture, New York, NY: Doubleday, 1950. Здесь описано, как религия направляла развитие западной цивилизации и способствовала удержанию в узде "варварства, которое скрывается внутри каждого из нас". Излагаются важные взгляды на научное мышление.
•
Jensen B. Simplicity: The New Competitive Advantage in a World of More, Better, Faster, Cambridge, MA: Perseus Books, 2000. Революция в мышлении и управлении знаниями. Системы проектирования будут проще в использовании для людей, если особенности человека были приняты во внимание при разработке процессов и технологий.
•
Lingenfelter S. Transforming Culture, Grand Rapids: Baker Book House, 1998. Модель для понимания особенностей культур через теорию социальных игр.
•
Spradely J. P. The Ethnographic Interview, New York, NY: Harcourt Brace Jovanovich College Publishers, 1979. Эту книгу следует прочесть каждому, кто хочет научиться брать интервью. Классический учебник, знакомый всем студентам, изучающим антропологию.
•
Wiig K. Knowledge Management Methods, Dallas, AL: Schema Press, 1995. Виртуальная энциклопедия методов, помогающих организациям более эф фективно использовать информационные ресурсы.