Поскольку этот пример, имея небольшой размер, прекрасно охватывает принципиальные свойства конструирования ОО-ПО, я часто использую его, когда необходимо в течение нескольких часов познакомить аудиторию с возможностью Метода. Показывая на практике (даже для людей с весьма слабой математической подготовкой), как каждый может перейти от классической декомпозиции к ОО-взгляду на вещи, и преимущества, получаемые в результате такого перехода, наш пример является замечательным педагогическим средством. Читателям еще в начале книги предлагалось прочесть эту лекцию, представляющую "рекламный ролик" ОО-метода проектирования.
Для облегчения задачи она написана как можно более независимо. Если эта лекция читается после изучения предыдущих лекций, то вы встретите некоторые повторения, в частности несколько коротких определений уже знакомых концепций.
Наша задача - спроектировать систему, представляющую некоторый общий тип интерактивных систем. В этих системах работа пользователя состоит в выполнении ряда этапов, и каждый этап поддерживается полноэкранной диалоговой панелью.
В целом процесс является простым и хорошо определенным. Сессия работы пользователя проходит через некоторое число состояний. В каждом состоянии отображается некоторая панель, содержащая вопросы к пользователю. Пользователь дает требуемые ответы, проверяемые на согласованность (вопросы повторяются, пока не будет дан корректный ответ); затем ответ обрабатывается некоторым образом, например обновляется база данных. Частью пользовательского ответа является выбор следующего шага, интерпретируемый как переход к следующему состоянию, в котором процесс повторяется.
Примером может служить система резервирования авиабилетов, где состояния представляют такие шаги обработки, как User
Типичная панель для состояния
(рис 2.1) Панель "Запрос Рейса"Сессия начинается в начальном состоянии Initial и заканчивается в заключительном состоянии Final. Мы можем представить всю структуру графом переходов, показывающим возможные состояния и переходы между ними. Ребра графа помечены целыми, соответствующими возможному выбору пользователя следующего шага при завершении состояния. На рис. 2.2 показан граф переходов системы резервирования авиабилетов.
Проблема, возникающая при проектировании и реализации таких приложений, состоит в достижении максимально возможной общности и гибкости. В частности:
(рис 2.2) Граф переходов в системе резервирования авиабилетовДавайте начнем с прямолинейной, без всяких ухищрений программной схемы. В этой версии наша система будет состоять из нескольких блоков по одному на каждое состояние системы: BEnquiry, BReservation, BCancellation и т. д. Типичный блок (выраженный не в ОО-нотации этой книги, а в специально подобранной для этого случая, хотя и удовлетворяющей некоторым синтаксическим соглашениям), выглядит следующим образом:
BEnquiry:
"Отобразить панель Enquiry on flights"
repeat
"Чтение ответов пользователя и выбор C следующего шага"
if "Ошибка в ответе" then
"Вывести соответствующее сообщение" end
until not "ошибки в ответе" end
"Обработка ответа"
case C in
C0: goto Exit,
C1: goto BHelp,
C2: goto BReservation,
...
end
Аналогичный вид имеют блоки для каждого состояния.
Что можно сказать об этой структуре? Ее нетрудно спроектировать, и она делает свое дело. Но с позиций
Наиболее очевидная критика связана с присутствием инструкций goto (реализующих switch языка C или "Вычисляемый Goto" Fortran), из-за чего управляющая структура выглядит подобно "блюду спагетти" и чревата ошибками.
Но goto - это симптом заболевания, а не настоящая причина. Мы взяли поверхностную структуру нашей проблемы -
Этот пример является отрезвляющим напоминанием, когда приходится слышать о преимуществах "моделирования реального мира" или "вывода системы из анализа реальности". В зависимости от того, как вы его описываете, реальный мир может быть простым или выглядеть непонятной кашей. Плохая модель приводит к плохому ПО. Следует рассматривать не то, насколько близко ПО к реальному миру, а насколько хорошо его описание. В конце этой лекции этому вопросу еще будет уделено внимание. |
Чтобы получить не просто систему, а хорошую систему, придется еще поработать.
Повторяя на этом частном примере эволюцию, пройденную программированием в целом, перейдем от низкоуровневого подхода goto к иерархической структуре, создаваемой при проектировании сверху вниз. Это решение принадлежит общему стилю, известному как "структурное", хотя этот термин следует использовать с осторожностью.
ОО-решение является, конечно же, структурным, соответствуя духу "структурного программирования", как оно изначально было введено Э. Дейкстрой.
Первым шагом на пути улучшения нашего решения будет придание центральной роли алгоритму обхода в структуре ПО. Диаграмма переходов - это всего лишь одно из свойств нашей системы, и нет никаких оснований, чтобы она правила над всеми частями системы. Ее отделение от остального алгоритма позволит, по крайней мере, избавиться от goto. Можно ожидать и большей общности, поскольку диаграмма переходов специфична для приложения, такого как резервирование авиабилетов, в то время как алгоритм
Что представляет собой диаграмма переходов? Абстрактно это функция transition, имеющая два аргумента - состояние и выбор пользователя. Функция transition (s, c) возвращает новое состояние, определяемое пользовательским выбором c в состоянии s. Здесь слово "функция" используется в его математическом смысле. На программном уровне можно выбрать реализацию transition либо функцией в программистском смысле, либо структурой данных, например массивом. На данный момент решение можно отложить и рассматривать transition просто как абстрактное понятие.
В добавление к функции transition необходимо спроектировать начальное состояние initial, - точку, в которой начинаются все сессии, и одно или несколько заключительных состояний как булеву функцию is_final. И снова речь идет о функции в математическом смысле, независимо от ее возможной реализации.
Зададим функцию transition в табличной форме со строками, представляющими состояние, и столбцами, отображающими пользовательский выбор:
| Состояние | Выбор | |||
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | |
| 1 (Initial) | -1 | 0 | 5 | 2 |
| 2 (Flights) | -1 | 0 | 1 | 3 |
| 3 (Seats) | 0 | 2 | 4 | |
| 4 ( |
0 | 3 | 5 | |
| 5 ( |
0 | 4 | 1 | |
| 0 (Help) | Return | |||
| -1 (Final) | ||||
Соглашения, используемые в таблице: здесь в состоянии |
Следуя традиционным рекомендациям декомпозиции сверху вниз, выберем "вершину" - главную функцию нашей системы. Это должна быть, очевидно, программа execute_session, описывающая выполнение полной интерактивной сессии.
(рис 2.3) Функциональная декомпозиция сверху-внизНепосредственно ниже (уровень 2) найдем операции, связанные с состояниями: определение начального и конечного состояний, структуру переходов и функцию execute_state, описывающую действия, выполняемые в каждом состоянии. На нижнем уровне 1 найдем операции, определяющие execute_state: отображение панели на экране и другие. Заметьте, что и это решение, также как и ОО-решение, описываемое чуть позже, отражает "реальный мир", в данном случае включающий состояния и элементарные операции данного мира. В этом примере и во многих других не в реальности мира состоит важная разница между ОО-подходом и другими решениями, а в том, как мы моделируем этот мир.
При написании программы execute_session попытаемся сделать наше приложение максимально независимым. (Наша нотация выбрана в соответствии с примером. Цикл repeat until заимствован из Pascal.)
execute_session is
-- Выполняет полную сессию интерактивной системы
local
state, next: INTEGER
do
state := initial
repeat
execute_state (state, >next)
-- Процедура execute_state обновляет значение next
state := transition (state, next)
until is_final (state) end
end
Это типичный алгоритм обхода диаграммы переходов. (Те, кто писал лексический анализатор, узнают образец.) На каждом этапе мы находимся в состоянии state, вначале устанавливаемом в initial ; процесс завершается, когда состояние удовлетворяет is_final. Для состояний, не являющихся заключительными, вызывается execute_state, принимающее текущее состояние и возвращающее в аргументе next выбор перехода, сделанный пользователем. Функция transition определяет следующее состояние.
Техника, используемая в процедуре execute_state, изменяющая значение одного из своих аргументов, никогда не подходит для хорошего ОО-проекта, но здесь она вполне приемлема. Для того чтобы сделать ее явной, используется "флажок" для " out " аргумента - next со стрелкой.
Для завершения проекта следует определить процедуру execute_state, описывающую действия, выполняемые в каждом состоянии. Ее тело реализует содержимое блока начальной goto -версии.
execute_state (in s: INTEGER; out c: INTEGER) is
-- Выполнить действия, связанные с состоянием s,
-- возвращая в c выбор состояния, сделанный пользователем
local
a: ANSWER; ok: BOOLEAN
do
repeat
display (s)
read (s, a)
ok := correct (s, a)
if not ok then message (s, a) end
until ok end
process (s, a)
c := next_choice (a)
end
Здесь вызываются программы уровня 1 со следующими ролями:
display (s) выводит на экран панель, связанную с состоянием s ;read (s, a) читает в a ответы пользователя, введенные в окнах панели состояния s ;correct (s, a) возвращает true, если и только если a является приемлемым ответом; если да, то process (s, a) обрабатывает ответ a, например, обновляя базу данных или отображая некоторую информацию, если нет, то message (s, a) выводит соответствующее сообщение об ошибке.Тип объекта, представляющего ответ пользователя, не будет уточняться. Значение a этого типа глобально представляет ввод пользователя, включающий и выбор следующего шага ( фактически во многом подобен классу, даже если остальная структура не является объектной.)
Для получения работающего приложения необходимо задать реализации программ уровня 1: display, read, , message и process.
Получили ли мы удовлетворительное решение? Не совсем. Оно лучше, чем первая версия, но все еще далеко от заявленных целей повторного использования и расширяемости.
Хотя с первого взгляда кажется, что нам удалось отделить общность, присущую приложениям такого типа, от специфических черт конкретного приложения, в реальности различные модули все еще тесно связаны друг с другом и с выбранным приложением. Главной проблемой остается структура данных, передаваемых в системе. Рассмотрим сигнатуры (типы аргументов и результатов) наших программ:
(рис 2.4) Сигнатура программЗамечание, звучащее подобно жалобе, состоит в том, что роль состояний всеобъемлюща. Текущее состояние s появляется как аргумент во всех программах, спускаясь с вершины execute_session, где оно известно как state. Так что кажущаяся простота и управляемость иерархической структуры, показанной на рис. 2.3, является ложью или, более точно, фасадом. За спиной формальной элегантности функциональной декомпозиции стоит неразбериха передачи данных. Истинная картина должна включать данные.
В основе технологии лежит битва между функциями и данными (объектами) за управление архитектурой системы. В необъектных подходах функции берут вверх над данными, но затем данные начинают мстить.
Месть проявляется в форме саботажа. Атакуя основания архитектуры, данные не пропускают изменения, - пока, подобно правительству не способному руководить своей перестройкой (в оригинале - perestroikа ), система не рухнет под собственной тяжестью.
(рис 2.5) Поток данныхВ этом примере структура рушится из-за необходимости различать состояния. Все программы уровня 1 должны выполнять различные действия, зависящие от состояния s: отображать панель для некоторого состояния, читать и интерпретировать ответы пользователя, определять корректность ответов, - для всех этих задач необходимо знать
inspect
s...
when Initial then
...
when Enquiry_on_flights then
...
....
end
Это приводит к длинной и сложной структуре и, что хуже всего, к неустойчивой системе, - любое добавление состояния потребует изменения всей структуры. Имеет место типичный случай необузданного распределения знаний. Слишком много модулей системы используют одну и ту же информацию - список всех возможных состояний, являющийся предметом изменений.
Если надеяться на получение общего повторно используемого решения, то ситуация еще хуже, чем может показаться. Дело в том, что во всех программах неявно присутствует еще один аргумент - приложение - система резервирования авиабилетов или другая проектируемая система. Так что программы, такие как display, если они действительно носят общий характер, должны знать все состояния всех возможных приложений! Аналогично функция transition должна содержать графы переходов для всех приложений - совершенно нереалистическое предположение.
Проблемы функциональной декомпозиции сверху вниз указывают, что нужно делать, чтобы получить хорошую ОО-версию.
Что пошло не так, в чем проблема? Слишком много передач данных свидетельствует обычно об изъянах в архитектуре ПО. Устранение этих изъянов приводит непосредственно к ОО-проекту, что находит отражение в следующем правиле проектирования:
Закон инверсии Если программы пересылают друг другу много данных, поместите программы в данные. |
Ранее модули строились вокруг операций (таких как execute_session и execute_state ) и данные распределялись между программами со всеми неприятными последствиями, с которыми нам пришлось столкнуться. ОО-проектирование ставит все с головы на ноги, - оно использует наиболее важные типы данных как основу модульности, присоединяя каждую программу к тому типу данных, с которым она наиболее тесно связана. Когда объекты одерживают победу, их бывшие хозяева - функции - становятся вассалами данных.
Закон
Конечно, желательно начинать ОО-проектирование с самого начала, - тогда не придется прибегать к
Пример "состояния" является типичным. Такой тип данных, играющий всеобъемлющую роль в передаче данных между программами, является первым кандидатом на роль модуля в ОО-архитектуре, основанной на классах (абстрактно описанных типах данных).
Понятие состояния было важным в оригинальной постановке проблемы, но затем в функциональной архитектуре эта важность была утеряна, - состояние было представлено обычной переменной, передаваемой из программы в программу, как если бы это было существо низкого STATE должно быть классом, одним из властителей структуры в нашей новой ОО-системе.
В этом классе мы найдем все операции, характеризующие состояние: отображение соответствующего экрана ( display ), анализ ответа пользователя ( read ), проверку ответа ( ), выработку сообщения об ошибке для некорректных ответов ( message ), обработку корректных ответов ( process ). Мы должны также включить сюда execute_state, выражающее последовательность действий, выполняемых всякий раз, когда сессия достигает заданного состояния (поскольку данное имя было бы сверхквалифицированным в классе, называемом STATE, заменим его именем execute ).
Возвращаясь к рисунку, отражающему функциональную декомпозицию, выделим в нем множество программ, принадлежащих классу STATE.
(рис 2.6) Компоненты класса STATEКласс имеет следующую форму:
...class STATE feature
input: ANSWER
choice: INTEGER
execute is do ... end
display is ...
read is ...
correct: BOOLEAN is ...
message is ...
process is ...
end
Компоненты input и являются атрибутами, остальные - подпрограммами (процедурами и функциями). В сравнении со своими двойниками при функциональной декомпозиции подпрограммы потеряли явный аргумент, задающий состояние, хотя он появится другим путем в клиентских вызовах, таких как s.execute.
В предыдущих подходах функция execute (ранее execute_state ) возвращала пользовательский выбор следующего шага. Но такой стиль нарушает правила хорошего проектирования. Предпочтительнее сделать execute командой. Запрос "какой выбор сделал пользователь в последнем состоянии?" доступен благодаря атрибуту . Аналогично, аргумент подпрограмм уровня 1 заменен теперь закрытым атрибутом input. Вот причина скрытия информации: клиентскому коду нет необходимости обращаться к ответам помимо интерфейса, обеспечиваемого компонентами класса.
Класс STATE описывает не частное состояние, а общее понятие состояния. Процедура execute - одна и та же для всех состояний, но другие подпрограммы зависят от состояния.
Наследование и отложенные классы идеально позволяют справиться с этими ситуациями. На уровне описания класса STATE мы знаем атрибуты и процедуру execute во всех деталях. Мы знаем также о существовании программ уровня 1 ( display и др.) и их спецификации, но не их реализации. Эти программы должны быть отложенными, класс STATE, описывающий множество вариантов, а не полностью уточненную абстракцию, сам является отложенным классом. В результате имеем:
indexing
description: "Состояния приложений, управляемых панелями"
deferred class
STATE
feature -- Access
choice: INTEGER
-- Пользовательский выбор следующего шага
input: ANSWER
-- Пользовательские ответы на вопросы в данном состоянии
feature -- Status report
correct: BOOLEAN is
-- Является ли input корректным ответом?
deferred
end
feature -- Basic operations
display is
-- Отображает панель, связанную с текущим состоянием
deferred
end
execute is
-- Выполняет действия, связанные с текущим состоянием,
-- и устанавливает choice - пользовательский выбор
local
ok: BOOLEAN
do
from ok := False until ok loop
display; read; ok := correct
if not ok then message end
end
process
ensure
ok
end
message is
-- Вывод сообщения об ошибке, соответствующей input
require
not correct
deferred
end
read is
-- Получить ответы пользователя input и choice
deferred
end
process is
-- Обработка input
require
correct
deferred
end
end
Для описания специфических состояний следует ввести потомков класса STATE, задающих отложенную реализацию компонент.
(рис 2.7) Иерархия классов StateПример мог бы выглядеть следующим образом:
class ENQUIRY_ON_FLIGHTS inherit
STATE
feature
display is
do
...Специфическая процедура вывода на экран...
end
...И аналогично для read, correct, message и process ...
end
Эта архитектура отделяет зерно от шелухи: элементы, общие для всех состояний, отделяются от элементов, специфичных для конкретного состояния. Общие элементы, такие как процедура execute, сосредоточены в классе STATE и нет необходимости в их повторном объявлении в потомках, таких ENQUIRY_ON_FLIGHTS. Принцип Открыт-Закрыт выполняется: класс STATE закрыт, поскольку он является хорошо определенным, компилируемым модулем, но он также открыт, так как можно добавлять в любое время любых его потомков.
Класс STATE является типичным представителем поведенческих классов ( behavior classes, см. лекцию 14 курса "Основы объектно-ориентированного программирования") - отложенных классов, задающих общее поведение большого числа возможных объектов, реализующих то, что полностью известно на общем уровне ( execute ) в терминах, зависящих от каждого варианта. Наследование и отложенный механизм задают основу представления такого поведения повторно используемых компонентов.
Для завершения проекта следует заняться управлением сессией. При функциональной декомпозиции эту задачу выполняла процедура execute_session - главная программа. Но мы знаем, как сделать это лучшим образом. Как ранее говорилось (см. лекцию 5 курса "Основы объектно-ориентированного программирования") главная функция системы, позиционируемая как верхняя функция в проектировании сверху вниз, - это нечто мифическое. Большие программные системы выполняют множество одинаково важных функций. И здесь основанный на АТД подход является предпочтительным. Вся система в целом рассматривается как множество абстрактных объектов, способных выполнять ряд служб ( services ).
Ранее мы рассмотрели одну ключевую абстракцию - STATE. Какая же абстракция в нашем рассмотрении осталась пропущенной? Ответ очевиден: центральным в нашей системе является понятие APPLICATION, описывающее специфическую интерактивную систему, подобную системе резервирования билетов. Это приводит нас к следующему классу.
(рис 2.8) Компоненты классов State и ApplicationЗаметьте, все не вошедшие в класс STATE программы функциональной декомпозиции стали теперь компонентами класса APPLICATION:
Execute_session - описывает, как выполнять сессию, ее имя теперь разумно упростить и называть просто execute, так как дальнейшую квалификацию обеспечивает имя класса.Initial и is_final - указывают, какие состояния имеют специальный статус в приложении. Конечно же, их разумно включить именно в класс APPLICATION, а не в класс STATE, поскольку они описывают свойства приложения, а не состояния, которое является заключительным или начальным только по отношению к приложению, а не само по себе. При повторном использовании состояние, бывшее заключительным в одном приложении вполне может не быть таковым для другого приложения.Transition - описывает переходы между состояниями приложения.Все компоненты функциональной декомпозиции нашли свое место в ОО-декомпозиции: одни в классе STATE, другие в APPLICATION. Это не должно нас удивлять. Объектная технология, о чем многократно говорится в этой книге, является прежде всего архитектурным механизмом, в первую очередь предназначенным для организации программных элементов в согласованные структуры. Сами элементы, возможно, нижнего уровня, те же самые или похожие на элементы необъектных решений. Объектные механизмы: абстракция данных, скрытие информации, утверждения, наследование,
Системе управления панелями, изучаемой в данной лекции, всегда необходимы: процедура execute_session, теперь просто execute ), чтение ввода пользователя ( read ), обнаружение заключительного состояния ( is_final ). Погружаясь в структуру, можно найти одни и те же элементы, независимо от выбранного подхода к проектированию. Что же меняется? - способ группирования элементов, создающий модульную архитектуру.
Конечно, нет необходимости ограничивать себя элементами, пришедшими из предыдущих решений. То, что для функционального решения было завершением процесса - построение функции execute и всего необходимого для ее работы - теперь становится только началом. Существует много других вещей, которые хотелось бы выполнять для подобных приложений:
Все эти операции и другие будут в равной степени являться компонентами класса APPLICATION. Здесь нет более или менее важных программ, чем наша бывшая "главная программа", - процедура execute, ставшая теперь обычным компонентом класса, равная среди других, но не первая. Устраняя понятие вершины, мы подготавливаем систему к эволюции и повторному использованию.
Для завершения рассмотрения класса APPLICATION рассмотрим несколько возможных реализационных решений:
1 до n. Заметьте, эти числа не являются абсолютными свойствами состояний, они связаны с определенным приложением, поэтому в классе STATE нет атрибута "номер состояния". Вместо этого, associated_state, APPLICATION, задает состояние, связанное с заданным номером.transition еще одним атрибутом - двумерным массивом размерности n * m, где m - число возможных пользовательских выборов при выходе из состояния.initial и устанавливается в подпрограмме choose_initial. Для конечных состояний мы используем соглашение, что переход в псевдосостояние 0 означает завершение сессии.APPLICATION использует процедуры создания библиотечных классов ARRAY и ARRAY2. Последний описывает двумерные массивы и построен по образцу ARRAY ; его процедура создания make принимает четыре аргумента, например create a.make (1, 25, 1, 10), а его подпрограммы item и put используют два индекса - a.put (x, 1, 2). Границы двумерного массива a можно узнать, вызвав a.lower1 и так далее.Вот определение класса, использующего эти решения:
indexing
description: "Интерактивные приложения, управляемые панелями"
class APPLICATION creation
make
feature -- Initialization
make (n, m: INTEGER) is
-- Создает приложение с n состояниями и m возможными выборами
do
create transition.make (1, n, 1, m)
create associated_state.make (1, n)
end
feature -- Access
initial: INTEGER
-- Номер начального состояния
feature -- Basic operations
execute is
-- Выполняет сессию пользователя
local
st: STATE; st_number: INTEGER
do
from
st_number := initial
invariant
0<= st_number; st_number <= n
until st_number = 0 loop
st := associated_state.item (st_number)
st.execute
-- Вызов процедуры execute класса STATE.
-- (Комментарии к этой ключевой инструкции даны в тексте.)
st_number := transition.item (st_number, st.choice)
end
end
feature -- Element change
put_state (st: STATE; sn: INTEGER) is
-- Ввод состояния st с индексом sn
require
1 <= sn; sn <= associated_state.upper
do
associated_state.put (st, sn)
end
choose_initial (sn: INTEGER) is
-- Определить состояние с номером sn в качестве начального
require
1 <= sn; sn <= associated_state.upper
do
initial := sn
end
put_transition (source, target, label: INTEGER) is
-- Ввести переход, помеченный label,
-- из состояния с номером source в состояние target
require
1 <= source; source <= associated_state.upper
0 <= target; target <= associated_state.upper
1 <= label; label <= transition.upper2
do
transition.put (source, label, target)
end
feature {NONE} -- Implementation
transition: ARRAY2 [STATE]
associated_state: ARRAY [STATE]
... Другие компоненты ...
invariant
transition.upper1 = associated_state.upper
end -- class APPLICATION
Обратите внимание на простоту и элегантность вызова st.execute. Компонент execute класса STATE является эффективным (полностью реализованным) поскольку описывает известное общее поведение состояний, но его реализация основана на вызове компонентов: read, message, , display, process, отложенных на уровне STATE, эффективизация которых выполняется потомками класса, такими как . Когда мы помещаем вызов st.execute в процедуру execute класса APPLICATION, у нас нет информации о том, какой вид состояния обозначает st, но благодаря st становится связанной с объектом конкретного вида, например , - тогда вызовы read, message и других царственных особ автоматически будут переключаться на нужную версию.
Значение st, полученное из associated_state, представляет полиморфную структуру данных ( polymorphic data structure ), содержащую объекты разных типов, все из которых согласованы (являются потомками) со STATE. Текущий индекс st_number определяет операции состояния.
(рис 2.9) Полиморфный массив состоянийВот как строится интерактивное приложение. Приложение должно быть представлено сущностью, скажем air_reservation, класса APPLICATION. Необходимо создать соответствующий объект:
create air_reservation.make (number_of_states, number_of_possible_choices)
Далее независимо следует определить и создать состояния приложения, как сущности классов-потомков STATE, либо новые, либо уже готовые и взятые из библиотеки повторного использования. Каждое состояние s связывается с номером i в приложении:
air_reservation.put_state (s, i).
Затем одно из состояний выбирается в качестве начального:
air_reservation.choose_initial (i0)
Для установления перехода от состояния sn к состоянию с номером tn, с меткой l используйте вызов:
air_reservation.enter_transition (sn, tn, l)
Это включает и заключительные состояния, для которых по умолчанию tn равно 0. Затем можно запустить приложение:
air_reservation.execute_session.
При эволюциях системы можно в любой момент использовать те же подпрограммы для добавления состояний и переходов.
Конечно же, можно расширить класс APPLICATION, изменяя сам класс или добавляя новых потомков, включив новые функциональные возможности - удаление, моделирование или любые другие.
Этот пример, надеюсь, показал впечатляющую картину той разницы, которая существует между ОО-конструированием ПО и ранними подходами. В частности, он показал преимущества, получаемые при устранении понятия главной программы. Сосредоточившись на понятии абстракции данных, забывая столь долго, пока это еще возможно, о том, что является главной функцией системы, мы получаем структуру, более подготовленную к будущим изменениям и повторному использованию в разнообразных вариантах.
Этот стабилизирующий эффект является одним из характеристических свойств Метода. Он предполагает некоторую дисциплину применения, поскольку такой способ проектирования постоянно наталкивается на сопротивление и естественное желание спросить, "А что же делает система?". Это один из тех навыков, по которому можно отличить ОО-профессионала от людей, которые не проникли в суть Метода, хотя и могут использовать ОО-язык программирования и объектную технику, но за объектным фасадом их систем по-прежнему стоит функциональная архитектура.
Как показано в этой лекции, идентифицировать ключевые абстракции часто удается, анализируя передачу данных и обращая внимание на те понятия, что чаще других используются в коммуникациях между компонентами системы. Часто это является прямым указанием на обращение ситуации - присоединение программ к абстрактным данным.
Заключительный урок этой лекции состоит в том, что следует быть осторожным и не придавать слишком большого значения тому факту, что ОО-системы выведены непосредственно путем моделирования "реального мира". Моделирующая мощь метода и в самом деле впечатляющая, и вполне приятно создавать программную архитектуру, чьи принципиальные компоненты непосредственно отражают абстракции внешней моделируемой системы. Но построить модель реального мира можно разными способами, не все они приводят к хорошей программной системе. Наша первая, goto версия была также близка к реальному миру, как и две другие, - фактически даже ближе, поскольку ее структура была построена по образцу диаграммы переходов системы, в то время как другие версии вводили промежуточные понятия. Но результат с точки зрения
Созданная в конечном итоге ОО-декомпозиция хороша из-за использования абстракций: STATE, APPLICATION, - все они являются ясными, общими, управляемыми, готовыми к изменениям и повторному использованию в широкой области применения. Вы понимаете, что эти абстракции столь же реальны, как и все остальное, но новичку они могут казаться менее естественными, чем концепции, используемые в ранее изучаемых решениях.
При создании хорошего ПО следует учитывать не его близость к реальному миру, а то, насколько выбранные абстракции хороши как для моделирования внешней системы, так и для построения структуры ПО. Фактически в этом суть ОО-анализа, проектирования и реализации - работа, которую для успеха проекта необходимо выполнять хорошо как сегодня, так и завтра. Профессионала от любителя отличает умение находить правильные абстракции.
Варианты примера, обсуждаемого в этой лекции, использовались для иллюстрации ОО-концепций в [M 1983] и [M 1987].
Поскольку этот пример, имея небольшой размер, прекрасно охватывает принципиальные свойства конструирования ОО-ПО, я часто использую его, когда необходимо в течение нескольких часов познакомить аудиторию с возможностью Метода. Показывая на практике (даже для людей с весьма слабой математической подготовкой), как каждый может перейти от классической декомпозиции к ОО-взгляду на вещи, и преимущества, получаемые в результате такого перехода, наш пример является замечательным педагогическим средством. Читателям еще в начале книги предлагалось прочесть эту лекцию, представляющую "рекламный ролик" ОО-метода проектирования.
Для облегчения задачи она написана как можно более независимо. Если эта лекция читается после изучения предыдущих лекций, то вы встретите некоторые повторения, в частности несколько коротких определений уже знакомых концепций.
Наша задача - спроектировать систему, представляющую некоторый общий тип интерактивных систем. В этих системах работа пользователя состоит в выполнении ряда этапов, и каждый этап поддерживается полноэкранной диалоговой панелью.
В целом процесс является простым и хорошо определенным. Сессия работы пользователя проходит через некоторое число состояний. В каждом состоянии отображается некоторая панель, содержащая вопросы к пользователю. Пользователь дает требуемые ответы, проверяемые на согласованность (вопросы повторяются, пока не будет дан корректный ответ); затем ответ обрабатывается некоторым образом, например обновляется база данных. Частью пользовательского ответа является выбор следующего шага, интерпретируемый как переход к следующему состоянию, в котором процесс повторяется.
Примером может служить система резервирования авиабилетов, где состояния представляют такие шаги обработки, как User
Типичная панель для состояния
(рис 2.1) Панель "Запрос Рейса"Сессия начинается в начальном состоянии Initial и заканчивается в заключительном состоянии Final. Мы можем представить всю структуру графом переходов, показывающим возможные состояния и переходы между ними. Ребра графа помечены целыми, соответствующими возможному выбору пользователя следующего шага при завершении состояния. На рис. 2.2 показан граф переходов системы резервирования авиабилетов.
Проблема, возникающая при проектировании и реализации таких приложений, состоит в достижении максимально возможной общности и гибкости. В частности:
(рис 2.2) Граф переходов в системе резервирования авиабилетовДавайте начнем с прямолинейной, без всяких ухищрений программной схемы. В этой версии наша система будет состоять из нескольких блоков по одному на каждое состояние системы: BEnquiry, BReservation, BCancellation и т. д. Типичный блок (выраженный не в ОО-нотации этой книги, а в специально подобранной для этого случая, хотя и удовлетворяющей некоторым синтаксическим соглашениям), выглядит следующим образом:
BEnquiry:
"Отобразить панель Enquiry on flights"
repeat
"Чтение ответов пользователя и выбор C следующего шага"
if "Ошибка в ответе" then
"Вывести соответствующее сообщение" end
until not "ошибки в ответе" end
"Обработка ответа"
case C in
C0: goto Exit,
C1: goto BHelp,
C2: goto BReservation,
...
end
Аналогичный вид имеют блоки для каждого состояния.
Что можно сказать об этой структуре? Ее нетрудно спроектировать, и она делает свое дело. Но с позиций
Наиболее очевидная критика связана с присутствием инструкций goto (реализующих switch языка C или "Вычисляемый Goto" Fortran), из-за чего управляющая структура выглядит подобно "блюду спагетти" и чревата ошибками.
Но goto - это симптом заболевания, а не настоящая причина. Мы взяли поверхностную структуру нашей проблемы -
Этот пример является отрезвляющим напоминанием, когда приходится слышать о преимуществах "моделирования реального мира" или "вывода системы из анализа реальности". В зависимости от того, как вы его описываете, реальный мир может быть простым или выглядеть непонятной кашей. Плохая модель приводит к плохому ПО. Следует рассматривать не то, насколько близко ПО к реальному миру, а насколько хорошо его описание. В конце этой лекции этому вопросу еще будет уделено внимание. |
Чтобы получить не просто систему, а хорошую систему, придется еще поработать.
Повторяя на этом частном примере эволюцию, пройденную программированием в целом, перейдем от низкоуровневого подхода goto к иерархической структуре, создаваемой при проектировании сверху вниз. Это решение принадлежит общему стилю, известному как "структурное", хотя этот термин следует использовать с осторожностью.
ОО-решение является, конечно же, структурным, соответствуя духу "структурного программирования", как оно изначально было введено Э. Дейкстрой.
Первым шагом на пути улучшения нашего решения будет придание центральной роли алгоритму обхода в структуре ПО. Диаграмма переходов - это всего лишь одно из свойств нашей системы, и нет никаких оснований, чтобы она правила над всеми частями системы. Ее отделение от остального алгоритма позволит, по крайней мере, избавиться от goto. Можно ожидать и большей общности, поскольку диаграмма переходов специфична для приложения, такого как резервирование авиабилетов, в то время как алгоритм
Что представляет собой диаграмма переходов? Абстрактно это функция transition, имеющая два аргумента - состояние и выбор пользователя. Функция transition (s, c) возвращает новое состояние, определяемое пользовательским выбором c в состоянии s. Здесь слово "функция" используется в его математическом смысле. На программном уровне можно выбрать реализацию transition либо функцией в программистском смысле, либо структурой данных, например массивом. На данный момент решение можно отложить и рассматривать transition просто как абстрактное понятие.
В добавление к функции transition необходимо спроектировать начальное состояние initial, - точку, в которой начинаются все сессии, и одно или несколько заключительных состояний как булеву функцию is_final. И снова речь идет о функции в математическом смысле, независимо от ее возможной реализации.
Зададим функцию transition в табличной форме со строками, представляющими состояние, и столбцами, отображающими пользовательский выбор:
| Состояние | Выбор | |||
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | |
| 1 (Initial) | -1 | 0 | 5 | 2 |
| 2 (Flights) | -1 | 0 | 1 | 3 |
| 3 (Seats) | 0 | 2 | 4 | |
| 4 ( |
0 | 3 | 5 | |
| 5 ( |
0 | 4 | 1 | |
| 0 (Help) | Return | |||
| -1 (Final) | ||||
Соглашения, используемые в таблице: здесь в состоянии |
Следуя традиционным рекомендациям декомпозиции сверху вниз, выберем "вершину" - главную функцию нашей системы. Это должна быть, очевидно, программа execute_session, описывающая выполнение полной интерактивной сессии.
(рис 2.3) Функциональная декомпозиция сверху-внизНепосредственно ниже (уровень 2) найдем операции, связанные с состояниями: определение начального и конечного состояний, структуру переходов и функцию execute_state, описывающую действия, выполняемые в каждом состоянии. На нижнем уровне 1 найдем операции, определяющие execute_state: отображение панели на экране и другие. Заметьте, что и это решение, также как и ОО-решение, описываемое чуть позже, отражает "реальный мир", в данном случае включающий состояния и элементарные операции данного мира. В этом примере и во многих других не в реальности мира состоит важная разница между ОО-подходом и другими решениями, а в том, как мы моделируем этот мир.
При написании программы execute_session попытаемся сделать наше приложение максимально независимым. (Наша нотация выбрана в соответствии с примером. Цикл repeat until заимствован из Pascal.)
execute_session is
-- Выполняет полную сессию интерактивной системы
local
state, next: INTEGER
do
state := initial
repeat
execute_state (state, >next)
-- Процедура execute_state обновляет значение next
state := transition (state, next)
until is_final (state) end
end
Это типичный алгоритм обхода диаграммы переходов. (Те, кто писал лексический анализатор, узнают образец.) На каждом этапе мы находимся в состоянии state, вначале устанавливаемом в initial ; процесс завершается, когда состояние удовлетворяет is_final. Для состояний, не являющихся заключительными, вызывается execute_state, принимающее текущее состояние и возвращающее в аргументе next выбор перехода, сделанный пользователем. Функция transition определяет следующее состояние.
Техника, используемая в процедуре execute_state, изменяющая значение одного из своих аргументов, никогда не подходит для хорошего ОО-проекта, но здесь она вполне приемлема. Для того чтобы сделать ее явной, используется "флажок" для " out " аргумента - next со стрелкой.
Для завершения проекта следует определить процедуру execute_state, описывающую действия, выполняемые в каждом состоянии. Ее тело реализует содержимое блока начальной goto -версии.
execute_state (in s: INTEGER; out c: INTEGER) is
-- Выполнить действия, связанные с состоянием s,
-- возвращая в c выбор состояния, сделанный пользователем
local
a: ANSWER; ok: BOOLEAN
do
repeat
display (s)
read (s, a)
ok := correct (s, a)
if not ok then message (s, a) end
until ok end
process (s, a)
c := next_choice (a)
end
Здесь вызываются программы уровня 1 со следующими ролями:
display (s) выводит на экран панель, связанную с состоянием s ;read (s, a) читает в a ответы пользователя, введенные в окнах панели состояния s ;correct (s, a) возвращает true, если и только если a является приемлемым ответом; если да, то process (s, a) обрабатывает ответ a, например, обновляя базу данных или отображая некоторую информацию, если нет, то message (s, a) выводит соответствующее сообщение об ошибке.Тип объекта, представляющего ответ пользователя, не будет уточняться. Значение a этого типа глобально представляет ввод пользователя, включающий и выбор следующего шага ( фактически во многом подобен классу, даже если остальная структура не является объектной.)
Для получения работающего приложения необходимо задать реализации программ уровня 1: display, read, , message и process.
Получили ли мы удовлетворительное решение? Не совсем. Оно лучше, чем первая версия, но все еще далеко от заявленных целей повторного использования и расширяемости.
Хотя с первого взгляда кажется, что нам удалось отделить общность, присущую приложениям такого типа, от специфических черт конкретного приложения, в реальности различные модули все еще тесно связаны друг с другом и с выбранным приложением. Главной проблемой остается структура данных, передаваемых в системе. Рассмотрим сигнатуры (типы аргументов и результатов) наших программ:
(рис 2.4) Сигнатура программЗамечание, звучащее подобно жалобе, состоит в том, что роль состояний всеобъемлюща. Текущее состояние s появляется как аргумент во всех программах, спускаясь с вершины execute_session, где оно известно как state. Так что кажущаяся простота и управляемость иерархической структуры, показанной на рис. 2.3, является ложью или, более точно, фасадом. За спиной формальной элегантности функциональной декомпозиции стоит неразбериха передачи данных. Истинная картина должна включать данные.
В основе технологии лежит битва между функциями и данными (объектами) за управление архитектурой системы. В необъектных подходах функции берут вверх над данными, но затем данные начинают мстить.
Месть проявляется в форме саботажа. Атакуя основания архитектуры, данные не пропускают изменения, - пока, подобно правительству не способному руководить своей перестройкой (в оригинале - perestroikа ), система не рухнет под собственной тяжестью.
(рис 2.5) Поток данныхВ этом примере структура рушится из-за необходимости различать состояния. Все программы уровня 1 должны выполнять различные действия, зависящие от состояния s: отображать панель для некоторого состояния, читать и интерпретировать ответы пользователя, определять корректность ответов, - для всех этих задач необходимо знать
inspect
s...
when Initial then
...
when Enquiry_on_flights then
...
....
end
Это приводит к длинной и сложной структуре и, что хуже всего, к неустойчивой системе, - любое добавление состояния потребует изменения всей структуры. Имеет место типичный случай необузданного распределения знаний. Слишком много модулей системы используют одну и ту же информацию - список всех возможных состояний, являющийся предметом изменений.
Если надеяться на получение общего повторно используемого решения, то ситуация еще хуже, чем может показаться. Дело в том, что во всех программах неявно присутствует еще один аргумент - приложение - система резервирования авиабилетов или другая проектируемая система. Так что программы, такие как display, если они действительно носят общий характер, должны знать все состояния всех возможных приложений! Аналогично функция transition должна содержать графы переходов для всех приложений - совершенно нереалистическое предположение.
Проблемы функциональной декомпозиции сверху вниз указывают, что нужно делать, чтобы получить хорошую ОО-версию.
Что пошло не так, в чем проблема? Слишком много передач данных свидетельствует обычно об изъянах в архитектуре ПО. Устранение этих изъянов приводит непосредственно к ОО-проекту, что находит отражение в следующем правиле проектирования:
Закон инверсии Если программы пересылают друг другу много данных, поместите программы в данные. |
Ранее модули строились вокруг операций (таких как execute_session и execute_state ) и данные распределялись между программами со всеми неприятными последствиями, с которыми нам пришлось столкнуться. ОО-проектирование ставит все с головы на ноги, - оно использует наиболее важные типы данных как основу модульности, присоединяя каждую программу к тому типу данных, с которым она наиболее тесно связана. Когда объекты одерживают победу, их бывшие хозяева - функции - становятся вассалами данных.
Закон
Конечно, желательно начинать ОО-проектирование с самого начала, - тогда не придется прибегать к
Пример "состояния" является типичным. Такой тип данных, играющий всеобъемлющую роль в передаче данных между программами, является первым кандидатом на роль модуля в ОО-архитектуре, основанной на классах (абстрактно описанных типах данных).
Понятие состояния было важным в оригинальной постановке проблемы, но затем в функциональной архитектуре эта важность была утеряна, - состояние было представлено обычной переменной, передаваемой из программы в программу, как если бы это было существо низкого STATE должно быть классом, одним из властителей структуры в нашей новой ОО-системе.
В этом классе мы найдем все операции, характеризующие состояние: отображение соответствующего экрана ( display ), анализ ответа пользователя ( read ), проверку ответа ( ), выработку сообщения об ошибке для некорректных ответов ( message ), обработку корректных ответов ( process ). Мы должны также включить сюда execute_state, выражающее последовательность действий, выполняемых всякий раз, когда сессия достигает заданного состояния (поскольку данное имя было бы сверхквалифицированным в классе, называемом STATE, заменим его именем execute ).
Возвращаясь к рисунку, отражающему функциональную декомпозицию, выделим в нем множество программ, принадлежащих классу STATE.
(рис 2.6) Компоненты класса STATEКласс имеет следующую форму:
...class STATE feature
input: ANSWER
choice: INTEGER
execute is do ... end
display is ...
read is ...
correct: BOOLEAN is ...
message is ...
process is ...
end
Компоненты input и являются атрибутами, остальные - подпрограммами (процедурами и функциями). В сравнении со своими двойниками при функциональной декомпозиции подпрограммы потеряли явный аргумент, задающий состояние, хотя он появится другим путем в клиентских вызовах, таких как s.execute.
В предыдущих подходах функция execute (ранее execute_state ) возвращала пользовательский выбор следующего шага. Но такой стиль нарушает правила хорошего проектирования. Предпочтительнее сделать execute командой. Запрос "какой выбор сделал пользователь в последнем состоянии?" доступен благодаря атрибуту . Аналогично, аргумент подпрограмм уровня 1 заменен теперь закрытым атрибутом input. Вот причина скрытия информации: клиентскому коду нет необходимости обращаться к ответам помимо интерфейса, обеспечиваемого компонентами класса.
Класс STATE описывает не частное состояние, а общее понятие состояния. Процедура execute - одна и та же для всех состояний, но другие подпрограммы зависят от состояния.
Наследование и отложенные классы идеально позволяют справиться с этими ситуациями. На уровне описания класса STATE мы знаем атрибуты и процедуру execute во всех деталях. Мы знаем также о существовании программ уровня 1 ( display и др.) и их спецификации, но не их реализации. Эти программы должны быть отложенными, класс STATE, описывающий множество вариантов, а не полностью уточненную абстракцию, сам является отложенным классом. В результате имеем:
indexing
description: "Состояния приложений, управляемых панелями"
deferred class
STATE
feature -- Access
choice: INTEGER
-- Пользовательский выбор следующего шага
input: ANSWER
-- Пользовательские ответы на вопросы в данном состоянии
feature -- Status report
correct: BOOLEAN is
-- Является ли input корректным ответом?
deferred
end
feature -- Basic operations
display is
-- Отображает панель, связанную с текущим состоянием
deferred
end
execute is
-- Выполняет действия, связанные с текущим состоянием,
-- и устанавливает choice - пользовательский выбор
local
ok: BOOLEAN
do
from ok := False until ok loop
display; read; ok := correct
if not ok then message end
end
process
ensure
ok
end
message is
-- Вывод сообщения об ошибке, соответствующей input
require
not correct
deferred
end
read is
-- Получить ответы пользователя input и choice
deferred
end
process is
-- Обработка input
require
correct
deferred
end
end
Для описания специфических состояний следует ввести потомков класса STATE, задающих отложенную реализацию компонент.
(рис 2.7) Иерархия классов StateПример мог бы выглядеть следующим образом:
class ENQUIRY_ON_FLIGHTS inherit
STATE
feature
display is
do
...Специфическая процедура вывода на экран...
end
...И аналогично для read, correct, message и process ...
end
Эта архитектура отделяет зерно от шелухи: элементы, общие для всех состояний, отделяются от элементов, специфичных для конкретного состояния. Общие элементы, такие как процедура execute, сосредоточены в классе STATE и нет необходимости в их повторном объявлении в потомках, таких ENQUIRY_ON_FLIGHTS. Принцип Открыт-Закрыт выполняется: класс STATE закрыт, поскольку он является хорошо определенным, компилируемым модулем, но он также открыт, так как можно добавлять в любое время любых его потомков.
Класс STATE является типичным представителем поведенческих классов ( behavior classes, см. лекцию 14 курса "Основы объектно-ориентированного программирования") - отложенных классов, задающих общее поведение большого числа возможных объектов, реализующих то, что полностью известно на общем уровне ( execute ) в терминах, зависящих от каждого варианта. Наследование и отложенный механизм задают основу представления такого поведения повторно используемых компонентов.
Для завершения проекта следует заняться управлением сессией. При функциональной декомпозиции эту задачу выполняла процедура execute_session - главная программа. Но мы знаем, как сделать это лучшим образом. Как ранее говорилось (см. лекцию 5 курса "Основы объектно-ориентированного программирования") главная функция системы, позиционируемая как верхняя функция в проектировании сверху вниз, - это нечто мифическое. Большие программные системы выполняют множество одинаково важных функций. И здесь основанный на АТД подход является предпочтительным. Вся система в целом рассматривается как множество абстрактных объектов, способных выполнять ряд служб ( services ).
Ранее мы рассмотрели одну ключевую абстракцию - STATE. Какая же абстракция в нашем рассмотрении осталась пропущенной? Ответ очевиден: центральным в нашей системе является понятие APPLICATION, описывающее специфическую интерактивную систему, подобную системе резервирования билетов. Это приводит нас к следующему классу.
(рис 2.8) Компоненты классов State и ApplicationЗаметьте, все не вошедшие в класс STATE программы функциональной декомпозиции стали теперь компонентами класса APPLICATION:
Execute_session - описывает, как выполнять сессию, ее имя теперь разумно упростить и называть просто execute, так как дальнейшую квалификацию обеспечивает имя класса.Initial и is_final - указывают, какие состояния имеют специальный статус в приложении. Конечно же, их разумно включить именно в класс APPLICATION, а не в класс STATE, поскольку они описывают свойства приложения, а не состояния, которое является заключительным или начальным только по отношению к приложению, а не само по себе. При повторном использовании состояние, бывшее заключительным в одном приложении вполне может не быть таковым для другого приложения.Transition - описывает переходы между состояниями приложения.Все компоненты функциональной декомпозиции нашли свое место в ОО-декомпозиции: одни в классе STATE, другие в APPLICATION. Это не должно нас удивлять. Объектная технология, о чем многократно говорится в этой книге, является прежде всего архитектурным механизмом, в первую очередь предназначенным для организации программных элементов в согласованные структуры. Сами элементы, возможно, нижнего уровня, те же самые или похожие на элементы необъектных решений. Объектные механизмы: абстракция данных, скрытие информации, утверждения, наследование,
Системе управления панелями, изучаемой в данной лекции, всегда необходимы: процедура execute_session, теперь просто execute ), чтение ввода пользователя ( read ), обнаружение заключительного состояния ( is_final ). Погружаясь в структуру, можно найти одни и те же элементы, независимо от выбранного подхода к проектированию. Что же меняется? - способ группирования элементов, создающий модульную архитектуру.
Конечно, нет необходимости ограничивать себя элементами, пришедшими из предыдущих решений. То, что для функционального решения было завершением процесса - построение функции execute и всего необходимого для ее работы - теперь становится только началом. Существует много других вещей, которые хотелось бы выполнять для подобных приложений:
Все эти операции и другие будут в равной степени являться компонентами класса APPLICATION. Здесь нет более или менее важных программ, чем наша бывшая "главная программа", - процедура execute, ставшая теперь обычным компонентом класса, равная среди других, но не первая. Устраняя понятие вершины, мы подготавливаем систему к эволюции и повторному использованию.
Для завершения рассмотрения класса APPLICATION рассмотрим несколько возможных реализационных решений:
1 до n. Заметьте, эти числа не являются абсолютными свойствами состояний, они связаны с определенным приложением, поэтому в классе STATE нет атрибута "номер состояния". Вместо этого, associated_state, APPLICATION, задает состояние, связанное с заданным номером.transition еще одним атрибутом - двумерным массивом размерности n * m, где m - число возможных пользовательских выборов при выходе из состояния.initial и устанавливается в подпрограмме choose_initial. Для конечных состояний мы используем соглашение, что переход в псевдосостояние 0 означает завершение сессии.APPLICATION использует процедуры создания библиотечных классов ARRAY и ARRAY2. Последний описывает двумерные массивы и построен по образцу ARRAY ; его процедура создания make принимает четыре аргумента, например create a.make (1, 25, 1, 10), а его подпрограммы item и put используют два индекса - a.put (x, 1, 2). Границы двумерного массива a можно узнать, вызвав a.lower1 и так далее.Вот определение класса, использующего эти решения:
indexing
description: "Интерактивные приложения, управляемые панелями"
class APPLICATION creation
make
feature -- Initialization
make (n, m: INTEGER) is
-- Создает приложение с n состояниями и m возможными выборами
do
create transition.make (1, n, 1, m)
create associated_state.make (1, n)
end
feature -- Access
initial: INTEGER
-- Номер начального состояния
feature -- Basic operations
execute is
-- Выполняет сессию пользователя
local
st: STATE; st_number: INTEGER
do
from
st_number := initial
invariant
0<= st_number; st_number <= n
until st_number = 0 loop
st := associated_state.item (st_number)
st.execute
-- Вызов процедуры execute класса STATE.
-- (Комментарии к этой ключевой инструкции даны в тексте.)
st_number := transition.item (st_number, st.choice)
end
end
feature -- Element change
put_state (st: STATE; sn: INTEGER) is
-- Ввод состояния st с индексом sn
require
1 <= sn; sn <= associated_state.upper
do
associated_state.put (st, sn)
end
choose_initial (sn: INTEGER) is
-- Определить состояние с номером sn в качестве начального
require
1 <= sn; sn <= associated_state.upper
do
initial := sn
end
put_transition (source, target, label: INTEGER) is
-- Ввести переход, помеченный label,
-- из состояния с номером source в состояние target
require
1 <= source; source <= associated_state.upper
0 <= target; target <= associated_state.upper
1 <= label; label <= transition.upper2
do
transition.put (source, label, target)
end
feature {NONE} -- Implementation
transition: ARRAY2 [STATE]
associated_state: ARRAY [STATE]
... Другие компоненты ...
invariant
transition.upper1 = associated_state.upper
end -- class APPLICATION
Обратите внимание на простоту и элегантность вызова st.execute. Компонент execute класса STATE является эффективным (полностью реализованным) поскольку описывает известное общее поведение состояний, но его реализация основана на вызове компонентов: read, message, , display, process, отложенных на уровне STATE, эффективизация которых выполняется потомками класса, такими как . Когда мы помещаем вызов st.execute в процедуру execute класса APPLICATION, у нас нет информации о том, какой вид состояния обозначает st, но благодаря st становится связанной с объектом конкретного вида, например , - тогда вызовы read, message и других царственных особ автоматически будут переключаться на нужную версию.
Значение st, полученное из associated_state, представляет полиморфную структуру данных ( polymorphic data structure ), содержащую объекты разных типов, все из которых согласованы (являются потомками) со STATE. Текущий индекс st_number определяет операции состояния.
(рис 2.9) Полиморфный массив состоянийВот как строится интерактивное приложение. Приложение должно быть представлено сущностью, скажем air_reservation, класса APPLICATION. Необходимо создать соответствующий объект:
create air_reservation.make (number_of_states, number_of_possible_choices)
Далее независимо следует определить и создать состояния приложения, как сущности классов-потомков STATE, либо новые, либо уже готовые и взятые из библиотеки повторного использования. Каждое состояние s связывается с номером i в приложении:
air_reservation.put_state (s, i).
Затем одно из состояний выбирается в качестве начального:
air_reservation.choose_initial (i0)
Для установления перехода от состояния sn к состоянию с номером tn, с меткой l используйте вызов:
air_reservation.enter_transition (sn, tn, l)
Это включает и заключительные состояния, для которых по умолчанию tn равно 0. Затем можно запустить приложение:
air_reservation.execute_session.
При эволюциях системы можно в любой момент использовать те же подпрограммы для добавления состояний и переходов.
Конечно же, можно расширить класс APPLICATION, изменяя сам класс или добавляя новых потомков, включив новые функциональные возможности - удаление, моделирование или любые другие.
Этот пример, надеюсь, показал впечатляющую картину той разницы, которая существует между ОО-конструированием ПО и ранними подходами. В частности, он показал преимущества, получаемые при устранении понятия главной программы. Сосредоточившись на понятии абстракции данных, забывая столь долго, пока это еще возможно, о том, что является главной функцией системы, мы получаем структуру, более подготовленную к будущим изменениям и повторному использованию в разнообразных вариантах.
Этот стабилизирующий эффект является одним из характеристических свойств Метода. Он предполагает некоторую дисциплину применения, поскольку такой способ проектирования постоянно наталкивается на сопротивление и естественное желание спросить, "А что же делает система?". Это один из тех навыков, по которому можно отличить ОО-профессионала от людей, которые не проникли в суть Метода, хотя и могут использовать ОО-язык программирования и объектную технику, но за объектным фасадом их систем по-прежнему стоит функциональная архитектура.
Как показано в этой лекции, идентифицировать ключевые абстракции часто удается, анализируя передачу данных и обращая внимание на те понятия, что чаще других используются в коммуникациях между компонентами системы. Часто это является прямым указанием на обращение ситуации - присоединение программ к абстрактным данным.
Заключительный урок этой лекции состоит в том, что следует быть осторожным и не придавать слишком большого значения тому факту, что ОО-системы выведены непосредственно путем моделирования "реального мира". Моделирующая мощь метода и в самом деле впечатляющая, и вполне приятно создавать программную архитектуру, чьи принципиальные компоненты непосредственно отражают абстракции внешней моделируемой системы. Но построить модель реального мира можно разными способами, не все они приводят к хорошей программной системе. Наша первая, goto версия была также близка к реальному миру, как и две другие, - фактически даже ближе, поскольку ее структура была построена по образцу диаграммы переходов системы, в то время как другие версии вводили промежуточные понятия. Но результат с точки зрения
Созданная в конечном итоге ОО-декомпозиция хороша из-за использования абстракций: STATE, APPLICATION, - все они являются ясными, общими, управляемыми, готовыми к изменениям и повторному использованию в широкой области применения. Вы понимаете, что эти абстракции столь же реальны, как и все остальное, но новичку они могут казаться менее естественными, чем концепции, используемые в ранее изучаемых решениях.
При создании хорошего ПО следует учитывать не его близость к реальному миру, а то, насколько выбранные абстракции хороши как для моделирования внешней системы, так и для построения структуры ПО. Фактически в этом суть ОО-анализа, проектирования и реализации - работа, которую для успеха проекта необходимо выполнять хорошо как сегодня, так и завтра. Профессионала от любителя отличает умение находить правильные абстракции.
Варианты примера, обсуждаемого в этой лекции, использовались для иллюстрации ОО-концепций в [M 1983] и [M 1987].
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.