Главная страница

Объектно-ориентированное программирование. Объектно-ориентированное программирование в действии. Объектноориентированное программирование


Скачать 5.29 Mb.
НазваниеОбъектноориентированное программирование
АнкорОбъектно-ориентированное программирование
Дата05.09.2022
Размер5.29 Mb.
Формат файлаpdf
Имя файлаОбъектно-ориентированное программирование в действии.pdf
ТипДокументы
#662573
страница3 из 23
1   2   3   4   5   6   7   8   9   ...   23
Глава 2 : Объектно- ориентированное проектирование
Когда программисты спрашивают друг друга: «Чем же, в конце концов, является объектно-ориентированное программирование?», ответ чаще всего подчеркивает синтаксические свойства таких языков, как C++ или Object Pascal, по сравнению с их более ранними, не объектно-ориентированными версиями, то есть C или Pascal. Тем самым обсуждение обычно переходит на такие предметы, как классы и наследование, пересылка сообщений, виртуальные и статические методы. Но при этом опускают наиболее важный момент в объектно-ориентированном программировании, который не имеет ничего общего с вопросами синтаксиса.
Работа на объектно-ориентированном языке (то есть на языке, который поддерживает наследование, пересылку сообщений и классы) не является ни необходимым, ни достаточным условием для того, чтобы заниматься объектно-ориентированным программированием. Как мы подчеркнули в главе 1, наиболее важный аспект в ООП — техника проектирования, основанная на выделении и распределении обязанностей. Она была названа проектированием на основе обязанностей или проектированием на основе ответственности (responsibility-driven design) [Wirfs-Brock 1989b, Wirfs-Brock 1990].
2.1. Ответственность подразумевает невмешательство
Как может констатировать любой, кто помнит себя ребенком (или кто воспитывает детей), ответственность — обоюдоострый меч. Когда вы заставляете какой-либо объект (является ли он ребенком, или программной системой) быть ответственным за конкретные действия, вы ожидаете с его стороны определенного поведения, по крайней мере пока не нарушены правила. Но, в равной степени важно, что ответственность подразумевает определенный уровень независимости или невмешательства. Если вы скажете своей дочке, что она отвечает за уборку своей комнаты, вы, как правило, не стоите рядом с ней и не наблюдаете за выполнением работы — это противоречило бы понятию ответственности.
Вместо этого вы рассчитываете, что после выдачи распоряжения будет получен желаемый результат.
Аналогично в случае примера с цветами из главы 1, когда я передаю запрос хозяйке цветочного магазина с просьбой переслать цветы, я не задумываюсь о том, как мой запрос будет обслужен. Хозяйка цветочного магазина, раз уж она взяла на себя ответственность, действует без вмешательства с моей стороны.
Разница между традиционным и объектно-ориентированным программированием во многих отношениях напоминает различие между активным наблюдением за тем, как ребенок выполняет работу, и передачей (делегированием) ребенку ответственности за эту деятельность. Традиционное программирование основывается в основном на приказаниях чему-либо сделать что-то — к примеру, модифицировать запись или обновить массив данных. Тем самым фрагмент кода привязан посредством передачи управления и соглашений о структуре данных ко многим другим разделам программной системы. Такие зависимости могут возникать через использование глобальных переменных, значений указателей или попросту из-за неправильного применения или зависимой реализации других фрагментов кода. Проектирование, основанное на ответственности, старается
PDF created with pdfFactory Pro trial version www.pdffactory.com
отсекать эти связи или по крайней мере сделать их настолько слабыми, насколько это возможно.
С первого взгляда идея кажется не более сложной, чем понятия маскировки информации и модульности, которые важны при программировании в целом, в том числе и при использовании традиционных языков. Но проектирование, основанное на распределении ответственности, поднимает маскировку данных с уровня техники до уровня искусства.
Принцип маскировки информации становится жизненно важным при переходе от программирования «в малом» к программированию «в большом».
Одно из основных преимуществ ООП наблюдается, когда программные подсистемы многократно используются в разных проектах. Например, программа, управляющая моделированием (подобно той, которую мы будем разрабатывать в главе 6), может имитировать как движение бильярдных шаров по столу, так и перемещение рыбы в цистернах. Эта способность кода к многократному использованию неявным образом подразумевает, что в программном обеспечении почти не должно быть проблемно- зависимых компонентов — оно должно полностью делегировать ответственность за специфичное поведение к фрагментам конкретной системы. Умению создавать подобный многократно используемый код не так просто научиться — оно требует опыта, тщательного исследования учебных примеров (парадигм, в исходном значении этого слова) и использования языков программирования, в которых такое делегирование является естественным и легко выражаемым. В последующих главах мы приведем несколько примеров.
2.2. Программирование «в малом» и «в большом»
О разработке индивидуального проекта часто говорят как о программировании «в малом», а о реализации большого проекта как о программировании «в большом».
Для программирования «в малом» характерны следующие признаки:

Код разрабатывается единственным программистом или, возможно, небольшой группой программистов. Отдельно взятый индивидуум может понять все аспекты проекта, от и до.

Основная проблема при разработке состоит в проектировании программы и написании алгоритмов для решения поставленной задачи.
С другой стороны, программирование «в большом» наделяет программный проект следующими свойствами:

Программная система разрабатывается большой командой программистов. При этом одна группа может заниматься проектированием (или спецификацией) системы, другая — осуществлять написание кода отдельных компонент, а третья — объединять фрагменты в конечный продукт. Нет единственного человека, который знал бы все о проекте.

Основная проблема в процессе разработки программного обеспечения — управление проектом и обмен информацией между группами и внутри групп.
В то время как начинающий студент обычно знакомится с программированием «в малом», особенности многих объектно-ориентированных языков наилучшим образом понимаются при встрече с проблемами, типичными для программирования «в большом». Тем самым
PDF created with pdfFactory Pro trial version www.pdffactory.com
некоторое представление о трудностях, возникающих при разработке больших систем, является полезным для понимания ООП.
2.3. Почему надо начинать с функционирования?
Из-за чего процесс проектирования начинают с анализа функционирования или поведения системы? Простой ответ состоит в том, что поведение системы обычно известно задолго до остальных ее свойств.
Предшествовавшие методы разработки программного обеспечения концентрировались на таких идеях, как характеристики основных данных или же общая структура вызова функций. Но структурные элементы приложения могут быть определены только после интенсивного анализа задачи. Соответственно процесс формальной спецификации часто заканчивался созданием документа, который не понимали ни программисты, ни клиенты.
Но поведение — это нечто, что может быть описано в момент возникновения идеи программы и (в отличие от формальной спецификации системы) выражено в терминах, имеющих значение как для программиста, так и для клиента.
Мы проиллюстрируем проектирование на основе обязанностей (или RDD- проектирование — Responsibility-Driven-Design) на учебном примере.
2.4. Учебный пример: проектирование на основе обязанностей
Представьте себе, что вы являетесь главным архитектором программных систем в ведущей компьютерной фирме. В один прекрасный день ваш начальник появляется в офисе с идеей, которая, как он надеется, будет очередным успехом компании. Вам поручают разработать систему под названием Interactive Intelligent Kitchen Helper
(Интерактивный разумный кухонный помощник) (рис. 2.1)
Рис. 2.1. Внешний вид программы «Интерактивный разумный кухонный помощник»
Задача, поставленная перед вашей командой программистов, сформулирована в нескольких скупых словах (написанных на чем-то, что оказывается использованной обеденной салфеткой, причем почерком, принадлежащим вашему начальнику).
PDF created with pdfFactory Pro trial version www.pdffactory.com

2.4.1. Интерактивный разумный кухонный помощник
Программа «Интерактивный разумный кухонный помощник» (Interactive Intelligent
Kitchen Helper, IIKH) предназначена для персональных компьютеров. Ее цель — заменить собой набор карточек с рецептами, который можно встретить почти в каждой кухне. Но помимо ведения базы данных рецептов, IIKH помогает в планировании питания на длительный период — например, на неделю вперед. Пользователь программы IIKH садится за компьютер, просматривает базу данных рецептов и в диалоговом режиме определяет меню на весь требуемый период.
Как это обычно бывает при первоначальном описании многих программных систем, спецификация для IIKH в значительной степени двусмысленна в отношении ряда важных пунктов. Кроме того, проект и разработка программной системы IIKH потребует совместных усилий нескольких программистов. Тем самым первоначальная цель команды разработчиков состоит в том, чтобы сделать ясными двусмысленные места и наметить разбиение проекта на компоненты, с тем чтобы распределить их между отдельными членами команды.
Краеугольным камнем в ООП является характеристика программного обеспечения в терминах поведения, то есть в терминах действий, которые должны быть выполнены. Мы увидим воплощение в жизнь этого принципа на многих уровнях процесса разработки
IIKH. Первоначально команда попытается охарактеризовать на очень высоком уровне абстрагирования поведение приложения в целом. Затем она займется описанием поведения различных программных подсистем. И только тогда, когда все аспекты поведения будут выделены и описаны, программисты-разработчики приступят к этапу кодирования. В следующих разделах мы будем отслеживать этапы работы команды программистов при создании данного приложения.
2.4.2. Работа по сценарию
Первой задачей является уточнение спецификации. Как мы уже заметили, исходные спецификации почти всегда двусмысленны и непонятны во всем, кроме наиболее общих положений. На этом этапе ставится несколько целей. Одной из них является лучшее понимание и ощущение того, чем будет конечный продукт (принцип «посмотри и почувствуй» для проектирования системы). Затем эта информация может быть возвращена назад клиенту (в данном случае вашему начальнику), чтобы увидеть, находится ли она в соответствии с исходной концепцией. Вероятно и, возможно, неизбежно то, что спецификации для конечного продукта будут изменяться во время разработки программной системы, и поэтому важно, чтобы проект мог легко включать в себя новые идеи, а также чтобы потенциально возможные исправления были выявлены как можно раньше — см. раздел 2.6.2. «Готовность к изменениям». На этом же этапе проводится обсуждение структуры будущей программной системы. В частности, действия, осуществляемые программной системой, разбиваются на компоненты.
2.4.3. Идентификация компонент
Создание сложной физической системы, подобной зданию или двигателю автомобиля, упрощается с помощью разбиения проекта на структурные единицы. Точно так же разработка программного обеспечения облегчается после выделения отдельных компонент программы. Компонента — это просто абстрактная единица, которая может выполнять определенную работу (то есть иметь определенные обязанности). На этом этапе нет необходимости знать в точности то, как задается компонента или как она будет
PDF created with pdfFactory Pro trial version www.pdffactory.com
выполнять свою работу. Компонента может в конечном итоге быть преобразована в отдельную функцию, структуру или класс, или же в совокупность других компонент
(шаблон). На этом уровне разработки имеются две важные особенности:

компонента должна иметь небольшой набор четко определенных обязанностей;

компонента должна взаимодействовать с другими компонентами настолько слабо, насколько это возможно.
Позднее мы поговорим о второй особенности подробнее. Сейчас мы просто занимаемся определением обязанностей компонент.
2.5. CRC-карточка — способ записи обязанностей
Чтобы выявить отдельные компоненты и определить их обязанности, команда программистов прорабатывает сценарий системы. То есть воспроизводится запуск приложения, как если бы оно было уже готово. Любое действие, которое может произойти, приписывается некоторой компоненте в качестве ее обязанности.
В качестве составной части этого процесса полезно изображать компоненты с помощью небольших индексных карточек. На лицевой стороне карточки написаны имя компоненты, ее обязанности и имена других компонент, с которыми она должна взаимодействовать.
Такие карточки иногда называются CRC-карточками от слов Component, Responsibility,
Collaborator (компонента, обязанность, сотрудники) [Beck 1989]. По мере того как для компонент выявляются обязанности, они записываются на лицевой стороне CRC- карточки.
2.5.1. Дайте компонентам физический образ
При проработке сценария полезно распределить CRC-карточки между различными членами проектной группы. Человек, имеющий карточку, которая представляет определенную компоненту, записывает ее обязанности и исполняет функции заменителя программы во время моделирования сценария. Он описывает действия программной системы, передавая «управление» следующему члену команды, когда программная система нуждается в услугах других компонент.
PDF created with pdfFactory Pro trial version www.pdffactory.com

Преимущество CRC-карточек в том, что они широко доступны, недороги и с них можно стирать информацию. Это стимулирует экспериментирование, поскольку альтернативные проекты могут быть испробованы, изучены и отброшены с минимальными затратами.
Физическое разделение карточек стимулирует интуитивное понимание важности логического разделения компонент, что помогает сделать упор на связности внутри модулей и зацеплении между модулями (которые вкратце будут описаны ниже).
Небольшой размер индексной карточки служит хорошей оценкой примерной сложности отдельного фрагмента — компоненты, которой приписывается больше задач, чем может поместиться на ее карточке, вероятно, является излишне сложной, и должно быть найдено более простое решение. Может быть, следует пересмотреть разделение обязанностей или разбить компоненту на две.
2.5.2. Цикл «что/кто»
Как мы заметили в начале нашего обсуждения, выделение компонент производится во время процесса мысленного представления работы системы. Часто это происходит как цикл вопросов «что/кто». Во-первых, команда программистов определяет: что требуется делать? Это немедленно приводит к вопросу: кто будет выполнять действие? Теперь программная система в значительной мере становится похожа на некую организацию, скажем, карточный клуб. Действия, которые должны быть выполнены, приписываются некоторой компоненте в качестве ее обязанностей.
Популярная наклейка от жевательной резинки утверждает, что время от времени может и должно спонтанно происходить необъяснимое. (Наклейка от жевательной резинки использует чуть более короткую фразу.) Мы знаем, однако, что в реальной жизни это вряд ли справедливо. Если происходит некоторое действие, должен быть и агент, которому предписано выполнять это действие. Точно так же как в карточном клубе каждое действие приписано определенным индивидуумам, при организации объектно-ориентированной программы каждое действие является обязанностью некоторой компоненты. Секрет хорошего объектно-ориентированного проекта состоит в том, чтобы установить агента для каждого действия.
2.5.3. Документирование
На этом этапе следует начать разработку документации. Два документа должны являться существенными составными частями любой программной системы: руководство пользователя и проектная документация системы. Работа над каждым из них может начинаться до того, как написана первая строчка программного кода.
Руководство пользователя описывает взаимодействие с системой с точки зрения пользователя. Это — отличное средство проверки того, что концепция команды программистов-разработчиков соответствует мнению клиента. Поскольку решения, принятые в процессе проработки сценария, соответствуют действиям, которые потребуются от пользователя при использовании программы, то написание руководства пользователя естественным образом увязывается с процессом проработки сценария.
Перед тем как написан какой-либо кусок кода, мышление команды программистов во многом похоже на сознание конечных пользователей. То есть именно на этом этапе разработчики могут наиболее легко предугадать те вопросы, на которые новичку- пользователю понадобятся ответы.
PDF created with pdfFactory Pro trial version www.pdffactory.com

Второй существенный документ — проектная документация. Она протоколирует основные решения, принятые при планировании программы, и, следовательно, должна создаваться в тот момент, когда эти решения еще свежи в памяти создателей, а не годом позже. Зачастую много проще написать общее глобальное описание программной системы в начале разработки. Затем, естественно, совершается переход к уровню отдельных компонент или модулей.
Хотя в равной мере важно документировать программу на уровне модулей, слишком большое внимание к деталям организации каждого фрагмента сделает сложным для последующих программистов, осуществляющих сопровождение программной системы, формирование общей картины приложения.
CRC-карточки являются одним из видов проектной документации, но многие другие важные решения не отражены в них. Аргументы за и против каждой важной альтернативы при проектировании должны записываться, равно как и факторы, которые повлияли на конечное решение. Должен вестись протокол или дневник хода проекта. Как руководство пользователя, так и проектная документация уточняются и изменяются в процессе работы в точном соответствии с тем, как модифицируется собственно программа.
2.6. Компоненты и поведение
Вернемся к программе IIKH. Команда разработчиков решает, что когда система начинает работу, пользователь видит привлекательное информационное окно (см. рис. 2.1).
Ответственность за его отображение приписана компоненте, названной Greeter.
Некоторым, пока еще не определенным образом (с помощью всплывающих меню, кнопок, нажатия на клавиши клавиатуры или использования сенсорного экрана) пользователь выбирает одно из нескольких действий.
Первоначально планируется только пять действий:
1. Просмотреть базы данных с рецептами, но без ссылок на какой-то конкретный план питания.
2. Добавить новый рецепт в базу данных.
3. Редактировать или добавить комментарии к существующему рецепту.
4. Пересмотреть существующий план в отношении некоторых продуктов.
5. Создать новый план питания.
Эти действия естественным образом разбиваются на две группы. Первые три действия связаны с базой данных рецептов, последние два — с планированием питания. В результате команда принимает следующее решение: создать компоненты, соответствующие этим двум обязанностям. Продолжая прорабатывать сценарий, планирование питания на время игнорируем и переходим к уточнению действий, связанных с компонентой Recipe Database. На рис. 2.2 показан
PDF created with pdfFactory Pro trial version www.pdffactory.com

Рис. 2.2. CRC-карточка для класса заставки Greeter первоначальный вид CRC-карточки для компоненты Greeter.
В широком смысле обязанность компоненты, работающей с базой данных, — просто поддерживать записи с рецептами.
Мы уже выделили три аспекта этой компоненты: Recipe Database должна обеспечивать просмотр библиотеки существующих рецептов, редактирование рецептов, включение новых рецептов в базу данных.
2.6.1. Отложенные решения
В конце концов придется решить, как пользователь станет просматривать базу данных.
Например, должен ли он сначала входить в список категорий таких, как «супы», «салаты»,
«горячие блюда», «десерты»?
С другой стороны, может ли пользователь задавать ключевые слова для ограничения области поиска, включая список ингредиентов («миндаль», «клубника», «сыр»)? Или же использовать список предварительно заданных ключевых слов («любимые пирожные
Боба»)? Следует ли применять полосы прокрутки (scroll bars) или имитировать закладки в виртуальной книжке? Размышлять об этих предметах доставляет удовольствие, но важно то, что нет необходимости принимать конкретные решения на данном этапе проектирования (см. раздел 2.6.2. «Готовность к изменениям»). Поскольку они влияют только на отдельную компоненту и не затрагивают функционирование остальных частей системы, то все, что надо для продолжения работы над сценарием, — это информация о том, что пользователь может выбрать конкретный рецепт.
2.6.2. Готовность к изменениям
Как было сказано, единственное, что является постоянным в жизни, — неизбежность изменений. То же самое справедливо для программного обеспечения. Независимо от того как тщательно вы пытаетесь разработать исходные спецификации и проект программной системы, почти наверняка изменения в желаниях или потребностях пользователя будут вызывать соответствующие исправления в программе (зачастую в течение всего жизненного цикла системы). Разработчики должны предвидеть это и планировать свои действия соответствующим образом:
PDF created with pdfFactory Pro trial version www.pdffactory.com


Главная цель состоит в том, что изменения должны затрагивать как можно меньше компонент. Даже принципиальные новшества во внешнем виде или функционировании приложения должны затронуть один или два фрагмента кода.

Старайтесь предсказать наиболее вероятные источники изменений и позвольте им влиять на возможно меньшее количество компонент программы. Наиболее общими причинами изменений являются интерфейс, форматы обмена информацией, вид выходных данных.

Старайтесь изолировать и уменьшить зависимость программного обеспечения от аппаратуры. Например, интерфейс просмотра рецептов в вашем приложении может частично зависеть от аппаратного обеспечения системы, на которой работает программа. Последующие версии должны переноситься на различные платформы.
Хороший проект должен предвидеть подобное изменение.

Уменьшение количества связей между фрагментами программы снижает взаимозависимость между ними и увеличивает вероятность того, что каждую компоненту удастся изменить с минимальным воздействием на другие.

Аккуратно заносите записи о процессе разработке и о дискуссиях, проводившихся вокруг принципиальных решений, в проектную документацию. Почти наверняка коллектив, отвечающий за сопровождение системы и разработку следующих версий, будет отличаться от команды, разработавшей первоначальную версию программы. Проектная документация позволит в последствии узнать о мотивах принятых решений и поможет избежать затрат времени на обсуждение вопросов, которые уже были разрешены.
2.6.3. Продолжение работы со сценарием
Каждый кулинарный рецепт будет идентифицироваться с конкретной программной компонентой. Если рецепт выбран пользователем, управление передается объекту, ассоциированному с рецептом. Рецепт должен содержать определенную информацию. В основном она состоит из списка ингредиентов и действий, необходимых для трансформации составляющих в конечный продукт. Согласно нашему сценарию компонента-рецепт должна также выполнять и другие действия. Например, она будет отображать рецепт на экране. Пользователь получит возможность снабжать рецепт аннотацией, а также менять список ингредиентов или набор инструкций. С другой стороны, пользователь может потребовать распечатать рецепт. Все эти действия являются обязанностью компоненты Recipe. (Временно мы продолжим описание Recipe как отдельно взятого объекта. На этапе проектирования мы можем рассматривать его как прототип многочисленных объектов-рецептов. Позднее мы вернемся к обсуждению альтернативы «одиночная компонента — множество компонент».)
Определив вчерне, как осуществить просмотр базы данных, вернемся к ее блоку управления и предположим, что пользователь хочет добавить новый рецепт. В блоке управления базой данных неким образом определяется, в какой раздел поместить новый рецепт (в настоящее время детали нас не интересуют), запрашивается имя рецепта и выводится окно для набора текста. Таким образом, эту задачу естественно отнести к той компоненте, которая отвечает за редактирование рецептов.
Вернемся к блоку Greeter. Планирование меню, как вы помните, было поручено программной компоненте Plan Manager. Пользователь должен иметь возможность сохранить существующий план. Следовательно, компонента Plan Manager может запускаться либо в результате открытия уже существующего плана, либо при создании нового. В последнем случае пользователя необходимо попросить ввести временные интервалы (список дат) для нового плана. Каждая дата ассоциируется с отдельной
PDF created with pdfFactory Pro trial version www.pdffactory.com
компонентой типа Date. Пользователь может выбрать конкретную дату для детального исследования — в этом случае управление передается соответствующей компоненте Date.
Компонента Plan Manager должна уметь распечатывать меню питания на планируемый период. Наконец, пользователь может попросить компоненту Plan Manager сгенерировать список продуктов на указанный период.
В компоненте Date хранятся следующие данные: список блюд на соответствующий день и
(необязательные) текстовые комментарии, добавленные пользователем (информация о днях рождения, юбилейные даты, напоминания и т. д.). Что должна уметь компонента?
Прежде всего выводить на экран вышеперечисленные данные. Кроме того, в ней должна быть предусмотрена функция печати. В случае желания пользователя более детально ознакомиться с тем или иным блюдом, следует передать управление компоненте Meal.
В компоненте Meal хранится подробная информация о блюде. Не исключено, что у пользователя окажется несколько рецептов одного блюда. Поэтому необходимо добавлять и удалять рецепты. Кроме того, желательно иметь возможность распечатать информацию о том или ином блюде. Разумеется, должен быть обеспечен вывод на экран.
Пользователю, вероятнее всего, захочется обратиться к еще каким-нибудь рецептам — следовательно, необходимо наладить контакт с базой данных рецептов. Раз так, компоненты Meal и база данных должны взаимодействовать между собой.
Далее команда разработчиков продолжает исследовать все возможные сценарии.
Необходимо предусмотреть обработку исключительных ситуаций. Например, что происходит, если пользователь задает ключевое слово для поиска рецепта, а подходящий рецепт не найден? Как пользователь сможет прервать действие (например, ввод нового рецепта), если он не хочет продолжать дальше? Все это должно быть изучено.
Ответственность за обработку подобных ситуаций следует распределить между компонентами.
Изучив различные сценарии, команда разработчиков в конечном счете решает, что все действия могут быть надлежащим образом распределены между шестью компонентами
(рис. 2.3). Компонента Greeter взаимодействует только с Plan Manager и Recipe Database.
Компонента Plan Manager «зацепляется» только с Date, а та в свою очередь — с Meal.
Компонента Meal обращается к Recipe Manager и через посредство этого объекта к конкретным рецептам.
Рис. 2.3. Взаимосвязь между компонентами программы IIKH
2.6.4. Диаграммы взаимодействия
Схема, изображенная на рис. 2.3, хорошо подходит для отображения статических связей между компонентами. Но она не годится для описания динамического взаимодействия во время выполнения программы. Для этих целей используются диаграммы взаимодействия.
PDF created with pdfFactory Pro trial version www.pdffactory.com

На рис. 2.4 показана часть диаграммы взаимодействия для программы IIKH. Время движется сверху вниз. Каждая компонента представлена вертикальной линией.
Сообщение от одной компоненты к другой изображается горизонтальной стрелкой между вертикальными линиями. Возврат управления (и, возможно, результата) в компоненту представлен аналогичной стрелкой. Некоторые авторы используют для этой цели пунктирную стрелку. Комментарии справа от рисунка более подробно объясняют взаимодействие.
Благодаря наличию оси времени диаграмма взаимодействия лучше описывает последовательность событий в процессе работы программы. Поэтому диаграммы взаимодействия являются полезным средством документирования для сложных программных систем.
Рис. 2.4. Пример диаграммы взаимодействия
2.7. Компоненты программы
В этом разделе мы исследуем компоненты программы более подробно. За этим внешне простым понятием прячется много нетривиальных аспектов (что, впрочем, справедливо почти для всех понятий за исключением совсем элементарных).
2.7.1. Поведение и состояние
Мы уже видели, что компоненты характеризуются своим поведением, то есть тем, что они должны делать. Но компоненты также хранят определенную информацию. Возьмем, к примеру, компоненту-прототип Recipe из программы IIKH. Можно представить ее себе как пару «поведение—состояние».

Поведение компоненты — это набор действий, ею осуществляемых. Полное описание поведения компоненты иногда называют протоколом. Например, в протоколе, поддерживаемом компонентой Recipe, значится, что она осуществляет редактирование инструкций по приготовлению блюд, отображает их на экране, распечатывает рецепты.

Говоря о состоянии компоненты, имеют в виду ее внутреннее содержание. Для
Recipe состояние включает в себя ингредиенты и инструкции по приготовлению блюд. Состояние не является статическим и может изменяться с течением времени.
Например, редактируя текст, пользователь изменяет состояние рецепта.
Не все компоненты обязаны иметь состояние. Например, у компоненты Greeter, вероятно, не будет внутренних данных, поскольку ей ни к чему помнить какую-либо информацию.
Однако большинство компонент характеризуется и поведением, и состоянием.
2.7.2. Экземпляры и классы
PDF created with pdfFactory Pro trial version www.pdffactory.com

Разделив понятия о состоянии и поведении, мы можем теперь затронуть тему, которую ранее избегали. Вероятно, в реальном приложении будет много рецептов. Однако все они будут вести себя одинаково. Отличается только состояние: список ингредиентов и инструкций по приготовлению. На ранних стадиях разработки нас должно интересовать поведение, общее для всех рецептов. Детали, специфические для отдельного рецепта, не важны.
Термин класс используется для описания множества объектов с похожим поведением. Мы увидим в последующих главах, что класс применяется как синтаксический механизм почти во всех объектно-ориентированных языках. Конкретные представители класса называются экземплярами. Заметим, что поведение ассоциировано с классом, а не с индивидуальными представителями. То есть все экземпляры класса воспринимают одни и те же команды и выполняют их сходным способом. С другой стороны, состояние является индивидуальным. Мы видим это на примере различных экземпляров класса Recipe. Все они могут выполнять одни и те же действия (редактирование, вывод на экран, печать), но используют различные данные. Мы рассмотрим концепцию класса более подробно в главе 3.
2.7.3. Зацепление и связность
Двумя важными понятиями при разработке программ являются зацепление (coupling) и связность (cohesion). Связность — это мера того, насколько отдельная компонента образует логически законченную, осмысленную единицу. Высокая связность достигается объединением в одной компоненте соотносящихся (в том или ином смысле) друг с другом функций. Наиболее часто функции оказываются связанными друг с другом при необходимости иметь доступ к общим данным. Именно это объединяет разные части компоненты Recipe.
С другой стороны, зацепление характеризует взаимозависимость между компонентами программы. В общем случае желательно уменьшить степень зацепления как только возможно, поскольку связи между компонентами программы препятствуют их модификации и мешают дальнейшей разработке или повторному использованию в других программах.
В частности, зацепление возникает, если одна программная компонента должна иметь доступ к данным (состоянию) другой компоненты. Следует избегать подобных ситуаций.
Возложите обязанность осуществлять доступ к данным на компоненту, которая ими владеет. Например, в случае с нашим проектом кажется, что ответственность за редактирование рецептов должна лежать на компоненте Recipe Database, поскольку именно в ней впервые возникает в этом необходимость. Но тогда объект Recipe Database должен напрямую манипулировать состоянием отдельных рецептов (их внутренними данными: списком ингредиентов и инструкциями по приготовлению). Лучше избежать столь тесного зацепления, передав обязанность редактирования непосредственно рецепту.
Более подробно о связности и зацеплении, а также о соответствующей технике программирования рассказывается в главе 17.
2.7.4. Интерфейс и реализация модуля — принципы Парнаса
Идея характеризации компонент программы через их поведение имеет одно чрезвычайно важное следствие. Программист знает, как использовать компоненту, разработанную другим программистом, и при этом ему нет необходимости знать, как она реализована.
PDF created with pdfFactory Pro trial version www.pdffactory.com

Например предположим, что шесть компонент приложения IIKH создаются шестью программистами. Программист, разрабатывающий компоненту Meal, должен обеспечить просмотр базы данных с рецептами и выбор отдельного рецепта при составлении блюда.
Для этого компонента Meal просто вызывает функцию browse, привязанную к компоненте
Recipe Database. Функция browse возвращает отдельный рецепт Recipe из базы данных.
Все это справедливо вне зависимости от того, как конкретно реализован внутри Recipe
Database просмотр базы данных.
Мы прячем подробности реализации за фасадом интерфейса. Происходит маскировка информации. Говорят, что компонента инкапсулирует поведение, если она умеет выполнять некоторые действия, но подробности, как именно это делается, остаются скрытыми. Это естественным образом приводит к двум различным представлениям о программной системе. Вид со стороны интерфейса — это лицевая сторона; ее видят другие программисты. В интерфейсной части описывается, что умеет делать компонента.
Вид со стороны реализации — это «изнанка», видимая только тем, кто работает над конкретной компонентой. Здесь определяется, как компонента выполняет задание.
Разделение интерфейса и реализации является, возможно, наиболее важной идеей в программировании. Ее непросто объяснить студентам. Маскировка информации имеет значение в основном только в контексте программных проектов, в которых занято много людей. При таких работах лимитирующим фактором часто является не количество привлеченных людей, а частота обмена информацией и данными как между программистами, так и между разрабатываемыми ими программными системами. Как будет показано ниже, компоненты часто разрабатываются параллельно разными программистами в изоляции друг от друга.
Интерес к многократному использованию программных компонент общего назначения в разных проектах возрастает. Для осуществления подобного, связи между различными частями системы должны быть минимальны и прозрачны.
Как мы уже отмечали в предыдущей главе, эти идеи были сформулированы специалистом по информатике Дэвидом Парнасом в виде правил, часто называемых принципами
Парнаса:

Разработчик программы должен предоставить пользователю всю информацию, которая нужна для эффективного использования приложения, и ничего кроме этого.

Разработчик программного обеспечения должен знать только требуемое поведение компоненты и ничего кроме этого.
Следствие принципа отделения интерфейса от реализации состоит в том, что программист может экспериментировать с различными алгоритмами, не затрагивая остальные компоненты программы.
2.8. Формализация интерфейса
Продолжим разработку программы IIKH. На следующих нескольких этапах уточняется описание компонент. Сначала формализуются способы взаимодействия.
Следует определить, как будет реализована каждая из компонент. Компонента, характеризуемая только поведением (не имеющая внутреннего состояния), может быть
PDF created with pdfFactory Pro trial version www.pdffactory.com
оформлена в виде функции. Например, компоненту, заменяющую все заглавные буквы в текстовой строке на строчные, разумнее всего сделать именно так. Компоненты с многими функциями лучше реализовать в виде классов. Каждой обязанности, перечисленной на
CRC-карточке компоненты, присваивается имя. Эти имена станут затем названиями функций или процедур. Вместе с именами определяются типы аргументов, передаваемых функциям. Затем описывается (вся) информация, содержащаяся внутри компоненты. Если компоненте требуются некие данные для выполнения конкретного задания, их источник
(аргумент функции, глобальная или внутренняя переменная) должен быть явно описан.
2.8.1. Выбор имен
Имена, связанные с различными действиями, должны тщательно подбираться. Шекспир сказал, что переименование не меняет сути объекта
1
, но определенно не все имена будут вызывать в воображении слушателя одинаковые мысленные образы.
Как давно известно правительственным чиновникам, неясные и используемые в переносном смысле имена придают отпугивающий вид даже простейшим действиям.
Выбор удобных имен необычайно важен. Они должны быть внутренне совместимы, значимы, коротки, содержательны. Часто значительное время тратится на нахождение правильного набора имен для выполняемых заданий и объектов. Являясь далеко не бесплодным и не бесполезным процессом, надлежащий выбор имен на ранней стадии проектирования значительно упрощает и облегчает дальнейшую разработку.
Были предложены следующие положения общего характера, регулирующие этот процесс
[Keller 1990]:

Используйте имена, которые можно произнести вслух. Основное правило: если вы не можете громко прочитать имя, забудьте о нем.

Применяйте заглавные буквы или символы подчеркивания для того, чтобы отметить начало нового слова в составном имени: CardReader или Card_reader вместо нечитаемого cardreader.

Тщательно проверяйте сокращения. Сокращение, ясное для одного человека, может быть загадочным для другого. Обозначает ли имя TermProcess последний процесс в цепочке (terminal process), или нечто, что прекращает выполнение процесса (terminate process), или же процесс, связанный с терминалом компьютера?

Избегайте многозначности имен. Имя empty для функции — обозначает ли оно проверку того, что некоторый объект пуст, или же она удаляет все значения из объекта (делает его пустым)?

Не используйте цифры в именах. Их легко спутать с буквами (0 как O, 1 как l, 2 как
Z, 5 как S).

Присваивайте функциям, которые возвращают логические (булевские) значения, такие имена, чтобы было ясно, как интерпретировать true и false. Например,
PrinterIsReady ясно показывает, что значение true соответствует принтеру в рабочем состоянии, в то время как PrinterStatus является гораздо менее точным.

Дайте дорогостоящим (с точки зрения компьютерных ресурсов) и редко используемым операциям уникальные, четко выделяемые имена. При таком подходе уменьшается вероятность использования «не тех» функций.
PDF created with pdfFactory Pro trial version www.pdffactory.com

Как только для всех действий выбраны имена, CRC-карточка для каждой компоненты переписывается заново с указанием имен функций и списка формальных аргументов. Пример CRC-карточки для компоненты Date приведен на рис. 2.5. Что осталось не установленным, так это то, как именно каждая компонента будет выполнять указанные действия.
1
«Что значит имя? Роза пахнет розой, хоть розой назови ее, хоть нет. Ромео под любым названьем был бы тем верхом совершенств, какой он есть». — Вильям Шекспир, «Ромео и Джульетта», действие II, сцена 2 (пер. Бориса Пастернака).
Необходимо еще раз «прокрутить» сценарий более детально, чтобы гарантировать, что все действия учтены и вся необходимая информация имеется и доступна для соответствующих компонент.
Рис. 2.5. Обновленная CRC-карточка для компоненты Date
2.9. Выбор представления данных
На данном этапе, если только это не было сделано раньше, происходит разделение команды разработчиков на группы, каждая из которых отвечает за конкретные компоненты программы. Задача теперь состоит в переходе от описания компоненты к конкретному коду. Главное здесь — проектирование структур данных, которые будут использоваться каждой из подсистем для хранения внутренней информации, необходимой для выполнения предписанных обязанностей.
Именно на этом этапе в игру вступают классические структуры данных, используемые в информатике. Выбор структуры данных является важным, центральным моментом с точки зрения проектирования. Если представление данных выбрано правильно, то код, используемый компонентой при выполнении ее обязанностей, становится почти самоочевидным.
Структуры данных должны точно соответствовать рассматриваемой задаче.
Неправильный выбор структуры может привести к сложным и неэффективным программам.
На этом же этапе описание поведения компонент должно быть преобразовано в алгоритмы. Реализованные функции затем сопоставляются с потребностями компоненты,
PDF created with pdfFactory Pro trial version www.pdffactory.com
являющейся клиентом данного фрагмента, чтобы гарантировать, что все ее запросы оказываются выполненными и что все необходимые для ее работы данные являются доступными.
2.10. Реализация компонент
Когда проект в целом определен и разбит на подсистемы, следующим шагом является реализация компонент. Если предыдущие этапы были выполнены корректно, каждая обязанность или поведение будут кратко охарактеризованы. Задачей данного этапа является воплощение желаемых действий на компьютерном языке. В следующем разделе мы опишем некоторые из наиболее типичных эвристических подходов, используемых с этой целью.
Если это не было сделано ранее (например, как часть этапа спецификации всей системы), то теперь можно решить, как будут устроены внутренние детали отдельных компонент. В случае нашего примера на данном этапе следует подумать, как пользователь будет просматривать базу данных рецептов.
По мере того как программные проекты с большим числом разработчиков становятся нормой, все реже встречается ситуация, когда один–единственный программист отвечает за всю систему. Наиболее важные для программиста качества — это способность понимать, как отдельный фрагмент кода подключается к более высокому программному уровню, и желание работать совместно с остальными членами команды.
Часто в процессе реализации одной компоненты становится ясно, что некоторая информация или действия должны быть присвоены совсем другой компоненте, которая работала бы «за сценой», не видимо для пользователя. Такие компоненты иногда называют суфлерами. Мы встретим соответствующие примеры в некоторых последующих главах.
Важной частью анализа и кодирования на этом этапе является полная характеризация и документирование необходимых предварительных условий, которые требуются программной компоненте для выполнения задания. Также следует проверить, правильно ли работает программная компонента, если вызвать ее с правильными входными значениями. Это подтвердит корректность алгоритмов, использованных при реализации компоненты.
2.11. Интеграция компонент
Когда индивидуальные подсистемы разработаны и протестированы, они должны быть интегрированы в конечный продукт. Это делается поэтапно. Начиная с элементарной основы, к системе постепенно добавляются (и тестируются) новые элементы. При этом для еще не реализованных частей используются так называемые заглушки (stubs) — подпрограммы без какого-либо поведения или с ограниченной функциональностью.
Например, при разработке программы IIKH было бы разумным начать интегрирование с компоненты Greeter. Чтобы протестировать ее в изоляции от остальных блоков программы, потребуются заглушки для управляющего кода базы данных с рецептами
Recipe Database и блока управления планированием питания Meal Plan. Заглушки просто должны выдавать информационные сообщения и возвращать управление. Таким образом, команда разработчиков компоненты Greeter сможет протестировать различные аспекты
PDF created with pdfFactory Pro trial version www.pdffactory.com
данной компоненты (например, проверить, вызывает ли нажатие клавиши нужную реакцию). Отладку отдельных компонент часто называют тестированием блоков.
Затем заглушки заменяются более серьезным кодом. Например, вместо заглушки для компоненты Recipe Database можно вставить реальную подсистему, сохранив заглушки для остальных фрагментов. Тестирование продолжается до тех пор, пока не станет ясно, что система работает правильно. Этот процесс называют тестированием системы в целом.
Когда все заглушки заменены работающими компонентами, приложение завершено.
Процесс тестирования заметно облегчается, если число связей между компонентами невелико — в этом случае не придется писать множество программ-заглушек.
Во время интеграции системы вполне возможно, что ошибка, проявляющаяся в одной из программных систем, вызвана некорректным кодом в другом фрагменте. Тем самым ошибки, выявляемые в процессе интеграции, приводят к необходимости исправлять некоторые компоненты. Вслед за этим измененные компоненты должны вновь тестироваться изолированно перед очередной попыткой интеграции. Повторная прогонка разработанных ранее тестовых примеров, выполняемая после изменений в компоненте программы, иногда называется регрессионным тестированием (regression testing).
2.12. Сопровождение и развитие
Хотелось бы, чтобы с передачей пользователю функционирующего приложения, работа команды разработчиков завершалась. К сожалению, такого почти никогда не происходит.
Необходимо дополнительное сопровождение программного обеспечения. Вот некоторые причины, вызывающие его неизбежность:

В переданном продукте могут быть обнаружены ошибки. Они должны быть исправлены либо через «заплатки» (patches) к существующей версии, либо в новой версии.

Изменение требований — возможно, из-за новых государственных постановлений или стандартизации.

Переход на другое аппаратное обеспечение. Например, в результате переноса системы на другие платформы или поддержки новых устройства ввода (световое перо или сенсорный экран). Может измениться технология вывода: скажем, вы перешли от текстового интерфейса к графическому.

Изменение запросов пользователей. Пользователи могут требовать увеличения возможностей программы, снижения цены, более простого использования и т. д.
Как правило, такие «повышенные» требования диктуют конкурирующие продукты.

Потребность в улучшенной документации.
Хороший проект предусматривает неизбежность изменений и подготавливает их с самого начала.
Упражнения
1. Опишите распределение обязанностей в организации, которая включает по крайней мере шесть членов. Рассмотрите учебное заведение (студенты, преподаватели, директор, гардеробщик), фирму (совет директоров, президент, рабочий) и клуб
(президент, вице-президент, рядовой член). Опишите обязанности каждого члена организации и его сотрудников (если они есть).
PDF created with pdfFactory Pro trial version www.pdffactory.com

2. Создайте с помощью диаграммы взаимодействия сценарий для организации из упражнения 1.
3. Для типичной карточной игры опишите программную систему, которая будет взаимодействовать с пользователем в качестве противоположного партнера.
Типичные компоненты должны включать игровой стол и колоду карт.
4. Опишите программную систему для управления ATM (Automatic Teller Machine).
Поскольку слово Teller достаточно многозначно (рассказчик, счетчик голосов при выборах, кассир в банке, диктор радиолокационной станции ПВО и т. д.), то у вас имеется большая свобода в выборе предназначения этой машины. Приведите диаграммы взаимодействия для различных сценариев использования этой машины.
1   2   3   4   5   6   7   8   9   ...   23


написать администратору сайта