Закон инверсии

Объектно-ориентированная архитектура

Проблемы функциональной декомпозиции сверху вниз указывают, что нужно делать, чтобы получить хорошую ОО-версию.

Что пошло не так, в чем проблема? Слишком много передач данных свидетельствует обычно об изъянах в архитектуре ПО. Устранение этих изъянов приводит непосредственно к ОО-проекту, что находит отражение в следующем правиле проектирования:

Закон инверсии Если программы пересылают друг другу много данных, поместите программы в данные.

Ранее модули строились вокруг операций (таких как 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 ) в терминах, зависящих от каждого варианта. Наследование и отложенный механизм задают основу представления такого поведения повторно используемых компонентов.