Закон инверсии
Объектно-ориентированная архитектура
Проблемы функциональной декомпозиции сверху вниз указывают, что нужно делать, чтобы получить хорошую ОО-версию.
Что пошло не так, в чем проблема? Слишком много передач данных свидетельствует обычно об изъянах в архитектуре ПО. Устранение этих изъянов приводит непосредственно к ОО-проекту, что находит отражение в следующем правиле проектирования:
Закон инверсии Если программы пересылают друг другу много данных, поместите программы в данные. |
Ранее модули строились вокруг операций (таких как execute_session и execute_state ) и данные распределялись между программами со всеми неприятными последствиями, с которыми нам пришлось столкнуться. ОО-проектирование ставит все с головы на ноги, - оно использует наиболее важные типы данных как основу модульности, присоединяя каждую программу к тому типу данных, с которым она наиболее тесно связана. Когда объекты одерживают победу, их бывшие хозяева - функции - становятся вассалами данных.
Закон инверсии является ключом получения ОО-проекта из классической функциональной (процедурной) декомпозиции, как это делается в данной лекции. Подобная необходимость возникает в случаях реверс-инженерии, когда существующая необъектная система преобразуется в объектную для обеспечения ее дальнейшей эволюции и сопровождения. К этому приему прибегают в командах, возможно, не столь хорошо знакомых с ОО-проектированием и предпочитающих начинать с привычной функциональной декомпозиции.
Конечно, желательно начинать ОО-проектирование с самого начала, - тогда не придется прибегать к инверсии. Но закон инверсииполезен не только в случае реверс-инженерии или для новичков-разработчиков. И в объектной разработке проекта на фоне объектного ландшафта могут встречать области функциональной декомпозиции. Анализ передачи данных является хорошим способом обнаружения и корректировки подобных дефектов. Если вы обнаружили в структуре, разрабатываемой как объектной, образцы передачи данных, подобные состояниям в данном примере, то это должно привлечь ваше внимание и привести в большинстве случаев к осознанию абстракции данных, не нашедшей еще должного места в проекте.
Состояние как класс
Пример "состояния" является типичным. Такой тип данных, играющий всеобъемлющую роль в передаче данных между программами, является первым кандидатом на роль модуля в ОО-архитектуре, основанной на классах (абстрактно описанных типах данных).
Понятие состояния было важным в оригинальной постановке проблемы, но затем в функциональной архитектуре эта важность была утеряна, - состояние было представлено обычной переменной, передаваемой из программы в программу, как если бы это было существо низкого ранга. Мы уже видели, как оно отомстило за себя. Теперь мы готовы предоставить ему заслуженный статус. STATE должно быть классом, одним из властителей структуры в нашей новой ОО-системе.
В этом классе мы найдем все операции, характеризующие состояние: отображение соответствующего экрана ( display ), анализ ответа пользователя ( read ), проверку ответа ( correct ), выработку сообщения об ошибке для некорректных ответов (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 и choice являются атрибутами, остальные - подпрограммами (процедурами и функциями). В сравнении со своими двойниками при функциональной декомпозиции подпрограммы потеряли явный аргумент, задающий состояние, хотя он появится другим путем в клиентских вызовах, таких как s.execute.
В предыдущих подходах функция execute (ранее execute_state ) возвращала пользовательский выбор следующего шага. Но такой стиль нарушает правила хорошего проектирования. Предпочтительнее сделать execute командой. Запрос "какой выбор сделал пользователь в последнем состоянии?" доступен благодаря атрибуту choice. Аналогично, аргумент ANSWERподпрограмм уровня 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 ) в терминах, зависящих от каждого варианта. Наследование и отложенный механизм задают основу представления такого поведения повторно используемых компонентов.