Детальное проектирование

Детальное проектирование

Реализация и техническая поддержка изящно спроектированных приложений существенно облегчается.
На рис. 6.1 изображена фаза проектирования, о которой пойдет речь в этой главе.
4- Основы: разделы 6.1-6.4.
♦ Детали: разделы 6.5-6.10 + Упражнения.
♦ Пример: Проектная документация программного обеспечения (SDD) для видеоигры Встреча, часть 2
Рис. 6.1. Схема разработки программ: темы главы 6
Учебные цели этой главы состоят в следующем.
♦ Понять, как образцы проектирования применяются в детальном проектировании.
♦ Полностью специфицировать классы и функции.
♦ Определить алгоритмы:.
♦ используя блок-схемы;.
♦ используя псевдокод.
Основы.
6.1. Введение в детальное проектирование.
6.1.1.
Что такое детальное проектирование.
Детальное проектирование — это техническая деятельность, которая следует за выбором архитектуры. Основной целью этой деятельности является как можно более полная подготовка проекта к его реализации. Другими словами, программисты должны иметь возможность реализовать детальный проект, сконцентрировавшись только на проблемах, связанных с созданием программного кода.
6.1.2.
Соотношение вариантов использования, архитектуры и детального проектирования.
Взаимосвязь между вариантами использования, архитектурой и детальным проектированием может быть представлена по аналогии с проектированием моста. Варианты использования будут являться частью требований, предъявляемых к конструкции моста. Отталкиваясь от требований, разработчики выбирают архитектуру (например, подвесной мост). После этого они разрабатывают детальный проект для реализации требуемых вариантов использования с учетом выбранной архитектуры (рис. 6.2).
В проектировании программного обеспечения каждый последующий уровень накапливает дополнительные классы. На шаге 1 варианты использования фиксируются как часть требований. На шаге 2 они вместе с другими источниками используются для определения классов предметной области. На шаге 3 мы разрабатываем программную архитектуру (см. главу 5). Последний шаг заключается в проверке того факта, что архитектура и детальный проект удовлетворяют требуемым вариантам использования. Применительно к нашему примеру с проектированием моста мы проверяем то, что автомобили действительно смогут использовать спроектированный мост при поездке с Холма Смита в Долину Джонса, как это было заявлено в вариантах использования. В случае программного проекта мы проверяем то, что классы и методы, специфицированные при детальном проектировании, способны реализовать требуемые варианты использования.
Рис. 6.2. Связь между вариантами использования, архитектурой и детальным проектированием
6.1.3. Типичная схема процесса детального проектирования.
Детальное проектирование начинается по результатам фазы архитектуры и завершается с моментом получения полного плана для фазы программирования. Типичная последовательность шагов, предпринимаемых при детальном проектировании, показана на рис. 6.3. В частности, на шаге 2 создаются классы, которые связывают архитектуру с классами предметной области, что проиллюстрировано в предыдущем разделе. При создании таких классов нам могут помочь образцы проектирования. Наиболее разумным было бы начать процесс детального проектирования с аспектов проекта, представляющих наибольший риск. Например, при проектировании игры Встреча мы можем посчитать рискованным способ модуляризации классов (все персонажи в одном пакете и т. п.). Мы должны установить это как можно раньше, указав детали методов интерфейса, чтобы иметь детальное представление о том, как организовать эту модуляризацию. Если использование образца проектирования State признано рискованным, значит, нам необходимо сначала специфицировать его детали.
Шаг 3 включает в себя проверку полноты созданного нами проекта. Он также включает в себя проверку того, что объектная модель обеспечивает варианты использования, что обсуждалось в разделе 6.1.2.
Шаг 6 продолжает процесс создания новых тестов по мере появления новых элементов.
Рис. 6.3. Типичная схема детального проектирования
ОДИН ИЗ СПОСОБОВ ОРГАНИЗОВАТЬ КОМАНДУ -.
ДЛЯ ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ (1).
1. Подготовьтесь к совещанию по детальному проектированию:.
♦ убедитесь, что члены команды знакомы с моделями, с которыми им придется работать. Обычно это объектные модели, диаграммы последовательности, переходов состояний или потоков данных;.
♦ убедитесь, что члены команды знакомы с предлагаемой нотацией. Как правило, это UML со стандартом псевдокода и (или) пример;.
♦ лидер проекта подготавливает список модулей;.
♦ лидер проекта создает повестку совещания;.
♦ лидер проекта выделяет время на обсуждение вопросов повестки (иначе люди могут бесконечно рассуждать о детальном проектировании!); распределите время, выделенное на вопросы повестки совещания.
ОДИН ИЗ СПОСОБОВ ОРГАНИЗОВАТЬ КОМАНДУ -.
ДЛЯ ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ (2).
2.
Проведите совещание:.
+ назначьте кого-то следить за временем обсуждения;.
♦ убедитесь, что архитектура готова к детальному проектированию. Проверьте, готовы ли интерфейсы модулей. Если не готовы, то проверьте всю группу;.
♦ не пытайтесь проводить детальное проектирование группой. Это не обязательно; каждый несет личную ответственность. Группы довольно редко выполняют детальное проектирование успешно;.
♦ выделите модули каждому из членов команды. Запросите оценки времени на проектирование, которые приведут к конкретной дате;.
+ записывайте решения и выводы и рассылайте их всем членам команды;.
♦ решите, как и когда будут обсуждаться результаты.
3.
Обновляйте документацию.
Результатом станет более детальный план-график с модулями и данными инспектирования.
4.
Проведите инспектирование детального проектирования.
См. раздел 6.9.2.
5.
Проведите доработку по результатам инспектирования.
6.
Проведите последнюю проверку и зафиксируйте полученные выводы.
6.1.4. Проектирование по схеме USDP.
В USDP [64] проектирование имеет место в основном во время итераций проектирования и конструирования (рис. 6.4). Фаза анализа зачастую определяется как часть водопадного процесса. В сравнении с терминологией этой книги фаза анализа состоит частично из анализа требований и выбора архитектуры. Якобсон и другие исследователи провели сравнение между процессом анализа и процессом проектирования (табл. 6.1).
Рис. 6.4. Проектирование согласно USDP
Таблица 6.1. Сравнение анализа и проектирования
Анализ
Проектирование
1. Концептуальный и абстрактный
1. Конкретно: спецификация реализации
2. Применим к различным проектам
2. Зависит от реализации
3. Стереотипы: управление, сущности и границы
3. Нет ограничений на стереотипы классов
4. Менее формален
4. Более формально
5. Менее дорогостоящий в проведении
5. Более дорогостоящее (примерно в 5 раз дороже)
6. Приводит к созданию набросков проекта
6. Провозглашает архитектуру
7. Возникает в результате обдумывания концепции
7. Может использовать инструменты (например, визуальная или циклическая разработка)
8. Информация, поступающая в ходе процесса, менее приоритетна
8. Информация, поступающая в ходе процесса, имеет высокий приоритет
9. Относительно неограничен
9. Ограничено анализом и архитектурой
10. Менее сконцентрирован на диаграммах последовательности
10. Болёе сфокусировано на последовательности
11. Имеет мало уровней
11. Многоуровневое
Пункт 3 в табл. 6.1 требует отдельного объяснения. USDP поддерживает три типа (стереотипа) классов на уровне анализа, не связанных с классами проектирования: классы сущности, граничные классы и классы управления. В классах сущности выражена суть концепции проекта, и их изменения в приложениях нежелательны. Граничные классы управляют взаимодействием между объектами сущности, а классы управления содержат методы, принадлежащие этим объектам, причем их наличие особенно характерно для приложений, в которых используются классы сущности. Граничные классы обычно аналогичны объекту Mediator образца проектирования Mediator (см. раздел 6.5.3.3).
Создатели USDP понимали, что визуальные средства могут быть выгодно использованы в проектировании (см. пункт 7 в табл. 6.1). Примером может служить инструмент Rational Rose (Rational Corporation). Пункт 8 заключает, что важнее поддерживать детальное проектирование на протяжении процесса разработки, чем поддерживать результаты фазы анализа (предполагается, что здесь необходимо сделать выбор). Наконец, в пункте 10 выражен тот факт, что мы возвращаемся к вариантам использования, для того чтобы прийти к согласованности с их диаграммами последовательности.
6.1.5. Проектирование и интерфейсы.
В нашем обсуждении различных архитектур в главе 5 мы использовали образец проектирования Facade, благодаря чему смогли получить четкий интерфейс для пакетов. Проектирование с использованием интерфейсов можно сравнить с заключении контракта. Элемент приложения (например, класс Заказчик), обеспечивающий требуемую функциональность, гарантирует предоставление функций с определенными именами, типами параметров и возвращаемых значений (например, void счет( void ) и boolean печатьСчетов( String типСчета )). Элементы приложения, использующие эту основу функциональности, следует проектировать так, чтобы они «не знали» подробностей реализации функциональности.
Применение интерфейсов в проектировании может иметь разные формы. Одна из них — это абстракция. Например, если мы пишем код для объектов Млекопитающие, мы должны делать это таким образом, чтобы упоминать только объекты Животные. Другими словами, мы пытаемся использовать только интерфейс Животные. Это повышает гибкость и применимость нашего проектирования.
Рассмотрим еще один пример. Допустим, мы пишем код приложения для работы с заказчиками. Это можно понимать как написание кода на основе интерфейса Заказчик. Вообще говоря, мы можем рассматривать абстрактный класс Заказчик с неабстрактным подклассом, например ПостоянныйЗаказчик (рис. 6.5). Подобный подход к проектированию более гибок, чем работа с конкретным (не абстрактным) классом Заказчик, так он позволяет нам легко добавлять новые типы заказчиков с их индивидуальными версиями метода счет(), и при этом нам не нужно менять код, манипулирующий объектами Заказчик. Такое деление на абстрактный и конкретный уровни характерно для многих образцов проектирования.
Рис. 6.5. Проектирование и интерфейсы
6.1.6. Повторно используемые компоненты.
Большинство инженерных дисциплин (электрика, механика и т. д.) основаны на использовании компонентов, которые могут быть произведены отдельно, независимо от остальных компонентов. Например, проектировщики мостов, вообще говоря, не занимаются проектированием конструкций балок. Из года в год наблюдается одна и та же ситуация: программные разработчики непрерывно «изобретают велосипед», реализуя уже давно существующие процедуры. Одной из причин недостаточного распространения повторного использования программного обеспечения является исторически сложившаяся неудачная организация существующих архитектур, проектов и кода. Развитию повторного использования программного обеспечения поспособствовало широкое распространение объектно-ориентированных, объектно-подобных и других парадигм компонентов. Благодаря огромному количеству методов, предоставляемых в пакете с каждым классом, необходимая нам функциональность часто уже включена и легкодоступна.
Примером повторного использования программного кода является применение библиотеки Microsoft MFC, элементов управления Visual Basic, объектов COM, Java Beans и других классов Java API. Стандартом для распределенного повторного использования является архитектура CORBA консорциума OMG.
В предыдущей главе, посвященной архитектуре, мы обсуждали каркас приложения. Это пакеты компонентов, спроектированных так, что они допускают повторное использование. Мы разработали каркасы для поддержки выбранной архитектуры приложения, и поэтому их можно использовать повторно достаточно эффективно. Еще одним примером широко применяемого каркаса могут служить базовые пакеты Java API.
Повторно используемые компоненты для Java-приложений обеспечивает стандарт Java Beans. К ним относятся различные графические и «производственные» компоненты, инкапсулирующие корпоративные задачи, такие как доступ к базам данных. Дополнительное преимущество компонентов Java Beans состоит в том, что они подчиняются стандартам, позволяющим им действовать внутри сред разработки.
Веб-приложения (не компоненты), такие как сценарии JavaScript или CGI, также часто бывают повторно используемыми.
Комбинированные возможности по применению стандартных алгоритмов, например сортировки и поиска, могут предоставить библиотеки стандартных шаблонов (STL). STL применяются к различным структурам данных и к объектам практически любых классов. Подробнее речь о них пойдет далее в разделе 6.6.
В целом нужно отметить, что рынок компонентов уже сформировался и постоянно развивается.
Допустим, вы нашли компонент, который, возможно, будет полезен в приложении. Но стоит ли действительно его использовать? Обычно на принятие окончательного решения влияют следующие факторы.
♦ Документирован ли этот компонент столь же тщательно, что и приложение? Если нет, можно ли это исправить?.
♦ В какой дополнительной настройке нуждаются компонент и (или) приложение?.
♦ Прошел ли компонент тестирование того же уровня (или, что предпочтительнее, еще более жесткое), что и приложение? Если нет, возможно ли это осуществить?.
Для сравнительной оценки стоимости различных вариантов можно составить таблицу, аналогичную приведенной в разделе 2.5.2, где мы исследовали, что выгоднее: купить готовое решение или создавать новое приложение.
6.2. Диаграммы последовательности и диаграммы потоков данных в детальном проектировании.
Вернемся к диаграммам последовательности (глава 4) и диаграммам потоков данных (глава 3), которые могут быть полезны при описании требований. Чаще всего они используются в качестве инструмента для проектирования. Руководство действиями, которые необходимо предпринять для осуществления детального проектирования, предлагается во врезках «Один из способов», а необходимые подробности и примеры вы найдете далее в тексте.
ОДИН ИЗ СПОСОБОВ УСОВЕРШЕНСТВОВАНИЯ МОДЕЛЕЙ ДЛЯ ДЕТАЛЬНОГО -.
ПРОЕКТИРОВАНИЯ (1). ДИАГРАММЫ ПОСЛЕДОВАТЕЛЬНОСТИ.
1.
Начните с диаграмм последовательности, созданных для детальных требований или архитектуры (если таковые имеются), относящихся к вариантам использования.
2.
Если это необходимо, представьте дополнительные варианты использования, для того чтобы описать, как проектируемые части обычно взаимодействуют с остальным приложением.
3.
Обеспечьте полную детализацию диаграмм последовательности:.
♦ убедитесь, что соответствующие объекты и их классы специфицированы;.
♦ выберите конкретные имена функций вместо естественного языка для представления операций.
ОДИН ИЗ СПОСОБОВ УСОВЕРШЕНСТВОВАНИЯ МОДЕЛЕЙ ДЛЯ ДЕТАЛЬНОГО -.
ПРОЕКТИРОВАНИЯ (2). ДИАГРАММЫ ПОТОКОВ ДАННЫХ.
1.
Соберите диаграммы потоков данных, созданные для детальных требований и (или) архитектуры (если они имеются).
2.
Если это необходимо, представьте дополнительные диаграммы потоков данных, чтобы пояснить потоки данных и процесс.
3.
Выясните, каким частям других моделей соответствуют диаграммы потоков данных. Например, «следующая диаграмма потоков данных для каждого объекта Счет находится в модели класса».
4.
Обеспечивайте полную детализацию диаграммы потоков данных:.
♦ выясните природу процесса, протекающего в каждом узле;.
♦ выясните тип передаваемых данных;.
♦ если описание процесса требует большей детализации, раскройте узлы процесса в диаграмме.
6.2.1. Детальные диаграммы последовательности.
Напомним, что варианты использования могут применяться для представления требований, и поэтому мы привлекаем их для определения ключевых наследуемых классов приложения. Теперь возьмем диаграммы последовательности, построенные по вариантам использования, и предоставим соответствующие классы с необходимыми для выполнения последовательностей методами.
Диаграмма последовательности для варианта использования «Встретить внешний персонаж» игры Встреча, взятая из примера в конце главы, представлена на рис. 6.6. Вместо словесных описаний функциональности, которую мы использовали для требований (см. раздел 4.4), мы определили собственные функции. Другое усовершенствование заключается в представлении класса РолиВстречи образца проектирования Facade, через который должны проходить все внешние ссылки на персонажи игры. Ниже приводятся причины выбора таких функций.
1.
Классу ВнешнийПерсонаж необходима функция, обеспечивающая отображение на экране. Мы реализуем ее с помощью метода показатъ(). (Поскольку эта возможность необходима для всех персонажей, мы можем гарантировать выполнение данного требования, добавив метод показать() в базовый класс ИграВстреча.) На диаграмме последовательности показано, что классом ИграВстреча создается внешний персонаж (шаг 1.2), затем создается объект Контакт и вызывается метод показать().
2.
Этот шаг в варианте использования отражает необходимость наличия метода выполнитъ() в классе Контакт. На этом шаге требуется, чтобы Фредди и главныйПерсонажИгрока могли менять значения своих характеристик. Так как эта возможность является общей для всех персонажей Встречи, мы заложим ее в базовый класс ПерсонажВстречи в методе установитьХарактеристики().
3.
На этом шаге необходимо показать результат контакта. Одним из возможных решений является следующее:.
1) сначала Контакт создает объект ОкноКонтакта.
2) затем мы выводим окно контакта, вызвав метод показатъРезультат().
Рис. 6.6. Диаграмма последовательности для варианта использования «Встретить внешний персонаж»
Поскольку все методы, необходимые для реализации этого варианта использования, теперь известны, мы можем представить их на объектной модели (рис. 6.7). Продолжая этот процесс, мы получим детальные модель классов и модель вариантов использования (в виде диаграмм последовательности) (это показано в примере в конце главы). Необходимо также получить детальную модель переходов состояний (если она применима). Диаграмма потоков данных относится к другой модели и обсуждается далее.
Рис. 6.7. Классы для случая внешнего персонажа игры Встреча
6.2.2. Детальные диаграммы потоков данных.
В главе 3 диаграммы потоков данных обсуждались как средство для представления требований. Мы рассмотрели пример с банковским приложением. Для детального проектирования модели данных должны детально описываться, а затем отображаться на функции или классы и методы. Действия, необходимые для описания всех деталей примера банковской диаграммы потоков данных из раздела 5.3.1, демонстрирует рис. 6.8.
Каждый элемент процесса раскрывается в более детальную диаграмму, и этот процесс раскрытия продолжается, пока не будет достигнут самый нижний уровень элементов процесса. На этом уровне элементы, как правило, представляют собой отдельные функции, возможно даже из разных классов. Например, функция getDepositO раскрывается в три функции (получение пароля, его проверка и вывод на экран). Две из этих функций взаимодействуют с хранилищами данных (локальным отчетом транзакций, списком недопустимых пользователей и шаблоном для вывода на экран), которые не были видны на диаграмме верхнего уровня. Отметим, что входные и выходные данные из детальных раскрытий совпадают по версиям с теми данными, из которых они были извлечены.
Не для всех приложений можно извлечь пользу из диаграммы потоков данных. Например, в них нет особой необходимости в нашем примере с игрой Встреча.
Рис. 6.8. Детальная диаграмма потоков данных для банковского приложения
6.3. Спецификация классов и функций.
Целью детального проектирования является полноценный проект, по которому может быть создана программа. Хорошо построенный проект покидает создателя с минимальным числом неопределенностей относительно намерений разработчика. Это справедливо и для программного проектирования. Типичные шаги при осуществлении детального проектирования для каждой функции класса с соответствующими пояснениями представлены ниже.
ОДИН ИЗ СПОСОБОВ СПЕЦИФИЦИРОВАТЬ КЛАСС -.
1.
Соберите атрибуты, перечисленные в SRS. Это проще сделать, если SRS организована по классам.
2.
Добавьте дополнительные атрибуты, необходимые для проектирования.
3.
Назовите методы, соответствующие каждому из требований для этого класса. Это проще сделать, если SRS организована по классам.
4.
Назовите дополнительные методы, необходимые для проектирования.
5.
Представьте атрибуты и методы на объектной модели.
6.
Установите инварианты класса (раздел 6.3.1).
ОДИН ИЗ СПОСОБОВ СПЕЦИФИЦИРОВАНИЯ ФУНКЦИИ -.
1.
Отметьте разделы SRS или SDD, которым удовлетворяет эта функция (метод).
2.
Определите, в каких выражениях функция должна оставаться инвариантной.
3.
Задайте предусловия метода (при каких условиях выполняется).
4.
Задайте постусловия метода (что происходит в результате его выполнения).
5.
Обеспечьте описание используемого алгоритма в виде псевдокода или блок-схемы. Исключением является случай, когда алгоритм тривиален.
Детальные диаграммы классов должны содержать имена всех атрибутов и операций, сигнатуры, область видимости, возвращаемые типы и т. д. Методы средств доступа обычно опускаются. Пример класса детального проекта приложения, контролирующего поток упаковок с дисками на фабрике, производящей микросхемы, представлен на рис. 6.9. В примере используется нотация UML.
Заметим, что специфицированы не все атрибуты. Некоторые обязательные атрибуты могут быть пропущены по решению разработчиков. Также обычно пропускаются различные вспомогательные функции, например getSizeO и setSizeO, поскольку их наличие следует из наличия соответствующих атрибутов (size).
Инструментарий имеет некоторые преимущества, позволяя разработчикам скрывать (то есть не показывать) определенные элементы, указанные на рис. 6.9. Это может быть, например, раздел «Обязанности» или типы переменных. Кроме того, многие инструменты позволяют разработчикам ограничиться лишь просмотром классов и их взаимосвязей, то есть использовать более общий подход к рассмотрению классов.
Одним из полезных средств создания спецификации классов является CORBA IDL (Interface Definition Language — язык определения интерфейса). Это стандартный текстовый формат для описания интерфейсов, обеспечиваемых наборами классов, их атрибутов и функций. Спецификацию IDL можно найти в [86].
Рис. 6.9. Детальная UML-нотация для классов
6.3.1. Инварианты класса.
Инвариант — это утверждение, остающееся справедливым на всем протяжении вычислений. Хорошо продуманные классы часто имеют инварианты. Например,.
персонажи видеоигры Встреча имеют то свойство, что сумма значений их характеристик всегда является положительным числом.
Инварианты класса представляют собой эффективные требования к объектам класса, выраженные в терминах атрибутов класса. Эти требования могут быть перенесены прямо на требования к приложению, и поэтому могут войти в SRS. В противном случае они предъявляются в ходе проектирования. Инварианты класса принимают форму ограничений на значения. Как и С-требования, они часто называются правилами бизнеса. Например, требование «Все участники веб-аукционов должны предоставлять номер своей кредитной карты» может быть переведено в специальный инвариант класса Участник следующим образом:.
зарегистрирован == true AND 400000001OR.
зарегистрирован == false AND номерКредитнойКарты == 0.
6.3.2. Инварианты, предусловия и постусловия функций.
Один из способов управления работой функций основан на применении инвариантов функций. Так называются утверждения о взаимосвязях переменных, гарантированно удовлетворяемые функциями. Примером из видеоигры Встреча может послужить следующий возможный инвариант для метода adjustQualityO: Сумма значений характеристик персонажа постоянна.
Другими словами, когда значение одной из характеристик меняется при помощи метода adjustQualityO, значения остальных характеристик изменяются так, что общая сумма значений всех характеристик остается неизменной.
Эффективным способом определения функций является использование предусловий и постусловий. Подробнее об этом рассказывалось в разделе 4.8.4. Предусловия описывают соотношения между переменными и константами, существование которых предполагается до момента выполнения функции. Постусловия описывают эти соотношения после выполнения функции. Например, функция снятъСоСчета (int сумма) класса Счет может быть описана так:.
Инвариант снятьСоСчета( сумма ).
доступныеФонды = max (0, баланс) Предусловие*:.
сумма >= 0 AND.
баланс - сумма >= -МАКСИМАЛЬНОЕ_ПРЕВЫШЕНИЕ_КРЕДИТА Постусловие*:.
баланс = баланс - сумма (х
- это значение х после выполнения функции.) Инвариант функции эффективнее дополнительных пред- и постусловия.
Заметьте, что банковское определение атрибута доступные фонды не включает в себя привилегию превышения кредита заказчиком. Каждый инвариант может быть заменен повторением утверждения инварианта одновременно в предусловиях и постусловиях.
6.4. Спецификация алгоритмов.
После определения функций, которые необходимо реализовать, может оказаться полезным описать используемый алгоритм, кратко останавливаясь на исходном коде. Основное преимущество этого подхода заключается в том, что разработчик может проинспектировать алгоритм отдельно без углубления в сложности программирования. Благодаря этому некоторые дефекты могут быть обнаружены до того, как они перерастут в дефекты программного кода. Чем важнее метод, тем важнее эта деятельность. Методы со сложным ветвлением являются первыми кандидатами для построения блок-схем.
6.4.1. Блок-схемы.
Блок-схемы являются одним из старейших графических представлений алгоритмов. Пример блок-схемы для метода setName() класса GameCharacter (ПерсонажИгры), демонстрирующий два простейших элемента блок-схем — условия (ромбы) и действия (прямоугольники), — приведен на рис. 6.10. Благодаря инспектированию блок-схемы на рис. 6.10 мы можем увидеть, что проверка допустимости имени параметра подчиняется довольно туманной логике.
Рис. 6.10. Пример блок-схемы
Весь доступный инструментарий для создания блок-схем из исходного кода — это один из видов обратного проектирования, при котором создаются инспектируемые артефакты, однако допускается инспектирование до программирования, что и является основной идеей использования блок-схем.
Использование блок-схем снизилось за два последних десятилетия двадцатого века. Отчасти это произошло из-за распространения объектно-ориентированного подхода, который стал довольно популярен именно в те годы в связи с тенденцией сокращения количества ветвлений благодаря распределению функциональности по классам и использованию виртуальных функций.
6.4.1.1. Пример блок-схемы.
Следующий пример — алгоритм поиска с возвратами для экспертных систем — иллюстрирует полезность применения блок-схем в объяснении алгоритмов.
Экспертные системы обычно базируются на знаниях, заложенных в виде правил, которые имеют следующий вид:.
посылка И посылка И ... И посылка => заключение, где посылки и заключение являются утверждениями. Например, животное относится к млекопитающим И животное полосатое = > животное является зеброй.
В этом случае наши факты являются обычными строками. Проблема заключается в создании программы, которая с приведенными ниже исходными данными определяла бы, можно ли сделать вывод, например, о некотором факте L. (Для данного примера ответ «Да».).
1.
Список фактов, таких как А, В, Q.
2.
Список правил, таких как А & R => L, А & В => С, В & С => F.
Одна из возможных объектных моделей представлена на рис. 6.11. Модель хранит текущий список известных фактов в виде статической структуры. Эта структура хранит список известных правил так же, как статическую структуру. Мы упростим систему этих списков программированием примера, приведенного выше, в терминах классов Fact и Rule. Основное внимание мы уделим наиболее сложной части: алгоритму proveBackO, работающему по принципу поиска от цели. Выясним, может ли некоторый факт, называемый soughtFact, быть получен из заданных фактов и правил. Блок-схема этого алгоритма приводится на рис. 6.12. Тщательное инспектирование этой блок-схемы показало бы, что она не в состоянии отслеживать возникновение цикличности в базе правил. Примером может послужить пара правил вида: X => Y и Y => X.
Рис. 6.11. Архитектура для поиска с возвратами
Рис. 6.12. Блок-схема для soughtFact.proveBack (ruleBase)
6.4.2. Псевдокод.
Псевдокод — это вариант текстового представления алгоритма, позволяющий не вдаваться в детали языков программирования. В качестве примера приведем псевдокод для гипотетического контроллера рентгеновского аппарата.
FOR количество микросекунд, отведенное оператором.
IF количество микросекунд превышает критическое значение Попробуйте получить подтверждение у оператора IF подтверждение не получено.
Отмена действия с сообщением «отсутствует подтверждение оператора на нестандартную длительность» ENDIF ENDIF IF уровень напряжения превышает критическое значение.
Отмена действия с сообщением «превышен уровень напряжения» ENDIF IF (пациент и экран размещены правильно и аппарат прошел самотестирование).
Включить рентгеновский луч с напряжением р ENDIF... ENDF0R.
Основным преимуществом псевдокода является его понятность и возможность достаточно точно представить алгоритм. Второе преимущество заключается в возможности инспектирования алгоритмов на предмет их корректности независимо от нагромождений языка программирования. Третье преимущество в том, что мы можем накапливать статистику по проценту ошибок в псевдокоде, а затем использовать эти данные для предсказания количества дефектов в конечном продукте. Многие организации используют инспектированный псевдокод в качестве комментариев в листинге исходного программного кода. Для извлечения псевдокода из исходного кода существует специальный инструментарий. Например, если выделить псевдокод символами //р, то из следующего программного кода можно получить вышеупомянутый псевдокод:.
//р FOR количество микросекунд, отведенное оператором for( int i=0; i.
//p IF количество микросекунд превышает критическое значение if( numMicrosecs >.
XRayPoli ci es.CRITICAL_NUM_MICROSECS ).
//p Попробуйте получить подтверждение у оператора int supervisorMicrosecsApproval =.
getApprovalOfSuperForLongExposure(); //p IF подтверждение не получено if( supervisorMicrosecsApproval.
throw( new SupervisorMicrosecsApprovalExcept!on() );.
6.4.3. Когда следует использовать блок-схемы и псевдокод.
И псевдокод, и блок-схемы имеют свои преимущества и недостатки. Перечислим их преимущества.
♦ Делают алгоритм более прозрачным.
♦ Повышают дисциплину в процессе документирования детального проектирования.
♦ Дают дополнительный уровень, на котором может быть проведено инспектирование:.
+ помогают отследить дефекты до того, как они появляются в программном коде; ♦ повышают надежность продукта.
♦ Часто снижают общую стоимость разработки. Однако они обладают следующими недостатками.
♦ Создают дополнительный уровень документации, которую необходимо вести.
♦ Привносят возможность ошибок при переводе в программный код.
♦ Могут потребоваться инструменты для извлечения псевдокода и упрощения рисования блок-схем.
Решение об использовании псевдокода и (или) блок-схем принимается в зависимости от каждого конкретного приложения. Некоторые разработчики избегают использования блок-схем, считая их устаревшим средством. Однако блок-схемы и псевдокод могут оказаться очень полезными для отдельной части приложений, где они будут способствовать производству продуктов более высокого качества.
Детали_.
Эту часть главы можно пропустить при первом чтении и вернуться к ней после прочтения последующих глав.
6.5. Образцы проектирования: приемы детального проектирования.
В разделе 5.2.5 мы обсуждали образцы проектирования применительно к архитектуре приложений. Теперь мы будем применять их в детальном проектировании. Само название «образец» предполагает, что это скорее нечто целое, нежели его отдельные части, которое иногда может помочь нам в решении проблем проектирования. Образцы проектирования должны использоваться только в благоприятных условиях, поскольку в противном случае они могут только усложнить обычное проектирование. Несмотря на эти предостережения, автор искренне верит в то, что образцы проектирования являются неотъемлемой частью инструментария программных разработчиков.
В этом разделе мы систематизируем образцы проектирования в соответствии с классификацией, данной в [33]. Ради этой ссылки мы включили образцы проектирования, которые уже обсуждались в предыдущей главе. По этой причине они не будут повторно разъясняться во всех подробностях. Различные веб-источники, например [53], демонстрируют, как образцы проектирования из [33] могут быть реализованы на языках С++ и Java.
Каждый образец проектирования обычно имеет два аспекта. Первый аспект — это обычная модель классов, описывающая классы и их взаимосвязь. Второй аспект представляет способ действия образца проектирования. Для описания последнего может быть применена модель вариантов использования.
ОДИН ИЗ СПОСОБОВ ПРИМЕНЕНИЯ ОБРАЗЦОВ -.
ПРОЕКТИРОВАНИЯ В ДЕТАЛЬНОМ ПРОЕКТИРОВАНИИ.
1.
Ознакомьтесь с проблемами проектирования, которые можно решить с помощью образцов проектирования.
♦ Уясните различия между креационными (К), структурными (С) и поведенческими (П) образцами проектирования.
Рассмотрите по порядку каждый из этапов детального проектирования.
2.
Выясните, к чему относится данная задача: к созданию чего-либо сложного (К), к представлению сложной структуры (С) или она связана с поведением приложения (П).
3.
Выясните, существует ли образец проектирования, пригодный для решения этой задачи:.
♦ используйте введенные обозначения (К), (С) и (П); ♦ используйте эту книгу или [33].
4.
Решите, что перевешивает — преимущества или недостатки:.
♦ преимущества обычно включают в себя высокую гибкость;.
♦ недостатки обычно заключаются в повышенной сложности и низкой эффективности.
В книге [33] образцы проектирования подразделяются на три категории. К первой относятся креациоииые образцы проектирования, отвечающие за процесс создания объектов. Ко второй категории относятся структурные образцы проектирования, занимающиеся отображением возможных комбинаций объектов. Третья категория — поведенческие образцы проектирования — фиксирует выбранный режим состояния объектов.
6.5.1. Креационные образцы проектирования.
Креационные образцы проектирования позволяют разнообразить создание объектов и управлять им, в том числе координировать группы объектов, создаваемые в ходе работы приложения. Без креационных образцов проектирования пришлось бы писать отдельный программный код для каждого типа объектов или группы объектов, которые могут понадобиться в ходе работы приложения. Креационные образцы проектирования обычно создают объекты при помощи отдельного объекта или метода без прямого использования конструкторов. Рассмотрим подробнее креационные образцы проектирования, приведенные в [33].
6.5.1.1.
Образец проектирования Singleton.
Проблема: предположим, что существует единственный объект, принадлежащий классу С. Например, в примере, приведенном в конце главы, у нас имеется только одна видеоигра, таким образом, будет только один объект класса ИграВстреча. Причем, как показано в этом примере, различным объектам необходимо ссылаться на этот объект.
Образец проектирования Singleton: идея этого образца проектирования реализуется заданием конструктора скрытого (или, возможно, защищенного) класса. Это предотвращает создание объектов класса С за исключением случая их создания одним из методов самого класса С. Класс С — это заданная часть статических данных типа С, которая будет единой. Назовем ее TheCObject. Определим открытый метод класса С, getTheObjectO, который будет возвращать объект TheCObject, если он уже существует, и создавать его в противном случае. Таким образом, для того, чтобы получить этот единственный элемент класса С, мы просто вызываем метод C.getTheCObjectO.
Статический объект класса С theCObject может быть инициализирован значением null, а затем установлен в значение new СО при первом же вызове getCObjectO. В любом случае нулевое значение theCObject может быть использовано для проверки существования этого объекта, упомянутой выше.
В примере в конце главы класс ИграВстреча наряду с прочими образцами проектирования использует и Singleton.
6.5.1.2.
Образец проектирования Factory.
Проблема: необходима возможность написать клиентский программный код, который создавал бы объекты базового класса в ходе работы приложения без использования ссылок на какие-либо подклассы базового класса.
Образец проектирования Factory: комплектует базовый класс методом, который создает объекты базового класса. Назовем этот метод getNewBaseObjectO. Теперь в клиентском коде мы будем использовать getNewBaseObjectO вместо newBaseO. Здесь мы использовали свойство виртуальных функций. В ходе работы приложения метод b.getNewBaseObjectO создает объект подкласса базового класса, которому принадлежит Ь. Использование образца Factory применительно к клиентскому программному коду, ссылающемуся только на базовый класс БиологическаяКлетка, демонстрирует рис. 6.13. В ходе работы приложения создается или объект КлеткаЖивотного, или объект КлеткаРастения, в зависимости от типа класса БиологическаяКлетка, на который ссылается программа-клиент.
Рис. 6.13. Пример использования образца проектирования Factory
Мы привели простейший вариант применения образца проектирования Factory.
6.5.1.3. Образец проектирования Prototype.
Проблема: предположим, что мы проектируем офисное приложение, которое обрабатывает объекты типа Документ и Заказчик. Например:.
Title = d.getTitleO: // d - объект типа Документ.
d.printSummary(c): //с - объект типа Заказчик d.addToDatabaseO;.
c.addToDatabaseO; // и т. д.
Предположим, что при каждом запуске наше офисное приложение проделывает эти операции только с одним подтипом объекта Документ (например, с объектами типа Документ Счет) и только с одним подтипом объекта Заказчик. Приложение определяет эти подтипы непосредственно в ходе работы. Таким образом, во время работы приложения возможны четыре варианта комбинации объектов типа Документ и Заказчик. Выражения вида:.
Document d = new DocumentO; и Customer с = new Customer О;.
позволяют создавать базовые объекты, а не объекты специального типа, необходимого в ходе работы приложения. Это в значительной степени препятствует выполнению кода для всех объектов типа Документ.
Образец проектирования Prototype: удачным решением этой проблемы может стать написание приложения с использованием статического объекта типа Документ (называемого прототипом, который мы назовем documentPrototypeS) и объекта типа Заказчик (который мы назовем customerprototypeS). Кроме того, каждый подкласс класса Документ должен иметь метод cloneO, который создает независимую копию объекта типа Документ (аналогично и для объектов типа Заказчик). Это показано в объектной модели на рис. 6.14, где Клиент — это клиентский класс MyOfficeApplication. Программному коду в MyOfficeApplication не требуется знать, с каким типом объекта типа Документ он имеет дело. Всякий раз, когда методу в MyOfficeAppli cation требуется новый объект типа Документ, он выполняет.
Document d = documentPrototypeS.clone().
вместо Document d = new Document О
Рис. 6.14. Пример образца проектирования Prototype
Это гарантирует, что во всем приложении будет создаваться согласованный тип объектов Документ. Соответствующий программный код представлен в листинге 6.1 и на рис. 6.15.
Листинг 6.1. Образец проектирования Prototype (1).
public class MyOfficeApplication { private static Document documentPrototypeS;.
private static Customer customerPrototypeS;.
publicMyOffi ceAppli cation.
(Document dPrototypeP. Customer cPrototypeP) { documentPrototypeS = dPrototypeP;.
customerPrototypeS = cPrototypeP:} public myMethod.
...II Необходим новый объект Документ.
Document d = documentPrototypeS. cloneO ; ...II Необходим новый объект Заказчик.
Customer с = customerPrototypeS.clone();...
Этот класс не знает, для какого типа (подкласса) — Документ или Заказчик — он был вызван.
Рис. 6.15. Образец проектирования Prototype (2)
Мы упоминали в разделе 5.2.5, что использование образцов проектирования клиентским кодом, вообще говоря, требует дополнительного наладочного кода. Наладочный код должен быть предоставлен классом MyOfficeAppl ication с прототипом Документ (подтипом) и прототипом Заказчик в ходе работы приложения с определениями приблизительно следующего вида:.
m = new MyOfficeApplication // поддерживает прототипы (new PurchaseOrderDocument). new CashCustomer() );.
6.5.1.4. Образцы проектирования Abstract Factory и Builder.
Проблема: во время работы приложения необходимо создать совокупность связанных объектов из различных возможных совокупностей. Например, рассмотрим пакет архитектурных программ, который создает планировку домов. Непосредственно во время работы пакета мы хотим увидеть, как одна и та же планировка дома будет выглядеть в стиле эпохи Тюдоров (где стены, двери, крыша оформлены в соответствующем стиле), в стиле модерн и т. д., просто выбирая стиль, а не перерисовывая весь дом каждый раз при помощи специального программного кода.
Образец проектирования Abstract Factory: Abstract Factory состоит в использовании в клиентском коде метода AnAbstractFactoryObject.getThingO вместо new ThingO. Во время работы приложения объект anAbstractFactoryObject задан как объект необходимого подкласса AbstactFactory, благодаря чему согласуются создаваемые им объекты.
Обратимся к примеру в конце главы. Допустим, мы хотим наградить игроков в игре Встреча, когда они доберутся до высших уровней, представив игровые зоны и соединения в новом согласованном виде. Зоны с различными стилями оформления на самом деле являются подклассами класса Area, а классы соединений являются подклассами AreaConnection. Наша задача при проектировании состоит в том, чтобы не допустить ссылок на различные уровни в клиентском коде. Под клиентским кодом здесь понимается код, использующий объекты Area. Такое проектирование намного упростит обновление и сопровождение кода.
О неаккуратности и негибкости подхода свидетельствует размещение следующих строк в клиентском коде или в классе EncounterEnvironment (СредаВстречи):.
агеа[5] = new АгеаО: // убрать! connection[5] = new AreaConnection(): // убрать!.
В таком случае нам было бы неудобно менять уровни зон и уровни соединений во время работы приложения (например, заменять стиль их оформления с уровня 3 на уровень 4). В частности, нам придется заменить эти строки на операторы выбора или на пары операторов вида:.
агеа[5] = new Lev8Area(); // неаккуратно.
connection[5] = new Lev8AreaConnection(); // неаккуратно.
Кроме загромождения клиентского кода это также повышает зависимость (связность) между различными частями проектирования.
Мы можем решить эту проблему благодаря использованию класса Abstract Factory. Это класс (в нашем случае EnvironmentFactory) с абстрактными методами, обещающими создать необходимые компоненты. Каждый из подклассов (в нашем случае: LevellFactory, Level2Factory, Level3Factory и т. д.) имеет согласованные работающие версии этих методов. Взаимосвязи между классами на примере Level 2Factory продемонстрированы на рис. 6.16.
EncounterEnvironment (СредаВстречи) агрегирует объект EnvironmentFactory, который мы назвали envFactory. Когда пользователь вызывает getAreaO для объекта EncounterEnvironment, этот вызов преобразуется в вызов метода getAreaO для объекта envFactory. Если игра идет на уровне 2, то результатом будет создание зоны уровня 2. Аналогично, вызов getConnectionO выразится в создании соединений для уровня 2.
Рис. 6.16. Образец проектирования Abstract Factory, примененный к классу СредаВстречи и демонстрирующий один класс образца проектирования Factory
Пользователь может вызвать incrementLevel О для объекта EncounterEnvironment, когда игрок достигает следующего уровня. Это приведет к установке envFactory в объекте Level 3Bui 1 der. В качестве альтернативы запрос на эти операции может быть передан внешнему управляющему коду (см. раздел 5.2.5). Тот же клиентский код теперь создаст зоны и соединения уровня 3. В клиентском коде используются непосредственно сами уровни.
Объектная модель с тремя уровнями изображена на рис. 6.17. Согласно такой объектной модели, в классе EncounterEnvironment вместо строк вида.
Area[5] = new Area О; // убрать!.
мы должны использовать.
area[5] = envFactory.getAreaO;.
а вместо.
connection[5] = new AreaConnection(): //убрать!.
мы должны использовать.
connecti on[5] = envFactory,getAreaConnection()
Рис. 6.17. Образец проектирования Abstract Factory, примененный к классу EncounterEnvironment (СредаВстречи) и демонстрирующий несколько классов образца проектирования Factory
После выполнения программы объект envFactory представляет собой экземпляр подкласса класса EnvironmentFactory (например, это может быть объект Level9Factory). В этом случае версия метода getAreaO для класса EnvironmentFactory создает объекты только типа Level9Area (это показано пунктирными линиями на рис. 6.17), а версия метода getAreaConnectionO создает объекты только типа Level 9AreaConnection. Таким образом, непротиворечивость достигается программным кодом класса EncounterEnvironment непосредственно во время работы приложения.
6.5.2. Структурные образцы проектирования.
Структурные образцы проектирования представляют комплексные объекты, такие как списки, коллекции и деревья, в виде объектов с удобными интерфейсами.
6.5.2.1. Образцы проектирования Composite и Decorator.
Проблема: предположим, мы хотим представить дерево объектов (например, схему организации компании) или связный список объектов.
Образцы проектирования Composite и Decorator: основная идея решения состоит в использовании отношений наследования и агрегации (рис. 6.18). Необходимо показать, что некоторые компоненты агрегируются другими компонентами (рис. 6.19). Модель классов включает в себя компоненты — листья дерева, — которые не являются объединением других компонентов. Образец проектирования действует следующим образом. Объект типа Composite выполняет метод doItO либо для листового объекта, что достаточно просто, либо для объекта NonLeafNode. Объект NonLeafNode вызывает метод doItO на каждого из своих объединенных потомков. Эти объекты типа Component продолжают процесс вниз по структуре дерева.
Рис. 6.18. Основа структуры образцов проектирования Composite и Decorator
Рис. 6.19. Образцы проектирования Composite и Decorator
Предположим, например, что дерево — это на самом деле схема управления организацией, а метод doItO — это displayEmployeeO. В этом случае тип объектов Component лучше называть Employee, а тип NonLeafNode — Supervisor. Термин «лист» теперь будет соответствовать классу Indi vidualContri butor. Вызов метода displayEmployeeO для объекта типа Component, который представляет всю организацию.
приведет к вызову для каждого из объектов своего метода displayEmployeeO, а затем к вызову displayEmployeeO для всех его подчиненных объектов. Для вице-президентов (например, TypeANonLeafNode бывшего SeniorVP) метод displayEmployeeO может работать по-разному, например включать биографический пресс-релиз, что эквивалентно действию метода displayEmployeeO для управляющих первого звена (TypeBNonLeafNode бывшего FirstLineSupervisor), и т. д.
Образец проектирования Decorator обладает свойством упрощенной передачи функций. Decorator создает связный список, а объединение (агрегация) (см. рис. 6.18) привлекает только один объект типа Component. Например, если textWindow является объектом типа Decorator, который связан с различными элементами оформления области вокруг блока с текстом (например, полосами прокрутки), то вызов метода display О для textWindow приведет к тому, что после вывода текста будет выводиться каждый из этих элементов. Текст будет инкапсулирован как объект класса Leaf.
6.5.2.2. Образец проектирования Facade.
Проблема: приложения могут состоять из сотен классов, тогда как человек в состоянии воспринимать только от пяти до девяти объектов единовременно. Как организовать классы в удобное количество коллекций, каждая из которых имела бы понятный интерфейс?.
Образец проектирования Facade: мы уже использовали структурный образец проектирования Facade в главе 5 применительно к архитектуре (см. раздел 5.3.2.1). При использовании образца проектирования Facade становится управляемым конечное количество пакетов (рис. 6.20). Объекты образца проектирования Facade обеспечиваются достаточным количеством открытых методов, для того чтобы позволить пользователям этих пакетов получить доступ ко всем необходимым объектам и требуемой функциональности. Рассуждения в главе 5 акцентированы на выгоде связывания каркасных классов с образцом проектирования Facade.
Рис. 6.20. Архитектура игры Встреча: классовая модель
6.5.2.3. Образец проектирования Adapter.
Проблема: предположим, что уже существующее приложение или даже просто объект предоставляет функциональность, необходимую нашему приложению. Например, допустим, что приложение вычисляет прибыль от вложения заданной суммы по заданной процентной ставке на заданное количество лет в некотором виде инвестирования. Мы хотим как можно меньше изменить наше приложение, с тем чтобы использовать преимущество существующего метода вычисления, но при этом быть готовыми к последующей «утилизации» устаревающих методов вычислений.
Образец проектирования Adapter: сначала мы пишем наше приложение, задавая собственные названия функции начисления процентов и классу (объекту), к которому относится эта функция. Например, метод.
amount( float originalAmount. float numYears. float intRate ).
относится к классу Financial. Этот класс является абстрактным.
Предположим, что функциональность в существующем приложении выражена как метод класса:.
computeValue( float years, float interest, float amount ) Этот метод относится к классу Principal (рис. 6.21).
Рис. 6.21. Образец проектирования Adapter
Образец проектирования Adapter состоит из класса, который мы для наглядности назовем FinancialAdapter. Этот класс унаследован от класса приложения (в нашем случае Financial) и агрегирует наследственный класс (в нашем случае Principal). Это можно реализовать, например, на Java следующим образом:.
Class FinancialAdapter extends Financial { Principal legacyAdaptee = null; // Область конструкторов...
/** Этот метод использует наследуемый метод computeValие() */ float amount( float originalAmount. float numYears. float intRate ) { return 1egacyAdaptee.computeValue.
( originalAmount. numYears. intRate ); } // конец метода amount О } // конец класса FinancialAdapter.
Новое приложение написано применительно к классу Financial и выполняется во время работы относительно объекта FinancialAdapter. (Как показано на объектной модели, объекты FinancialAdapter являются объектами Financial, так что это допустимо.) Например, приложение может быть написано с методом, в котором имеется параметр типа Financial:.
Void executeFinanceApplicationC Financial aFinancial );.
Этот метод может быть выполнен при следующем вызове: executeFinanceApplication( new FinancialAdapterO ):.
Все вызовы метода amount О класса Financial относятся к наследуемому методу computeValueO.
Адаптировать приложение к новой реализации метода amountO в новом классе достаточно легко. Нам потребуется только изменить программный код в классе FinancialAdapter, а оставшаяся часть приложения не будет затронута.
Для сохранения возможности перенастраивания во время работы приложения, мы можем оставить класс FinancialAdapter без изменений, но представить новый класс FinancialAdapter2, наследуемый от Financial. Как только нам потребуется перенастроить приложение на вторую наследуемую систему, нам необходимо будет выполнить приложение при помощи следующего оператора:.
executeFinanceApplicationC new FinancialAdapter2() ):.
Возможность перенастраивания кода внесением только локальных изменений (в противоположность изменениям в нескольких местах) имеет большое значение для разработки и сопровождения.
Путем наследования класса FinancialAdapter такой же эффект может быть получен от классов Financial и Principal вместо использования агрегации.
6.5.2.4. Образец проектирования Flyweight.
Проблема: предположим, что приложению придется иметь дело с большим количеством почти неразличимых «мелких» объектов. Например, пусть в нашей видеоигре используются такие артефакты, как сгустки энергии и мечи. Одновременно могут оказаться активными десятки сгустков энергии (маленьких неразличимых объектов).
Другим примером может послужить приложение, которое представляет текст в различных форматах (печати, просмотра, портативного устройства и т. д.). В принципе, может оказаться полезным определить каждый символ в тексте как отдельный объект. С другой стороны, огромное число различных объектов может обернуться сложностью управления и пустой тратой места для их хранения.
Образец проектирования Flyweight: Flyweight предотвращает создание отдельного объекта для каждой элементарной сущности благодаря использованию единственного объекта для всех сущностей и введению параметризованных методов. В вышеприведенном примере мы будем использовать только один объект для всех символов. Альтернативой множеству объектов EnergyPacket (СгустокЭнергии) из видеоигры может послужить единственный объект EnergyPacket с методами, параметризованными в зависимости от контекста, в котором используется этот объект. Игровым персонажам будет необходимо помнить лишь о том, сколькими сгустками энергии они располагают. Таким образом, вместо вызова.
energyPacket [23].expendO ... energyPacket[74] .expend О.
мы будем использовать.
theEnergyPacket.expend( главныйПерсонажИгрока. двор ) ... theEnergyPacket.expend( фредди, подвал).
Образец проектирования Flyweight обычно используется в связке с объектом Factory, который проверяет, существует ли уже соответствующий объект. Если существует, то возвращается этот объект; иначе объект создается и предоставляется для дальнейшего повторного использования.
6.5.2.5. Образец проектирования Proxy.
Проблема: предположим, что запуск метода является достаточно дорогостоящим, поскольку требует значительных временных затрат для такого процесса, как, например, загрузка рисунка из сети или рисование. Возможно, мы будем не в состоянии предотвратить сам вызов метода, но нам хотелось бы предотвратить выполнение его наиболее дорогостоящих частей без особой на то необходимости.
Образец проектирования Proxy: пусть RealActiveCl ass — это класс, включающий в себя метод expensiveMethodO, который мы не хотим выполнять без особой надобности. Введем абстрактный класс BaseActiveClass, от которого будет унаследован класс RealActiveClass. Образец проектирования Proxy предоставляет еще один класс, который мы назовем Proxy. Этот класс также унаследован от класса BaseActiveClass. Пусть класс Proxy содержит метод expensiveMethodO, который ссылается на класс RealActiveClass (рис. 6.22).
Рис. 6.22. Образец проектирования Proxy
Идея заключается в том, что метод expensiveMethodO класса Proxy является «подделкой» дорогостоящего метода. Клиентское приложение написано в терминах класса BaseActiveClass, а во время работы оно выполняется с объектом типа Proxy. Этот объект перехватывает ссылки, которые шли к классу Real ActiveCI ass. Объект Proxy не позволяет создаваться объектам класса Real ActiveCI ass, за исключением случаев, когда это необходимо. К тому же версия метода expensiveMethodO из класса Proxy, которую вызывает приложение, не выполняет дорогостоящие части более, чем это необходимо. При возникновении такой необходимости объект Proxy передает вызов своего метода expensiveMethodO методу expensiveMethodO объекта RealActiveCIass.
Например, RealActiveClass может иметь большой атрибут в виде битовой карты, хранимый на диске. Метод drawO класса Proxy будет извлекать эту битовую карту только в том случае, если она еще не была загружена. Если метод drawO никогда не будет вызван, то битовая карта никогда не будет извлечена с диска. Если это возможно, то при последующем вызове метода draw() битовая карта уже должна быть размещена в памяти резидентно.
6.5.3. Образцы проектирования, основанные на поведении приложения.
Образцы проектирования, основанные на поведении, позволяют варьировать поведение приложения в ходе его работы. Без использования этих образцов проектирования нам пришлось бы проектировать и реализовывать каждый вариант поведения по отдельности.
6.5.3.1.
Образцы проектирования Interpreter, Observer и State.
Образец проектирования Interpreter был описан в разделе 5.3.3. Образец проектирования Observer рассматривался в разделе 5.3.2.2.1. Образец проектирования State был рассмотрен в разделе 5.3.2.3.
6.5.3.2.
Образец проектирования Iterator.
Проблема: многим приложениям требуется программный код, который «проходил» бы множество объектов различными способами. В качестве примера возьмем внутреннюю структуру организации. Примером применения различных настроек к этому множеству будет процесс распечатки имен сотрудников в алфавитном порядке или по степени их должностной ответственности. Другим примером «прохождения», на этот раз базы данных, является поиск. Например, «Найти всех заказчиков, задолжавших более $500 и опаздывающих с расчетом». Нам необходимо писать этот программный код, концентрируясь на действиях, которые нужно предпринять при обращении к соответствующим объектам, то есть нам требуется возможность описать требуемую последовательность затрагиваемых объектов непосредственно в ходе работы приложения.
Образец проектирования Iterator: образец проектирования Iterator базируется на классах Iterator, иногда называемых «умными указателями». Каждый класс Iterator имеет некоторую определенную структуру. Наиболее эффективной является структура ссылки на элемент множества объектов, для обращения к которым этот класс спроектирован. Наиболее простой пример — это Iterator, примененный к массиву. В этом случае в качестве такой структуры (итератора) можно взять просто целое число — индекс элементов в массиве. Работу образца проектирования Iterator демонстрирует рис. 6.23.
Рис. 6.23. Образец проектирования Iterator
Итераторы предназначены для «посещения» множества объектов класса Item при наличии у них следующих методов.
1.
firstO — устанавливает объект Iterator как ссылку на первый элемент множества.
2.
getltemO — возвращает ссылку на текущий элемент множества.
3.
nextO — превращает объект (указатель) типа Iterator в ссылку, указывающую на следующий элемент.
4.
last() — возвращает true, если объект типа Iterator указывает за конец множества в результате последовательного прохода по его элементам.
Иногда методы getltemO и nextO объединяются.
Ниже приводится программный код для полного прохождения некоторым итератором i множества объектов. Соответствующие элементы, а также их порядок достаточно эффективно описываются итератором i. Когда каждый из элементов определен, метод doWhateverO выполняет свою функцию над элементом.
for( i.firstO: i.lastO; i.nextO ) i .getltemO .doWhareverO:.
6.5.3.3. Образец проектирования Mediator.
Проблема: довольно часто объекты классов тесно взаимодействуют друг с другом, причем достаточно нетривиальными способами. Нам необходимо перехватить поведенческие связи между объектами, не предоставляя объектам сведений друг о друге. Например, в игре Встреча при вызове графического интерфейса пользователя для задания характеристик персонажа появляется окно со списком характеристик (Java-объект типа Component). Также появляется текстовое окно для задания значения конкретной характеристики игрового персонажа (другой объект типа Component). Действия, предпринятые в отношении окна со списком, некоторым образом отражаются и на текстовом окне.
Мы не хотим, чтобы в вывод окна со списком характеристик персонажа был «зашит» расчет значений этих характеристик или способ отображения текстового окна. Это не только может повлиять на переносимость окна со списком, но и будет препятствовать изменениям в проектировании, а также затруднит использование такого же окна со списком в другом выводе при взаимодействии с другими компонентами.
Другой пример взаимодействия объектов — затенение пунктов меню текстового редактора, когда они неприменимы к отображаемому тексту. В данном случае взаимодействуют меню и содержимое экрана.
Образец проектирования Mediator: Mediator решает эту проблему встраиванием в каждый промежуточный компонент ссылки на общий промежуточный объект (а не на конкретный промежуточный объект). Этот промежуточный объект и берет на себя бремя технического обеспечения связей между объектами (рис. 6.24).
Рис. 6.24. Образец проектирования Mediator
Образец проектирования на рис. 6.24 состоит из базового класса Mediator, каждый подкласс которого инкапсулирует необходимую взаимосвязь между объектами, принадлежащими классам ConcreteColleaguel, ConcreteColleague2 и т. д. Все взаимодействующие объекты относятся к подклассам базового класса Col l eague. который ссылается на Mediator. Таким образом, взаимодействующим объектам нет необходимости знать друг о друге.
Нижняя часть рис. 6.24 иллюстрирует применение образца проектирования Mediator к примеру с игрой Встреча. Когда в окне со списком выбирается одна из характеристик (в левой части рисунка), объект QualListDisp сообщает нам, что он агрегировал промежуточный объект EncounterDisplay (ОкноВстречи) для обработки этого события. Этот промежуточный объект, заданный непосредственно в ходе работы программы как SetQualitiesDisplay, сообщает объекту QualValueDisp, что необходимо отобразить значение данной характеристики. Хотя это далеко не прямой путь, однако он приводит нас к достижению цели: QualListDisp и QualValueDisp не ссылаются друг на друга. Таким образом, эти объекты могут использоваться как составляющая другого поведения приложения (см. пример в конце главы).
6.6. Библиотека стандартных шаблонов (STL) С++.
Библиотека стандартных шаблонов (STL — Standard Template Library), созданная А. Степановым, является библиотекой стандартных многократно используемых структур данных и алгоритмов, основанной на использовании шаблонов. STL — это часть стандарта ANSI С++. Исчерпывающую информацию по STL можно найти в [81]. Основные составляющие STL:.
♦ контейнеры. Содержит коллекции объектов: векторы, списки, стеки и очереди. Например, для классов X класс 1 i st является контейнером, обеспечивающим доступ к последовательностям объектов переменной длины, который представлен двусвязным списком. list имеет метод push_back(X) для помещения объектов в конец списка;.
♦ алгоритмы. Алгоритмы манипулируют STL-контейнерами и включают в себя: вставку, удаление, поиск и сортировку. Например, алгоритм поиска объекта может быть представлен функцией find (*Х begin, *Х end, X objectWanted), где X является классом;.
♦ итераторы. Итераторы представляют операцию над элементами контейнера, что уже обсуждалось в разделе 6.5.3.2;.
♦ функциональные объекты. Каждый функциональный объект принимает аргумент и возвращает результат, но при этом он остается исходным объектом. К функциональным объектам относятся следующие операции: деление, логическое И, «Не равно» и т. п. Одним из примеров является функция сбора выбранных элементов в последовательность: accumulate( Inputlterator first, Inputlterator last, X initialValue, BinaryOperation binaryOp ).
где базовые классы, ссылающиеся на список параметров, определены в STL; ♦ адаптеры. Адаптеры обсуждались выше в разделе 6.5.2.3.
6.7. Стандарты, нотация и инструментальные средства детального проектирования.
6.7.1. Стандарт IEEE 890.
Вернемся к стандарту IEEE 890 для SDD, описанному в главе 5 применительно к программной архитектуре. Раздел этого документа, посвященный детальному проектированию, состоит из поочередного описания каждого модуля (пакета) с детальным описанием каждого блока данных. Для объектно-ориентированного проектирования последнее может быть заменено детальным описанием каждого класса.
1.
Введение.
1.1.
Цель.
1.2.
Описание проекта.
1.3.
Определения, сокращения и термины.
2.
Ссылки.
3.
Описание декомпозиции.
3.1.
Модульная декомпозиция.
3.1.1.
Описание модуля 1.
3.1.2.
Описание модуля 2.
3.2.
Декомпозиция на параллельные процессы.
3.2.1.
Описание процесса 1.
3.2.2.
Описание процесса 2.
3.3.
Декомпозиция данных.
3.3.1.
Описание блока данных 1.
3.3.2.
Описание блока данных 2.
4.
Описание зависимостей.
4.1.
Межмодульные зависимости.
4.2.
Межпроцессные зависимости.
4.3.
Зависимости внутри данных.
5.
Описание интерфейса.
5.1.
Модульный интерфейс.
5.1.1.
Описание модуля 1.
5.1.2.
Описание модуля 2.
5.2.
Интерфейс процессов.
5.2.1.
Описание процесса 1.
5.2.2.
Описание процесса 2.
6.
Детальное проектирование.
6.1.
Детальное проектирование модулей.
6.1.1.
Модуль 1: детали.
6.1.2.
Модуль 2: детали.
6.2.
Детальное проектирование данных.
6.2.1.
Блок данных 1: детали.
6.2.2.
Блок данных 2: детали.
6.7.2.
Язык UML.
Мы уже использовали UML для описания вариантов использования и классов, относящихся к архитектуре (разделы 6.3 и 5.2.2). UML позволяет представить все методы в детальном проектировании. Поскольку классы, как правило, содержат множество методов, зачастую желательно их выборочное представление. Это стало возможным благодаря инструментам Rational Rose и Together/J. Они также существенно облегчают масштабирование объектной модели.
6.7.3.
Инструменты, использующие исходный код: Javadoc.
Диаграммы иерархии классов, использующие гиперссылки, могут быть сгенерированы прямо из исходного кода. Например, Javadoc генерирует списки классов, принадлежащих пакетам, и методов, принадлежащих классам. Из комментариев в исходном коде, оформленных специальным образом, Javadoc также генерирует описания этих компонентов. Исходный программный код с комментариями Javadoc приводится в листинге 6.2. Примеры, в которых широко используется Javadoc, вы найдете в конце глав 7 и 8. Назначение ключевых слов /**...*/, @author, (aversion и @param понятно из приводимого ниже вывода.
Листинг 6.2. Исходный программный код с комментариями Javadoc.
/**.
* Персонаж ролевой игры.
* (Pauthor Eric Braude.
* (aversion 0.1, 14.07.98 */.
Листинг 6.2 (продолжение).
public abstract class GameCharacter {.
/** Имя игрового персонажа; при инициализации null */ private String _name;.
/** Ни одно имя не будет длиннее этой величины */ public final static int alltimeLimltOfNameLengthO.
/** Для регистрации */ protected void displayO.
/** Доступ к _паше */ public String getNameO.
/** Подклассы должны объявлять ограничение на размер имен.
* персонажей */.
protected abstract int MaxNumCharsInNameO;.
/** Устанавливает значение _name равным aName, если длина.
* не превосходит aMaxNumCharsO: в противном случае.
* обрезается.
* Наследники должны использовать это для setName( String ),.
* но не перезаписывать.
* @param aName: предлагаемое имя для _name.
* @param aMaxNumChars: ограничение на размер aName */.
protected final void setName( String aName ).
Из программного кода, приведенного в листинге 6.2, может быть сгенерирована следующая документация Javadoc, представленная ниже в отредактированном виде. Вывод Javadoc можно сделать более привлекательным, если в исходный код вставить теги HTML.
Class Characters.GameCharacter.
java.lang.Object.
I.
+ — Characters.GameCharacter.
public abstract class GameCharacter extends Object Персонаж ролевой игры.
Variable Index.
_name.
Имя игрового персонажа; при инициализации null Constructor Index GameCharacter() Method Index.
alltimeLimitOfNameLength().
Ни одно имя не будет длиннее этой величины displayO.
Для регистрации getName().
Доступ к _пате main(String[ ]).
Протестировать этот класс maxNumCharsInName().
Подклассы должны объявлять ограничение на размер имен setName(String. int).
Устанавливает значение jiame равным aName. если длина не превосходит aMaxNumCharsO: в противном случае - обрезает. Наследники должны использовать это для setName( String ), но не перезаписывать testForGameCharacterClass(String).
Тестирует все методы данного класса Variables jiame.
private String jiame.
Имя игрового персонажа: при инициализации null Constructors GameCharacter public GameCharacterO Methods.
alItimeLimitOfNameLength.
private int alltimeLimitOfNameLengthO Ни одно имя не будет длиннее этой величины display.
protected void displayO Для регистрации getName.
protected String getNameО Доступ к jiame maxNumCharsInName.
protected static int maxNumCharsInName() Имя игрового персонажа: при инициализации null setName.
protected final void setName(String aName. int aMaxNumChars) Устанавливает значение jiame равным aName, если длина не превосходит aMaxNumCharsO: в противном случае - обрезает. Наследники должны использовать это для setName( String ). но не перезаписывать.
Parameters:.
aName: предлагаемое имя для _name aMaxNumChars: ограничение на размер aName.
main.
public static void main(String args[ ]) Протестировать этот класс testForGameCharacterClass(String).
public static void testForGameCharacterClass(String aDestination).
throws IOException Тестирует все методы данного класса.
Такие инструменты, как Javadoc, реализуют так называемое обратное проектирование. Это означает, что вся документация по продукту (включая проект) производится после, а не до создания продукта. В принципе, в любом проектировании в некоторой степени присутствует обратное проектирование. В частности, программисты обычно добавляют методы, не предусмотренные при детальном проектировании. Однако обратное проектирование неэффективно, если оно заменяет выбор архитектуры и детальное проектирование.
Javadoc используется в примере в конце главы. Исчерпывающим источником информации по Javadoc является [67].
6.8. Влияние детального проектирования на проект.
В результате проведения детального проектирования план проекта становится более конкретным во многих отношениях. В частности, теперь с большей точностью может быть проведена оценка стоимости проекта, графики работ могут быть разбиты вплоть до конкретных задач, которые, в свою очередь, могут быть распределены между разработчиками.
ОДИН ИЗ СПОСОБОВ МОДЕРНИЗИРОВАНИЯ ПРОЕКТА ПОСЛЕ -.
ЗАВЕРШЕНИЯ ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ.
1.
Убедитесь, что в SDD отражена последняя версия детального проектирования, зафиксированная после инспектирования.
2.
Внесите все детали в план-график (SPMP).
3.
Поставьте конкретные задачи перед членами команды (SPMP).
4.
Уточните оценки стоимости и длительности проекта (см. ниже).
5.
Исправьте SCMP, чтобы отразить его новые части.
6.
Произведите ревизию процесса, в результате которого было проведено детальное проектирование, и выявите возможные улучшения, включая:.
♦ затраченное время на подготовку проекта, инспектирование и внесение изменений;.
♦ результаты поиска дефектов. Определите количество найденных, оставшихся, обнаруженных на стадии детализации, исправленных при детальном проектировании дефектов. Выясните, где они допущены (это относится и к предыдущим фазам, и к стадиям детального проектирования).
6.8.1. Оценка объема работ с помощью детального проектирования.
Поскольку с помощью детального проектирования мы можем оценить количество и объем необходимых приложению методов, становится возможной и более точная оценка общего объема работ. Далее (см. главу 2) по объему работ может быть вычислена их стоимость.
ОДИН ИЗ СПОСОБОВ ОЦЕНИТЬ ОБЪЕМ И ДЛИТЕЛЬНОСТЬ -.
ПРОЕКТА ПРИ ПОМОЩИ ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ.
1.
Начните со списка методов.
Убедитесь в его полноте во избежание получения заниженной оценки.
2.
Оцените для каждого метода количество строк программного кода.
♦ Классифицируйте методы как очень малые, малые, средние, большие, очень большие.
Нормальное соотношение методов: ±7%/24%/38%/24%/7%. + Используйте свои данные для перевода оценок в количественные значения. В качестве альтернативы используйте таблицу Хэмфри (табл. 6.2).
3.
Получите общее количество строк программного кода.
4.
Преобразуйте количество строк программного кода в человеко-часы.
По возможности используйте свои методы преобразования.
В качестве альтернативы используйте опубликованные методы.
5.
Убедитесь в том, что ваши оценки размера методов и длительности работ будут проанализированы и сохранены в конце проекта.
Для получения новой оценки продолжительности проекта снова может быть использована модель СОСОМО. При определении количества строк программного кода для очень малых, малых и т. д. задач лучше всего использовать личные данные. При отсутствии таких данных могут использоваться корпоративные данные, данные отдела или подразделения. В крайнем случае можно обратиться к таблице Хэмфри [48] (табл. 6.2). Эта таблица помогает определить количество строк программного кода С++, реализующих один метод.
Таблица 6.2. Оценка количества строк программного кода (по Хэмфри)
Тип метода
Категория
Очень малые
Малые
Средние
Большие
Очень большие
Вычисления
2,34
5,13
11,25
24,66
54,04
Обработка данных
2,60
4,79
8,84
16,31
30,09
Ввод-вывод
9,01
12,08
16,15
21,62
28,93
Логика
7,55
10,98
15,98
23,25
33,83
Структура
3,88
5,04
6,56
8,53
11,09
Обработка текста
3,75
8,00
17,07
36,41
7,67
Вычислительные методы представляют собой численные расчеты; методы обработки данных манипулируют данными (например, изменяют их формат); методы логики в основном посвящены ветвлениям; установочные методы выполняют инициализацию; методы структуры определяют состояние; методы обработки текста манипулируют текстом. Оценки методов, совмещающих в себе перечисленные выше типы, могут быть получены усреднением. Например, обычный (средний по объему) метод, который реализует вычисления, однако имеет также значительную логическую составляющую, может быть оценен как имеющий (11,25 + 15,98)/2 = 13,62 строк программного кода.
В [71] приводится оценка, согласно которой в среднем Java и С++ требуют одинакового количества строк программного кода для реализации заданной функциональности.
Такие термины, как «очень малый» и «малый», довольно расплывчаты, поскольку выражают лишь количественные категории, а не точные значения. Эти категории могут частично перекрываться, в этом случае следует использовать средние значения из соответствующей таблицы. (Вообще говоря, это является упрощением нечеткости, но вполне удовлетворяет нашим требованиям.) Нечеткие термины в достаточной степени практичны, так как они просты для понимания. К тому же их можно уточнить путем введения категорий с нормальным распределением (рис. 6.25).
Рис. 6.25. Нормальное распределение числа методов соответственно их объему
В среднем около 38 % методов могут быть отнесены к категории средних, 24 % — к малым и 7 % — к очень малым. Эти числа получены на основании того факта, что около 38 % значений в нормальном распределении лежат в пределах половины стандартного отклонения от среднего значения. На практике, если количество методов, которые вы оценили, например, как очень большие, сильно отличается от 7 %, следует смириться с тем, что ваше приложение действительно имеет нестандартное количество очень больших методов, либо провести ревизию ваших оценок.
В качестве примера произведем оценку объема метода execute О класса Engagement (Контакт). Этот метод реализует пересчет значений характеристик персонажа, что является важнейшим механизмом выполнения процесса контакта. Таким образом, мы можем классифицировать метод executeO как «вычисления». Размер метода executeO не является чем-то особо выдающимся, поскольку метод состоит из обычного последовательного вычисления значений. Следовательно, мы оценим объем этого метода как «средний». В итоге получим оценку вклада метода executeO в общий программный код в размере 11,25 строк.
Организация, соответствующая требованиям пятого уровня модели СММ (см. раздел 1.8.3), вместо действительных размеров методов обычно предоставляет их оценки, с тем чтобы улучшить процесс оценивания.
В примере в конце главы эти оценки применены к видеоигре Встреча.
6.9. Детальное проектирование и качество.
Качественное выполнение детального проектирования означает, что мы в состоянии его понять. В этом есть и эстетический аспект; кроме того, понятный проект говорит сам за себя. Тем не менее существуют полезные количественные меры для оценки качества проектирования. В этом разделе мы рассмотрим количественные меры эффективности детального проектирования. Шаги, гарантирующие качество детального проектирования, демонстрируются врезками «Один из способов...».
ОДИН ИЗ СПОСОБОВ ПРОВЕСТИ ИНСПЕКТИРОВАНИЕ -.
ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ (1).
1.
Будьте готовы фиксировать метрики во время процесса проектирования.
♦ В том числе затрачиваемое время, типы дефектов и строгость требований.
2.
Убедитесь, что каждый модуль архитектуры полностью раскрыт.
3.
Убедитесь, что каждая деталь относится непосредственно к архитектуре.
♦ Если какая-либо деталь не принадлежит ни к одному из модулей архитектуры, возможно, потребуется пересмотреть архитектуру.
4.
Убедитесь, что вариант проектирования охватывает все необходимые функции.
5.
Убедитесь, что проектирование является полным (в смысле классов и методов).
6.
Убедитесь в том, что проектный вариант тестируем.
Вопросы проведения инспектирования обсуждались в главе 1.
ОДИН ИЗ СПОСОБОВ ПРОВЕСТИ ИНСПЕКТИРОВАНИЕ -.
ДЕТАЛЬНОГО ПРОЕКТИРОВАНИЯ (2).
1.
Проверьте детальное проектирование на:.
♦ простоту.
Проектный вариант, который немногие могут понять (даже после серьезных усилий!) достаточно дорог для технической поддержки и может привести к появлению дефектов;.
♦ обобщенность.
Допускается ли возможность проектирования аналогичных приложений;.
♦ расширяемость. Допускаются ли улучшения;.
♦ эффективность. Скорость, хранение данных;.
♦ переносимость.
2.
Убедитесь в том, что обеспечена полная детализация:.
♦ только программный код не является «деталью»;.
♦ детальная работа все равно должна быть когда-то сделана, и это лучшее время для ее выполнения: не откладывайте!.
Некоторые метрики для детального проектирования, соответствующие шагу 1 «Один из способов провести инспектирование детального проектирования», представлены в этом разделе. Шаги 2, 3 и 4 проверяют то, что детальное проектирование охватывает всю архитектуру, и только ее.
Шаг 5 гарантирует полноту проектирования. То, что каждый метод каждого класса правильно описан, проверить достаточно легко, но как мы можем удостовериться в том, что предусмотрели все необходимые методы и классы? Для этого необходимо вернуться к требованиям и убедиться, что разработанный нами детальный проект отвечает всем поставленным требованиям. Если мы имеем структуру организации требований, подобную приведенной в примере в конце главы, то мы точно знаем, что каждому функциональному требованию соответствует свой метод. Таким образом, проверка полноты функциональности сводится к гарантии того, что каждый из методов будет вызван в соответствующий момент работы приложения. Например, рассмотрим следующее требование.
3.2.ПВ.3.2. Возможность настройки характеристик персонажа.
Значение любой характеристики игрового персонажа может быть задано, как только персонаж оказывается в одиночестве в игровой зоне. Задаваемое значение должно быть меньше либо равно сумме значений всех характеристик персонажа.
Мы уже убедились в существовании функции, реализующей это требование. Однако для того чтобы убедиться, что наш проект обеспечивает выполнение этой функции, нам необходимо «пройти» через всю последовательность возможных вызовов этой функции. Для этого необходимо разработать набор тестов, результаты которых следует сохранить для фазы тестирования. Вот один из примеров.
Начать игру; вызвать окно для задания характеристик; задать характеристику; еще раз задать характеристику; закрыть окно.
Попасть в зону без внешних персонажей; вызвать окно для задания характеристик; задать характеристику; закрыть окно.
Закончить контакт; дождаться ухода внешнего персонажа; вызвать окно для задания характеристик; задать характеристику; закрыть окно.
Для каждого из этих сценариев мы проверили то, что соответствующие классы и методы действительно существуют. Как только мы сделали это для каждого функционального требования, можно переходить к проверке нашего проекта с точки зрения функциональности. Аналогично мы можем поступить с нашим детальным проектированием для каждого нефункционального требования: мы можем проверить мысленно или с помощью вычислений (например, рассчитывая время), обеспечивается ли нашим проектированием каждое из этих требований. И снова работа по созданию таких последовательностей может быть использована позднее при разработке тестов. Затем мы продолжаем выполнять шаги, представленные в «Одном из способов провести инспектирование детального проектирования» (1) и (2).
Шаг 6 осуществляется для обеспечения возможности тестирования. Другими словами, на этом шаге мы выясняем, удобны ли элементы проектирования для тестирования. Проект, который не может быть легко разделен на составные части, скорее всего, будет невозможно тестировать. Эффективным способом проверки этого свойства является написание тестов для каждого элемента проекта сразу, как только появляется его спецификация.
Шаг 7 относится к свойствам, которые мы ожидаем от детального проектирования. В идеале мы хотим наличия всех этих свойств, но на практике это зачастую неосуществимо. В частности, простота может препятствовать общности и расширяемости. Например, для получения общности в проектировании на рис. 1.2 мы использовали три класса вместо двух, пойдя на компромисс с простотой. Образцы проектирования тоже довольно часто предоставляют дополнительные классы. Таким образом, лучше заранее определиться с наиболее значимыми свойствами, а затем оценивать проектирование относительно них. Если требуется переносимость, мы можем представить план действий для реализации на любой желаемой платформе.
Шаг 8 проверяет то, что каждая деталь кратко описана до реализации в коде. Обычно разработчики откладывают многие детали до момента реализации в целях экономии времени. Однако зачастую этот подход оказывается ошибочным. Во время реализации необходимо рассматривать множество взаимосвязей, и если заранее продумать все детали и по отдельности проинспектировать их, можно получить существенную отдачу.
6.9.1.
Качество и метрики в детальном проектировании.
Метрики детального проектирования обычно включают в себя подсчет количества модулей, функций, точек входа и выхода. Для объектно-ориентированных приложений по аналогии подсчитывается количество пакетов, классов, методов, параметров, атрибутов и т. д.
Более общей, а поэтому и более сложной оценкой является метрика 19 стандарта IEEE — структура проектирования [59], которая определяет «простоту детального проектирования» программы.
6.9.2.
Инспектирование детального проектирования.
Основные принципы и правила проведения инспектирования были изложены в разделе 1.6.4. Инспектирование детального проектирования состоит в инспектировании его классов, прототипов методов этих классов (имя, возвращаемый тип, типы параметров), блок-схем и псевдокода, а также взаимоотношений классов и методов в рамках различных моделей. Эти модели могут содержать модели вариантов использования и соответствующие им диаграммы последовательности, модель классов, модели переходов состояний и модели потоков данных.
6.9.2.1. Классификация дефектов.
При инспектировании всегда собираются данные о каждом дефекте, такие как его серьезность, тип и возможный источник ошибки в жизненном цикле проекта. Стандарт IEEE 1044.1 классифицирует серьезность дефектов так, как показано в табл. 6.3.
Таблица 6.3. Классификация серьезности ошибок по IEEE 1044.1
Степень серьезности дефекта.
Высочайшая
Описание.
Сбой вызывает разрушение системы, невосполнимую потерю данных или подвергает опасности персонал
Высокая
Вызывает повреждение критических системных функций при отсутствии обходных путей
Средняя
Вызывает повреждение критических системных функций, однако существует решение этой проблемы
Низкая
Доставляет некоторые неудобства или неприятности
Отсутствует
Ничего из перечисленного
Менее дорогостоящая (ввиду меньших затрат времени на сбор статистики), но и менее полезная классификация может быть получена с помощью использования отбраковки (табл. 6.4).
Таблица 6.4. Классификация серьезности дефектов с использованием отбраковки
Степень серьезности дефекта
Описание
Значительная
Не удовлетворяются требования
Средняя
Дефект не может быть отнесен ни к одному из двух других типов
Незначительная
Дефект, который не повлияет на работу и техническую поддержку приложения
Дефекты могут относиться к одному из перечисленных ниже типов, которые взяты из стандарта IEEE 1044.1-1995. Типы, относящиеся к детальному проектированию для инспектирования на уровне Javadoc, помечены символами [XDOC], а для инспектирования на уровне псевдокода — [PS].
♦ Проблема логики (забытые случаи или шаги; повторенная логика; пропуск граничных условий; неважные функции; некорректное прерывание; недостаток текста условия; проверка неправильных переменных; некорректность итерационных циклов и т. д.) [PS],.
♦ Вычисления (некорректное или недостаточное уравнение; потеря точности; ошибка в знаке) [PS].
♦ Интерфейс и синхронизация (некорректная обработка прерываний; некорректная синхронизация ввода-вывода, несоответствие модулей или подпрограмм) [PS],.
♦ Обработка данных (некорректная инициализация данных; некорректное получение или хранение данных; некорректный масштаб или единицы измерения данных) [XDOC].
♦ Границы данных [XDOC, PS],.
♦ Данные (некорректны или отсутствуют данные с датчиков, данные оператора, данные таблиц, внешние данные, выходные данные, входные данные) [XDOC, PS].
♦ Документация (неоднозначное описание и т. д.) [XDOC, PS].
♦ Качество документа (несоответствие применяемым стандартам и т. д.) [XDOC, PS],.
♦ Расширение (изменение в требованиях и т. д.) [XDOC, PS],.
♦ Ошибки, возникшие в результате предыдущих исправлений [XDOC, PS].
♦ Проблема представления (связана с фазой тестирования).
♦ Возможность взаимодействия (несовместимость с другими программами или компонентами) [XDOC, PS].
Соответствие стандартам [XDOC, PS].
♦ Другие (не перечисленные выше) [XDOC, PS],.
6.9.2.2. Примеры инспектирования детальных требований.
В этом разделе мы будем инспектировать примеры псевдокода. В центре нашего внимания при инспектировании находятся полноправные дефекты (когда методы правильно выбраны и соответствуют требованиям) и дефекты по пропуску (когда необходимо добавить еще несколько методов).
Псевдокод метода должен быть проверен на удовлетворение соответствующему требованию из SRS или из SDD. Вот, например, набросок D-требований из SRS для игры Встреча:.
[важно] Все персонажи игры имеют одинаковый набор характеристик. Значение каждой характеристики должно быть неотрицательным вещественным числом, заданным с точностью до одной десятой. Значения всех характеристик изначально заданы одинаковыми, а их сумма составляет 100. В первой версии будут следующие характеристики: сосредоточенность, выносливость, ум, терпение и сила. Значение характеристики не может лежать в промежутке между 0 и 0,5.
Это требование реализовано функцией adjustQuality( String aQuality, float aQualityValue ) с псевдокодом, который необходимо проинспектировать (рис. 6.26).
Рис. 6.26. Инспектирование псевдокода
Инспектирование этого псевдокода вскрывает следующие дефекты.
1.
Строка 9: необходимо упомянуть метод setQualityO.
2.
Строка 10: недостаточно подробно описано, как получать остальные значения характеристик; кроме того, почему всегда «уменьшить» (почему бы иногда не «увеличить»)?.
Напомним, что в процессе инспектирования необходимо лишь указывать на существование дефекта, но ни минуты времени не должно быть потрачено на попытки устранить этот дефект. Классификация дефекта 2 (строка 10) с помощью отбраковки по степени серьзности более предпочтительна, так как ее интерпретация может привести к значительным изменениям в продукте. Используя классификацию стандарта IEEE 1044.1, получим, что это вычислительный дефект.
6.10. Подведение итогов.
Подведем итоги обсуждения детального проектирования в этой главе.
♦ Выполняйте детальное проектирование так, чтобы оно позволяло написать программный код.
♦ Старайтесь использовать стандартные образцы проектирования.
♦ Определяйте выбранные алгоритмы:.
♦ с помощью блок-схем;.
♦ с помощью псевдокода.
♦ Применяйте выбранный инструментарий.
♦ Например, Javadoc.
Упражнения.
Ответы и подсказки для упражнений, помеченных символами «о» или «п», приводятся в конце этой главы.
Вопросы для проверки.
П6.1. В двух-трех предложениях сформулируйте условия, при которых следует использовать следующие средства детального проектирования:.
♦ блок-схемы;.
♦ псевдокод.
П6.2. Назовите три креационных образца проектирования и сформулируйте их назначение в одном-двух предложениях.
П6.3. Назовите три структурных образца проектирования и сформулируйте их назначение в одном-двух предложениях.
П6.4. Назовите три образца проектирования, основанных на поведении, и сформулируйте их назначение в одном-двух предложениях.
Общие упражнения.
06.1. Выполните оценку числа строк Java-кода для следующего набора методов. Методы грубо упорядочены по возрастанию объема. Считайте, что об этом коде не известно больше ничего.
♦ Метод 1 — ввод-вывод;
+ Метод 2 — обработка текста;
♦ Метод 3 — вычисления;
♦ Метод 4 — логика;
♦ Метод 5 — обработка данных;
♦ Метод 6 — вычисления;
+ Метод 7 — обработка текста;
♦ Метод 8 — обработка данных;
♦ Метод 9 — структура;
♦ Метод 10 — вычисления;
♦ Метод 11 — обработка текста;
4- Метод 12 — обработка данных.
06.2. Выполните проектирование (как проектирование архитектуры, так и детальное) приложения управления счетом согласно приведенным ниже требованиям. Если вы обнаружите в требованиях дефекты, исправьте их. Составьте отчет о времени, которое потребовалось вам на наиболее важные операции, с точностью до пяти минут.
1.
Система отображает текущий баланс в системном окне.
2.
Система разрешает пользователю сделать вклад в размере не более $10 ООО.
3.
Система разрешает пользователю снимать со счета любую сумму. Если пользователь попытается снять сумму, превышающую остаток средств на счете, система выводит сообщение: «Эта сумма превышает остаток на счете».
Упражнения в команде.
К6.1. (SDD).
Составьте SDD для вашего проекта, опираясь на стандарт IEEE. Используйте при этом самую позднюю версию SRS.
Определите время, потраченное группой и индивидуально. Подсчитайте время для отдельных составляющих работы: архитектура, инспектирование, обзоры,.
детальное проектирование. Прокомментируйте полученные результаты и решите, каким образом можно было распределить время более эффективно. Критерии оценки.
1.
Ясность («Отлично» — SDD непосредственно ведет к пониманию архитектуры и детального проектирования).
2.
Полнота («Отлично» — достаточно, чтобы охватить все требования).
3.
Изящество («Отлично» — допускает расширение и сопровождение).
4.
Выражение улучшения процесса («Отлично» — демонстрирует определенное пошаговое улучшение).
Примеры.
Мы опишем два варианта детального проектирования: каркаса ролевой видеоигры и приложения Встреча. Благодаря такому разделению мы упростим себе повторное использование и сопровождение каркаса.
Пример 1. Каркас ролевой видеоигры. Детальное проектирование (оставшиеся части SDD).
[Примечание для студентов. Это продолжение документа SDD. Его оглавление вы найдете в главе 5-].
6. Детальное проектирование каркаса ролевой видеоигры.
Архитектура пакетов, описываемых в этом разделе, приводится на рис. 5.40.
6.1. Детальное проектирование модулей.
[Примечание для студентов. В этом разделе приводятся все необходимые нетривиальные свойства модулей, описанных в разделе 3.1 SDD для каркаса ролевой игры.].
6.1.1. Пакет РолеваяИгра.
Все события мыши прослушиваются объектами класса СлушательМышиРолевой Игры, который наследуется от класса СлушателъМыши (рис. 6.27).
Объекты, чувствительные к событиям мыши, запрашивают обработку событий у объекта РолеваяИгра. Объект РолеваяИгра передает управление методу handleEventO агрегированного объекта СостояпиеИгры. Диаграмма последовательности для этого процесса показана на рис. 6.28. Для данной реализации игры методы либо показаны на рис. 6.28, либо являются тривиальными.
Рис. 6.27. Пакет РолеваяИгра
Рис. 6.28. Диаграмма последовательности, отражающая управление событиями мыши
[Примечание для студентов. Здесь можно поместить псевдокод методов, содержащихся в выделенных классах, а также описание вариантов использования. Поскольку в нашем случае методы и их подробное описание занимают лишь однудве строки, нам достаточно подписей на рис. 6.28.].
6.1.2.
Пакет Персонажи.
Данный раздел содержит уточнения к разделу 3.1.2 SDD.
В пакете Персонажи имеется единственный класс: ПерсонажИгры.
6.1.2.1. Класс ПерсонажИгры.
Метод setNameO класса ПерсонажИгры.
Предусловия — нет; постусловия — нет; инварианты — нет. Псевдокод:.
IF не задан параметр aName OR не задан параметр maxNumChrsInNameO Установить имя по умолчанию и показать его в системном окне.
ELSE.
IF параметр string имеет слишком большую длину.
укоротить в соответствии с maxNumCharsInNameO ELSE присвоить имя параметру.
6.1.3.
Пакет ИгроваяСреда.
Этот пакет описан на рис. 5.36 в разделе 3.1 SDD.
6.1.4.
Пакет Артефакты.
[Отсутствует на данной итерации.].
[Окончание детального проектирования для пакетов каркаса.].
Пример 2. Ролевая видеоигра Встреча. Детальное проектирование (оставшиеся части SDD).
[Это продолжение документа SDD, оглавление которого вы найдете выше в разделе 6.7.1.].
6. Детальное проектирование ролевой видеоигры Встреча.
Архитектура, отражающая связи между пакетами и классами предметной области, описываемыми в этом разделе, приводится на рис. 6.29.
6.1. Детальное проектирование модулей.
[Примечание для студентов. В данном разделе приводятся все необходимые детали каждого из модулей, описанных в разделе 3.1 SDD.].
6.1.1. Пакет ИграВстреча.
[Примечание для студентов. Здесь мы детализируем раздел 3.1.1 SDD, описывая классы пакета ИграВстреча. При этом рассматриваются только нетривиальные аспекты классов. Основным средством для описания являются диаграммы переходов состояний.]
Рис. 6.29. Архитектура пакетов видеоигры
Диаграмма переходов состояний для класса Встреча показана на рис. 3.11. В соответствии с ней разработана объектная модель пакета ИграВстреча (рис. 6.30). В рамках из сплошных линий на рис. 6.30 представлены классы предметной области. Существует единственный экземпляр объекта класса ИграВстреча. Он проходит через те же состояния, что показаны в верхней части диаграммы переходов состояний. Состояние Встреча агрегирует объект Контакт, инкапсулирующий вступление персонажей игры в контакт. Класс Контакт, в свою очередь, агрегирует класс ОкноКонтакта. Последний чувствителен к событиям мыши, которые регистрирует объект СлушателъМышиРолевойИгры. В процессе выполнения игры этот объект-слушатель ссылается на объект ИграВстреча, что позволяет объекту ИграВстреча обрабатывать события с учетом текущего состояния. Для этого используется образец проектирования State. Пакет ИграВстреча отвечает за перемещение внешнего персонажа. Эту задачу выполняют методы класса ПеремещениеПерсонажа, который является классом потоков.
Для реализации метода handleEventO в соответствии с образцом проектирования State классам необходимо иметь возможность обращаться к другим пакетам. Эта цель достигается благодаря использованию facade-объектов РолиВстречи и СредаВстречи.
Рис. 6.30. Пакет ИграВстреча
6.1.1.1.
Пакет ОкнаИгрыВстреча, вложенный в пакет ИграВстреча.
Для управления видами экрана, относящимися к тому или иному состоянию, существует отдельный вложенный пакет ОкнаИгрыВстреча (рис. 6.31). Qual Li stDi spl — это поле со списком, содержащее характеристики персонажа Встречи. Qual ValueDi spl — это текстовое поле, доступное только для чтения, в котором выводится значение характеристики. Для ввода значения характеристики предназначено редактируемое поле SetQual ValueDi spl. ЭлементОкнаВстречи является абстракцией, в которой собраны свойства и методы отображаемых на экране элементов Встречи.
Класс ОкноКонтакта разрабатывается для отображения текущего значения выделенной характеристики. Класс ОкноХарактеристикИгрока позволит игроку задавать значение любой характеристики. ОкноВстречи обобщает их свойства и представляет собой базовый класс (см. образец проектирования Mediator).
Более детально в данном документе эти классы не рассматриваются.
6.1.1.2.
Диаграммы последовательности для обработки событий 6.1.1.2.1. Событие: Игрок закрывает окно контакта.
Последовательность действий, которой сопровождается закрытие игроком окна со сведениями о контакте, показана на рис. 6.32.
Рис. 6.31. Вложенный пакет ОкнаИгрыВстреча
Рис. 6.32. Диаграмма последовательности для события «Игрок закрывает окно контакта»
6.1.1.2.2. Событие: Игрок завершает установку характеристик.
Последовательность действий, которой сопровождается завершение установки характеристик игроком, показана на рис. 6.33.
Рис. 6.33. Диаграмма последовательности для события «Игрок завершает установку характеристик»
6.1.1.2.3. Событие: Игрок перемещается в соседнюю зону.
Диаграмма последовательности для того случая, когда игрок переходит в соседнюю зону, щелкнув на соответствующей гиперссылке, показана на рис. 6.34.
Рис. 6.34. Диаграмма последовательности для события «Игрок перемещается в соседнюю зону»
6.1.1.2.4. Диаграммы последовательности для других событий.
Остальные события обрабатываются способом, аналогичным описанному в разделах 6.1.1.1-6.1.1.3.
6.1.1.ИВ. Класс ИграВстреча.
Это одиночный facade-icnacc пакета ИграВстреча. Наследование.
Этот класс наследуется от каркасного пакета РолеваяИгра. Атрибуты класса ИграВстреча.
EncounterGame encounterGameS.
Это одиночный объект ИграВстреча. Конструкторы класса ИграВстреча.
private EncounterGameO.
Предусловия: нет.
Постусловия: создает экземпляр ИграВстреча. Методы класса ИграВстреча.
public static EncounterGame getTheEncounterGame():.
Предусловия: нет.
Постусловия: значение encounterGameS отлично от нуля. Возвращаемое значение: encounterGameS.
6.1.1.КО. Класс Контакт.
Этот класс инкапсулирует контакты между персонажем игрока и внешним персонажем согласно требованию SRS 3.2.КО. Методы.
public void engageO; // согласно требованию 3.2.КО.3.1.
Предусловия: персонаж игрока и внешний персонаж находятся в одной зоне. Постусловия: значения характеристик персонажей соответствуют требованию SRS 3.2.К0.3.1; персонаж игрока и внешний персонаж находятся в случайно выбранных, но различных зонах.
6.1.1.ПП. Класс ПеремещениеПерсонажа.
[Примечание для студентов. В этом документе, как и в SRS, мы используем буквенные обозначения для именования подразделов. Это позволяет упростить поиск и добавление классов. Еще один плюс такой «нумерации» в том, что мы можем прослеживать здесь требования SRS.].
Данный класс управляет перемещением внешнего персонажа. Наследование.
Данный класс наследуется от java.lang.Thread. Методы.
public static EncounterGame run О;.
Изначально помещает внешний персонаж в зону подвал. Затем перемещает внешний персонаж из одной зоны в другую согласно соединениям между зонами. Соседние зоны для перехода выбирает случайным образом через случайные временные интервалы, в среднем равные 2 секундам.
6.1.1.ЯЯ.
Классы состояния Контакт, Ожидание, Подготовка и Отчет.
[Сокращением «ЯЯ» мы обозначили группу классов, которые мы не будем описывать по отдельности.] Наследование.
Эти классы наследуются от класса СостояпиеИгры из каркасного пакета. В каждом из этих классов согласно диаграммам последовательности из раздела 6.1.1.1 реализуется метод handleEventO.
6.1.2.
Пакет ПерсонажиВстречи.
[Этим разделом уточняется раздел 3.1.2 SDD.].
Структура пакета ПерсонажиВстречи показана на рис. 6.35. Он реализуется с применением образца проектирования Facade, при этом в качестве facade-объекта используется объект РолиВстречи.
Рис. 6.35. Пакет ПерсонажиВстречи
6.1.2.ВП. Класс ВнешнийПерсонаж.
Этот класс аналогичен описываемому далее классу ПерсонажИгрока. Он разрабатывается согласно требованиям SRS 3.2.ВП.
6.1.2.ПВ. Класс ПерсонажВстречи.
В этом классе инкапсулируются требования к персонажам Встречи, не заданные в пакете ПерсонажиИгры, согласно требованию SRS 3.2.ПВ.
Инвариант класса: значения qualValuel неотрицательны (описание qualValuel см. далее в перечне атрибутов класса). Наследование.
Этот класс наследуется от каркасного пакета ПерсонажиИгры. Атрибуты класса ПерсонажВстречи Согласно требованиям SRS 3.2.ПВ.1.
private static final String[] qualityTypes.
Представляет характеристики, которыми обладают персонажи Встречи. Это сосредоточенность, выносливость, ум, терпение и сила.
private f1oat[] qualValuel.
Массив значений характеристик. Конструкторы класса ПерсонажВстречи.
Согласно требованиям SRS Э.2.ПВ.З. Null-конструктор.
Постусловия: значения характеристик представляют собой равные доли числа 100.
protected EncounterCharacter( String nameP ) Постусловия:.
♦ значения характеристик представляют собой равные доли числа 100;.
♦ имя персонажа равно содержимому NameP. Методы класса ПерсонажВстречи.
public synchronized void adjustQuality( String qualityP, float qualityValueP ).
Этот метод удовлетворяет требованию SRS 3.2.ПВ.3.2.
Инварианты: нет.
Предусловия:.
qualityP принадлежит qualityTypesS[] AND qualityValueP >= 0.
AND qualityValuePПостусловия:.
qualityP имеет значение qualityValueP AND.
Значения остальных характеристик остаются в том же соотношении, что и раньше, за исключением значений меньших 0,5 — они приравниваются к нулю. Далее приводится псевдокод метода adjustQual ity().
Присвоить характеристике требуемое значение. IF Настраивается единственная ненулевая характеристика.
разделить введенное число поровну между всеми остальными характеристиками.
ELSE Изменить остальные характеристики так, чтобы сохранилось соотношение характеристик.
Обнулить характеристики, значения которых теперь меньше 0,5. public float getQualityValue( String qualityP ).
Предусловия: qualityP — строка, содержащая корректное имя характеристики. Возвращаемое значение: значение qualityP.
public float getTo1erance( ).
Возвращаемое значение: пороговое значение ненулевых характеристик.
public int maxNumCharsInName( ).
Возвращаемое значение: максимальное число символов, разрешенное в имени персонажа Встречи.
public float sumOfQualities( ).
Возвращаемое значение: сумма значений характеристик. Этот метод удовлетворяет требованию SRS 3.2.ПВ3.2.
public void showCharacter( Component compP, Graphics drawP, Point posP, int heightPixP, boolean faceLeftP ).
Отображает персонаж в компоненте пользовательского интерфейса compP с центром в точке posP, высотой heightPixP и, если значение faceLeftP истинно, смотрящим влево.
Этот метод удовлетворяет требованиям 3.2.ПИЛ и 3.2.ХИ.1.
private void setQuality( String qualityP, float valueP ).
Предусловия: qualityP — строка, содержащая корректное имя характеристики. Присваивает указанной характеристике значение, заданное параметром valueP, если он больше или равен 0,5. В противном случае характеристика обнуляется.
Данный метод удовлетворяет требованию 3.2.ПВ.3.2 (нижняя граница ненулевых значений характеристик).
6.1.2. ПИ. Класс ПерсонажИгрока.
Данный класс разрабатывается согласно требованиям SRS 3.2.ПИ. Наследование.
Данный класс наследуется от класса ПерсонажВстречи. Атрибуты.
private static final PlayerCharacter playerCharacterS:.
Это одиночный объект, представляющий персонаж игрока. Методы.
public static final PlayerCharacter getPlayerCharacterO;.
Этот метод возвращает playerCharacterS. 6.1.2.РВ. Класс РолиВстречи.
Метод, дающий представление об этом одиночном интерфейсном классе, приведен в разделе 5 данного документа.
Рис. 6.36. Пакет СредаВстречи
6.1.3.ГС. Класс ГиперссылкаСоединения.
Этот класс инкапсулирует способы перехода в соседние зоны. Он разрабатывается согласно требованию SRS 3.2.ГС. В этом классе реализуется интерфейс СлушателъМыши. Атрибуты.
private Connection connectionl; // соответствующее соединение зон Методы.
Единственный метод, достойный упоминания, — mousedickО. 6.1.3.30. Класс Зона.
Этот класс инкапсулирует области, в которых существуют персонажи, и разрабатывается в соответствии с требованием SRS 3.2.30. Наследование.
Этот класс наследуется от класса ИгроваяЗона. Атрибуты.
private String namel; // согласно требованию 3.2.30.1.1 private Image imagel; // согласно требованию 3.2.30.1.2 private String[] qualitiesl; // согласно требованию 3.2.30.1.3 private Vector connectionHyperlinksI:.
Методы.
public void displayO
6.1.3. Пакет СредаВстречи.
Классы этого пакета описывают окружение, в котором проходит игра (рис. 6.36).
Выводит изображение объекта-зоны на экран.
public static Area getArea( String areaNameP ).
Возвращает объект-зону, соответствующую areaNameP. Удовлетворяет требованию 3.2.30.2.
public static AreaConnection( String areaConnectionNameP ).
Возвращает объект AreaConnection, соответствующий areaConnectionNameP. Удовлетворяет требованию 3.2.30.2.
6.1.3.СВ. Класс СоединениеЗоныВстречи.
Этот класс инкапсулирует способы переходов в соседние зоны. Он наследуется от класса СоединениеЗоны и разрабатывается согласно требованию SRS 3.2.СВ. Наследование.
Этот класс наследуется от класса СоединениеИгровойЗоны каркасного пакета. Атрибуты.
private Area firstAreal: // согласно требованию 3.2.СВ.1.1 private Area secondAreal; // согласно требованию 3.2.СВ.1.1.
Методы.
Предоставляют доступ к указанным выше атрибутам. 6.1.3.СР. Класс СредаВстречи.
Представляет собой facade-клэсс для пакета СредаВстречи. Атрибуты.
private EncounterEnvironment encounterEnvironmentS; // одиночный facade-объект.
//[Имя зоны][Имя соединения зон][«North»|"South"|"East"|"West"]: private String[3] layouts:.
Методы.
public static EncounterEnvironment getEncounterEnvi ronmentO.
Возвращаемое значение: encounterEnvi ronmentS. public static String[3] getLayoutO Возвращаемое значение: 1 ayoutS.
6.2. Детальное проектирование данных.
Никаких структур данных, кроме упомянутых при описании классов в разделе 6.1, не имеется.

Популярные статьи

Свежие статьи