Объектно-ориентированные технологии проектирования прикладных программных систем

       

Абстрактные классы


Рассмотрим пример, представленный на рисунке 2.19. В нем рассмотрена операция подсчет выплат для различных категорий служащих фирмы: временных служащих с почасовой оплатой труда, постоянных служащих с понедельной оплатой труда и руководящих работников фирмы с помесячной оплатой.

Каждая из категорий служащих представлена своим подклассом класса служащий, от которого они наследуют атрибут годовой_доход и операцию подсчет_выплат. Но подсчет выплат для каждой категории служащих производится по-своему, с учетом значений их собственных (неунаследованных) атрибутов; поэтому в каждом из подклассов операция подсчет_выплат переопределяется. Следовательно, в суперклассе операция подсчет_выплат может быть определена произвольным образом, так как она никогда не будет выполняться. В то же время сигнатуры всех операций подсчет_выплат в суперклассе и в подклассах должны быть одинаковыми (иначе это будут разные операции). Из сказанного следует, что в суперклассе можно задать только сигнатуру операции подсчет_выплат, это обеспечит одинаковые сигнатуры этой операции во всех подклассах. Методы, реализующие операцию подсчет_выплат, достаточно определить только в подклассах класса служащий. Суперкласс, в котором заданы только атрибуты и сигнатуры операций, но не определены методы, реализующие его операции, называется абстрактным классом. Методы, реализующие операции абстрактного класса, определяются в его подклассах, которые называются конкретными классами.

Рис. 2.19. Абстрактный класс

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

| |

Comments:

Copyright ©



Активности и действия


Диаграмма состояний была бы не очень полезной, если бы она содержала только переходы (безусловные и условные), соответствующие генерируемым во время работы системы событиям. Являясь описанием поведения объекта, диаграмма состояний должна описывать, что делает объект в ответ на переход в некоторое состояние или на возникновение некоторого события. Для этого в диаграмму состояний включаются описания активностей и действий.

Активностью называется операция, связанная с каким-либо состоянием объекта (она выполняется, когда объект попадает в указанное состояние); выполнение активности требует определенного времени. Примеры активностей: выдача картинки на экран телевизора, телефонный звонок, считывание порции файла в буфер и т.п.; иногда активностью бывает просто приостановка выполнения программы (пауза), чтобы обеспечить необходимое время пребывания в соответствующем состоянии (это бывает особенно важно для параллельной асинхронной программы). Активность связана с состоянием, поэтому на диаграмме состояний она обозначается через "do: имя_активности" в узле, описывающем соответствующее состояние (см. рисунок 2.48).

Рис. 2.48. Указание активностей и действий на диаграмме состояний

Действием называется мгновенная операция, связанная с событием: при возникновении события происходит не только переход объекта в новое состояние, но и выполняется действие, связанное с этим событием. Например, в телефонной сети событие повесил трубку сопровождается действием разъединить связь (см. рисунок 2.49). Действие указывается на диаграмме состояний вслед за событием, которому оно соответствует, и его имя (или описание) отделяется от имени события косой чертой ("/") (см. рисунок 2.48).

Рис. 2.49. Диаграмма состояний телефонной линии, на которой указаны активности и действия

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

| |

Comments:

Copyright ©



Чем неудобны не объектно-ориентированные системы программирования




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

Целесообразность использования объектно-ориентированных систем программирования связана со следующими обстоятельствами:

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

Тем не менее даже в случае разработки прикладных программных систем в не объектно-ориентированных системах программирования использование методологии объектно-ориентированного проектирования этих систем, описанной в данном курсе, существенно упрощает их реализацию и последующее сопровождение.

|

Comments:

Copyright ©



Дальнейшее исследование и усовершенствование модели


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

Здесь мы рассмотрим приемы бескомпьютерного поиска и исправления ошибок в объектной модели. В их основе лежат внешние признаки, по которым можно находить ошибки в модели; эти признаки могут быть объединены в следующие группы.

Признаки пропущенного объекта (класса):

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

Признаки ненужного (лишнего) класса:

нехватка атрибутов, операций и зависимостей у некоторого класса; для исправления ошибки необходимо подумать, не следует ли исключить такой класс.

Признаки пропущенных зависимостей:

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

Признаки ненужных (лишних) зависимостей:

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

Признаки неправильного размещения зависимостей:

имена ролей слишком широки или слишком узки для их классов; для исправления ошибки необходимо переместить зависимость вверх или вниз по иерархии классов.

Признаки неправильного размещения атрибутов:

нет необходимости доступа к объекту по значениям одного из его атрибутов; для исправления ошибки необходимо рассмотреть нужно ли ввести квалифицированную зависимость.

Примеры практического применения описанных признаков см. в п.

| |

Comments:

Copyright ©



Дальнейшее усовершенствование модели


Карточка выступает в двух сущностях: как регистрационная единица в банке (сберкнижка), обеспечивающая клиенту доступ к его счетам, и как структура данных, с которой работает ATM. Поэтому удобно расщепить класс карточка на два класса: регистрация_карточки и карточка; первый из этих классов обеспечивает клиенту доступ к его счетам в банке, а второй определяет структуру данных, с которой работает ATM.

Класс проводка удобно представить как агрегацию классов изменение, так как проводка - это согласованная последовательность внесения изменений в счета и другие банковские документы; при работе с банковскими документами рассматривается три вида изменений: снятие, помещение и запрос.

Класс банк естественно объединить с классом компьютер_банка, а класс консорциум - с классом центральный_компьютер.

Рис. 2.40. Окончательный вид объектной диаграммы для банковской сети

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

| |

Comments:

Copyright ©



Диаграммы потоков данных


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

ДПД содержит процессы, которые преобразуют данные, потоки данных, которые переносят данные, активные объекты, которые производят и потребляют данные, и хранилища данных, которые пассивно хранят данные.

Процессы

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

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

Рис. 2.60. Примеры процессов

Процессы реализуются в виде методов (или их частей) и соответствуют операциям конкретных классов.

Потоки данных

Поток данных соединяет выход объекта (или процесса) со входом другого объекта (или процесса). Он представляет промежуточные данные вычислений. Поток данных изображается в виде стрелки между производителем и потребителем данных, помеченной именами соответствующих данных; примеры стрелок, изображающих потоки данных, представлены на рисунке 2.61. На первом примере изображено копирование данных при передаче одних и тех же значений двум объектам, на втором - расщепление структуры на ее поля при передаче разных полей структуры разным объектам.


Рис. 2.61. Потоки данных Активные объекты Активным называется объект, который обеспечивает движение данных, поставляя или потребляя их. Активные объекты обычно бывают присоединены к входам и выходам ДПД. Примеры активных объектов показаны на рисунке 2.62. На ДПД активные объекты обозначаются прямоугольниками.


Рис. 2.62. Активные объекты (экторы) Хранилища данных Хранилище данных - это пассивный объект в составе ДПД, в котором данные сохраняются для последующего доступа. Хранилище данных допускает доступ к хранимым в нем данным в порядке, отличном от того, в котором они были туда помещены. Агрегатные хранилища данных, как например, списки и таблицы, обеспечивают доступ к данным в порядке их поступления, либо по ключам. Примеры хранилищ данных приведены на рисунке 2.63.


Рис. 2.63. Хранилища данных Потоки управления ДПД показывает все пути вычисления значений, но не показывает в каком порядке значения вычисляются. Решения о порядке вычислений связаны с управлением программой, которое отражается в динамической модели. Эти решения, вырабатываемые специальными функциями, или предикатами, определяют, будет ли выполнен тот или иной процесс, но при этом не передают процессу никаких данных, так что их включение в функциональную модель необязательно. Тем не менее иногда бывает полезно включать указанные предикаты в функциональную модель, чтобы в ней были отражены условия выполнения соответствующего процесса. Функция, принимающая решение о запуске процесса, будучи включенной в ДПД, порождает в ДПД поток управления (он изображается пунктирной стрелкой).


Рис. 2.64. Поток управления

На рисунке 2.64 изображен пример потока управления: клиент, желающий снять часть своих денег со счета в банке, вводит в ATM пароль и требуемую сумму, однако фактическое снятие и выдача денег происходит только в том случае, когда введенный пароль совпадает с его образцом. Несмотря на то, что потоки управления иногда оказываются весьма полезными, следует иметь в виду, что включение их в ДПД приводит к дублированию информации, входящей в динамическую модель. | |


Comments:

Copyright ©


Динамическая модель банковской сети


В качестве примера применения рассмотренных принципов построения динамической модели построим динамическую модель банковской сети. Начнем с составления и изучения сценариев.

ATM просит клиента вставить карточку клиент вставляет карточку ATM принимает карточку и читает ее номер ATM просит ввести пароль

клиент вводит "1234." ATM передает номер и пароль в консорциум, консорциум проверяет номер и пароль, определяет код банка - "39" и сообщает ATM, что запрос принят

ATM просит клиента (с помощью меню на экране) выбрать вид проводки (снятие, вклад, перевод, запрос) клиент выбирает снятие ATM спрашивает клиента какова требуемая сумма клиент вводит $100 ATM убеждается, что введенная сумма в пределах лимита и просит консорциум выполнить проводку, консорциум передает запрос в банк, банк выполняет проводку и возвращает новое значение баланса счета ATM выдает сумму и просит клиента взять ее клиент берет деньги ATM спрашивает не нужно ли клиенту чего еще клиент вводит нет

ATM печатает счет, выдает карточку и просит клиента взять их клиент берет счет и карточку ATM просит (другого) клиента ввести карточку

Рис. 2.53. Нормальный сценарий для банковской сети

На рисунке 2.53 представлен нормальный сценарий обслуживания клиента в банковской сети; один из возможных сценариев, содержащих исключительные ситуации, показан на рисунке 2.54.

ATM просит клиента вставить карточку клиент вставляет карточку ATM принимает карточку и читает ее номер ATM просит ввести пароль клиент вводит "9999." ATM передает номер и пароль в консорциум; консорциум, проконсультировавшись с соответствующим банком, отвергает запрос ATM сообщает, что пароль введен неверно

клиент вводит "1234." ATM передает номер и пароль в консорциум, консорциум проверяет номер и пароль, определяет код банка - "39" и сообщает ATM, что запрос принят ATM просит выбрать вид проводки клиент выбирает снятие ATM спрашивает какова требуемая сумма клиент (раздумав брать деньги) набирает отмену ATM выдает карточку и просит клиента взять ее клиент берет карточку ATM просит (другого) клиента вставить карточку


Рис. 2.54. Сценарий для банковской сети, содержащий исключительные ситуации

Для каждого сценария можно составить соответствующую трассу событий (рисунок 2.55). Для этого выделяем в сценарии имена событий (событиями являются все сигналы, вводы данных, решения, прерывания, переходы и действия, выполняемые клиентом или внешними устройствами), указывая для каждого события объект, порождающий это событие (активный объект). Имея трассы событий, можно построить диаграммы состояний объектов проектируемой системы. Банковская сеть есть агрегация определенного числа параллельно и независимо работающих объектов четырех классов: консорциум, банк, ATM (банкомат) и клиент; поэтому состояние банковской сети определяется как кортеж состояний составляющих ее объектов: 1 объекта класса консорциум, b объектов класса банк, a объектов класса ATM (банкомат) и c объектов класса клиент (a, b, c - количество ATM, банков и клиентов сети соответственно). Классификация объектов, используемая при объектно-ориентированном подходе, позволяет вместо a+b+1 диаграмм состояний построить всего три (диаграммы состояний клиентов строить не нужно, так как их текущее состояние ясно и так).


Рис. 2.55. Трасса событий в банковской сети


Рис. 2.56. Привязка событий к объектам банковской сети

Построение диаграмм состояний начинается с привязки событий к объектам банковской сети (см. рисунок 2.56), являющимся источниками этих событий. Сначала рассматриваются нормальные события, потом исключительные события. Построение диаграммы состояний объекта (класса) может считаться законченным, когда диаграмма охватывает все рассматриваемые сценарии. Диаграммы состояний объектов классов ATM (банкомат), консорциум и банк представлены на рисунках 2.57, 2.58 и 2.59 соответственно.


Рис. 2.57. Диаграмма состояний объектов класса ATM (банкомат)


Рис. 2.58. Диаграмма состояний объектов класса консорциум


Рис. 2.59. Диаграмма состояний объектов класса банк

| |


Comments:

Copyright ©


Функциональная модель банковской сети


Функциональная модель показывает как вычисляются значения в системе и как они зависят одно от другого. Для конструирования функциональной модели необходимо выполнить следующее:

определить входные и выходные значения; построить ДПД, показывающие функциональные зависимости; описать функции; описать ограничения; сформулировать критерии оптимизации; уточнить набор операций в объектной модели.

Выполним все эти шаги, чтобы построить функциональную модель банковской сети (ATM).

Определение входных и выходных значений

Начнем с определения входных и выходных значений. Эти значения являются параметрами событий между системой и окружающим ее миром. Входные и выходные значения банковской сети показаны на рисунке 2.66 (пунктирная линия показывает границу системы).

Рис. 2.66. Входные и выходные значения банковской сети

Поскольку все взаимодействия между внешним миром и системой проходят через ATM (или кассовый терминал, который здесь не рассматривается), все входные и выходные значения являются параметрами событий, связанных с объектом ATM (банкомат).

Построение ДПД

ДПД обычно строится по уровням. На верхнем уровне, как правило, показывают один единственный процесс, или, как в нашем примере (рисунок 2.67), процесс ввода, основной процесс вычисления требуемых значений и процесс вывода. Входные и выходные данные на этой диаграмме поставляются и потребляются внешними объектами (клиент, карточка).

Рис. 2.67. Процессы верхнего уровня в системе обслуживания банковской сети

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

Рис. 2.68. ДПД процесса выполнить проводку в системе обслуживания банковской сети


ДПД показывают только зависимости между операциями; они не содержат информации о последовательности выполнения операций (операции, показанные на ДПД могут быть не всегда выполняемыми или исключающими одна другую). Например, пароль должен быть подтвержден до внесения изменений в счет; если он указан неверно, изменения в счет не вносятся. Последовательность выполнения операций указывается в динамической, а не в функциональной модели. Некоторые значения данных влияют на выбор последовательности вычислений в динамической модели; тем не менее эти значения не влияют непосредственно на выходные значения ДПД, так как ДПД показывает все возможные пути вычислений. Однако может оказаться полезным поместить функции, осуществляющие выбор последовательности вычислений, и в функциональную модель, так как эти функции могут выражать сложные зависимости от входных данных. Такие функции показаны на ДПД, но их выходными значениями являются управляющие сигналы, показанные пунктирными линиями. Примером такой функции является функция проверить пароль. Описание функций После того как ДПД составлена, необходимо составить описание каждой функции из ДПД. Пример описания функции приведен на . Мы не приводим других примеров описания функций банковской сети в виду их тривиальности. Описание ограничений Ограничения - это функциональные зависимости между объектами, не сводящиеся к непосредственным зависимостям между выходами и входами. Ограничения могут накладываться одновременно на два объекта, на различные объекты одного класса в различные моменты времени (инвариант), или на объекты разных классов в различные моменты времени (хотя в последнем случае это, как правило, связи между входами и выходами). Ограничения, которым должны удовлетворять входные значения функции, называются ее предусловиями, а ограничения, которым удовлетворяют выходные значения функции, - ее постусловиями. В банковской сети можно ввести следующие ограничения:

"счет не может иметь отрицательного баланса"; "отрицательный баланс кредитуемого счета не может быть больше лимита кредитования для этого счета".



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

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

Операции вводятся на различных стадиях разработки модели проектируемой системы (см. выше):

при разработке объектной модели; при определении событий; при определении действий и активностей; при определении функций.

На заключительном этапе разработки модели проектируемой системы все введенные операции вводятся в ее объектную модель. К перечисленным операциям добавляются операции, связанные с внешним поведением объектов рассматриваемых классов: эти операции определяются природой объектов, а не свойствами разрабатываемой системы. Такие операции расширяют определение объекта, выводя его за рамки непосредственных требований со стороны конкретной системы. Примерами таких операций для банковской сети являются: закрыть счет, создать сберегательный счет, создать банковскую карточку и т.п. С точки зрения системы эти операции не имеют смысла, но они отражают реальные свойства классов счет и карточка и могут оказаться полезными при использовании этих классов в других системах. В заключение следует исследовать объектную модель на предмет упрощения операций ее классов. Пример банковской сети недостаточно сложен, чтобы продемонстрировать это.

| |


Comments:

Copyright ©


Интерфейсы и окружения


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

Интерфейс объекта определяется интерфейсом соответствующего класса и задается списком сигнатур его открытых операций (методов). Интерфейс подсистемы определяется через итерфейсы составляющих ее объектов и подсистем следующим образом: операция может быть включена в интерфейс подсистемы, если в составе этой подсистемы имеется объект (подсистема), интефейс которого содержит эту операцию. Интерфейсы описываются на языке описания интерфейсов IDL (Interface Definition Language).

Все возможности по обработке данных внутри подсистемы (т.е. в каждом компоненте, входящем в ее состав) определяются набором интерфейсов ее компонентов, который определяет внутреннее окружение подсистемы.

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

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

Рис. 2.43. Объектная диаграмма банковской сети после выделения подсистемы банк

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

| |

Comments:

Copyright ©



Объектно-ориентированное программирование на Фортране


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

Реализация классов с помощью массивов языка Фортран. В Фортране определен всего один структурный тип данных - массив, так что структуры (записи) тоже следует моделировать. Класс представляется как неявный набор массивов, по одному для каждого атрибута класса. Массивы должны иметь одинаковый размер, который должен быть достаточным для включения всех объектов этого класса, которые будут существовать во время выполнения программы, так как Фортран не поддерживает динамического распределения памяти. Значения индекса массивов представляют уникальный идентификатор объекта внутри соответствующего класса. При этом идентификаторы объектов разных классов будут иметь одинаковые значения, так что программист должен сам следить за тем, какому классу принадлежит объект (впрочем реализация механизма наследования, описываемая ниже, может несколько облегчить это). Все массивы одного и того же класса можно объединить в общий блок. Например, память для тысячи окон из рассматриваемого в этом разделе примера может быть организована следующим образом:

COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) INTEGER NWINDOW

Программист должен организовать счетчик порожденных объектов данного класса (NWINDOW), и присваивать его значения уникальным идентификаторам вновь порождаемых объектов.

В Фортране нет средств определения новых типов данных, поэтому нет возможности определить тип Length: данные этого типа должны иметь один из определенных в Фортране типов (INTEGER, REAL, COMPLEX, LOGICAL и CHARACTER).

В Стандарте Фортрана ограничена длина идентификаторов, однако большая часть компиляторов поддерживают идентификаторы в 32 и более символов.
В примерах фрапгментов фортранных программ предполагается, что допустимы достаточно длинные идентификаторы, которые могут содержать символ подчеркивания ("_"). Если компилятор не поддерживает длинных имен, все идентификаторы из примеров следует заменить более короткими, что, естественно, ухудшит читаемость программ. Таким образом, в объектной программе на Фортране каждый объект некоторого класса может быть представлен индексом атрибутных массивов этого класса, причем указанный индекс определяет доступ к атрибутам этого объекта:

INTEGER AWINDOW REAL X1 X1 = XMIN(AWINDOW)

При передаче параметров методам объект может передаваться как соответствующий ему индекс атрибутных массивов соответствующего класса:

SOUBROUTINE WINDOW__ADD_TO_SELECTIONS (SELF, SHAPE) INTEGER SELF, SHAPE

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

FUNCTION CIRCLE__PICK(X0, Y0, RADIUS, X, Y) LOGICAL CIRCLE__PICK REAL X0, Y0, RADIUS, X, Y

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

FUNCTION CREATE_WINDOW(X1, Y1, WIDTH, HEIGHT) COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) INTEGER NWINDOW INTEGER CREATE_WINDOW REAL X1, Y1, X2, Y2 NWINDOW = NWINDOW + 1 XMIN(NWINDOW) = X1 YMIN(NWINDOW) = Y1 XMAX NWINDOW) = X1 + WIDTH YMAX NWINDOW) = Y1 + HEIGHT CREATE_WINDOW = NWINDOW RETURN END

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


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

COMMON/SHAPE/XMIN,YMIN,XMAX,YMAX,WINDOW,RADIUS,NSHAPE REAL XMIN(1000),YMIN(1000),XMAX(1000),YMAX(1000),WINDOW(1000) REAL RADIUS(1000) INTEGER NSHAPE

Второй способ более экономен. Он состоит в представлении класса как набора подклассов, каждый из которых реализован как самостоятельный класс со своим набором массивов и индексами объектов. Каждый класс представляется парой целых массивов: один из этих массивов содержит код подкласса, другой - индексы объектов в соответствующем массиве подкласса. В следующем примере определен класс ITEM, который имеет подклассы SHAPE (содержит не более 1000 объектов) и GROUP (содержит не более 100 объектов). Общий блок CLASSES определяет целочисленный код для каждого класса.

COMMON/ITEM/ITEM_CLASS,ITEM_ID,NITEM INTEGER ITEM_CLASS(1100),ITEM_ID(1100) INTEGER NITEM/0/ COMMON/CLASSES/GROUP,BOX,CIRCLE INTEGER GROUP/1/,BOX/2/,CIRCLE/3/

Когда создается новый объект, значение индекса должно выбираться как из суперкласса, так и из соответствующего подкласса. Например, следующий код создает новый круг (CIRCLE):

FUNCTION CREATE_CIRCLE(X0,Y0,RADIUS0) COMMON/WINDOW/XMIN,YMIN,XMAX,YMAX,WINDOW сюда следует поместить описания общих блоков ITEM, SHAPE и CLASSES INTEGER CREATE_CIRCLE NSHAPE = NSHAPE + 1 X(NSHAPE) = X0 Y(NSHAPE) = Y0 RADIUS(NSHAPE) = RADIUS0 NITEM = NITEM + 1 ITEM_CLASS(NITEM) = CIRCLE ITEM_ID(NITEM) = NSHAPE CREATE_CIRCLE = NITEM RETURN END

Резолюция методов. Вызовы методов для объектов, класс которых известен во время компиляции, осуществляются как непосредственные вызовы соответствующих фортранных подпрограмм. Остальные объекты должны содержать номер класса (см.выше общий блок CLASSES). Для каждой операции можно составить управляющую подпрограмму, которой в качестве параметров передаются номер класса и индекс объекта. Управляющая подпрограмма содержит оператор перехода по вычислению, который позволяет вызывать требуемый метод:

FUNCTION PICK(CLASS,ID,PX,PY) LOGICAL PICK LOGICAL GROUP_PICK,BOX_PICK,CIRCLE_PICK INTEGER CLASS,ID GOTO(100,200,300) CLASS PICK = .FALSE. RETURN 100 PICK = GROUP_PICK(ID,PX,PY) RETURN 200 PICK = BOX_PICK(ID,PX,PY) RETURN 300 PICK = CIRCLE_PICK(ID,PX,PY) RETURN END

| |


Comments:

Copyright ©


Обобщение и наследование


Обобщение и наследование позволяют выявить аналогии между различными классами объектов, определяют многоуровневую классификацию объектов. Так, в графических системах могут существовать классы, определяющие обрисовку различных геометрических фигур: точек, линий (прямых, дуг окружностей и кривых, определяемых сплайнами), многоугольников, кругов и т.п. (рисунок 2.17).

Рис. 2.17. Обобщение (выделение суперклассов)

Обобщение позволяет выделить класс одномерные фигуры и считать классы прямая, дуга и сплайн подклассами класса одномерные фигуры, а класс одномерные фигуры - суперклассом классов прямая, дуга и сплайн. Если при этом принять соглашение, что атрибуты и операции суперкласса действительны в каждом из его подклассов (говорят, что эти атрибуты и операции наследуются подклассами), то одинаковые атрибуты и операции классов прямая, дуга и сплайн (подклассов) могут быть вынесены в класс одномерные фигуры (суперкласс). Аналогично можно поступить и с двумерными фигурами, определив для классов многоугольник и круг суперкласс двумерная фигура. Наконец, можно определить класс фигура как суперкласс классов нульмерная фигура, одномерная фигура и двумерная фигура. Легко видеть, что отношения "подкласс - суперкласс" (обобщение) и "суперкласс - подкласс" (наследование) транзитивны, что позволяет строить классификационные деревья. При этом атрибуты и операции каждого суперкласса наследуются его подклассами всех уровней (мы как бы выносим за скобки одинаковые операции). Это значительно облегчает и сокращает описание классов.

На схемах обобщение (наследование) изображается треугольничком (рисунок 2.17). Треугольничек следует ставить даже в том случае, когда суперкласс имеет всего один подкласс. Слово размерность, следующее за верхним треугольничком на рисунке 2.17, является дискриминатором.

Дискриминатор - это атрибут типа "перечисление", показывающий, по какому из свойств объектов сделано данное обобщение.

Другие примеры обобщения (наследования) показаны на рисунке 2.18 (эти примеры связаны с основным нашим примером - системой обслуживания клиентов банковским консорциумом).


Рис. 2.18. Другие примеры обобщения (наследования)

Необходимо отметить, что, как показывает опыт практического проектирования систем, следует избегать обширных многоуровневых классификаций, так как поведение подклассов низших уровней многоуровневой классификации бывает трудно понять: большая часть (а нередко и все) атрибутов и операций таких классов определена в их суперклассах различных уровней. Если количество уровней классификации стало непомерно большим, нужно слегка изменить структурирование системы. Чтобы понять, какое число уровней является непомерно большим, можно руководствоваться следующими оценками: два-три уровня наследования, как правило, приемлемы всегда (мне известна одна фирма, разрабатывающая программные системы, в которой издан стандарт фирмы, запрещающий более чем трехуровневые классификации в программах); десятиуровневая классификация почти всегда неприемлема; пять-шесть уровней, как правило, достаточно для программистов и не слишком обременяет администрацию. Обобщение и наследование широко применяются не только при анализе требований к программным системам и их предварительном проектировании, но и при их реализации. Иногда в подклассе бывает необходимо переопределить операцию, определенную в одном из его суперклассов. Для этого операция, которая может быть получена из суперкласса в результате наследования, определяется и в подклассе; это ее повторное определение "заслоняет" ее определение в суперклассе, так что в подклассе применяется не унаследованная, а переопределенная в нем операция. Напомним, что каждая операция определяется своей сигнатурой; следовательно, сигнатура переопределения операции должна совпадать с сигнатурой операции из суперкласса, которая переопределяется данной операцией. Так, в примере, изображенном на рисунке 2.17, в классе круг переопределяется операция вращение его суперкласса фигура (при повороте круга его изображение не меняется, что позволяет сделать операцию вращение в классе круг пустой). Переопределение может преследовать одну из следующих целей:



расширение: новая операция расширяет унаследованную операцию, учитывая влияние атрибутов подкласса; ограничение: новая операция ограничивается выполнением лишь части действий унаследованной операции, используя специфику объектов подкласса; оптимизация: использование специфики объектов подкласса позволяет упростить и ускорить соответствующий метод (например, переопределяя операцию max класса IntegerSet в его подклассе SortedIntegerSet, мы можем резко упростить ее, используя специфические свойства упорядоченных множеств); удобство.

Целесообразно придерживаться следующих семантических правил наследования:

все операции-запросы (операции, которые используют значения атрибутов, но не изменяют их) должны наследоваться всеми подклассами; все операции, изменяющие значения атрибутов, должны наследоваться во всех их расширениях; все операции, изменяющие значения ограниченных атрибутов, или атрибутов, определяющих зависимости, должны блокироваться во всех их расширениях (например, операция размер_по_оси_x естественна для класса эллипс, но должна быть заблокирована (заменена пустой операцией) в его подклассе круг); операции не следует переопределять коренным образом; все методы, реализующие одну и ту же операцию, должны осуществлять сходное преобразование атрибутов; унаследованные операции можно уточнять, добавляя дополнительные действия.

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

| |


Comments:

Copyright ©


Одновременные события Синхронизация


На рисунке 2.50 представлена диаграмма состояний составного объекта: он состоит из четырех параллельно и независимо работающих компонентов (состояние каждого компонента не зависит от состояний остальных компонентов). Состояние такого объекта определяется как кортеж, членами которого являются состояния каждого из составляющих объектов. Диаграмма состояний при этом распадается на четыре независимые диаграммы состояний каждого из компонентов.

В системах, состоящих из нескольких параллельно работающих объектов, иногда бывает необходимо согласовать (синхронизировать) работу двух или более объектов. Для этого тоже используются события, так как синхронизация по существу состоит в необходимости задержать переход одного из синхронизируемых объектов в очередное состояние, пока другой объект не придет в некоторое фиксированное состояние, а переходы из состояния в состояние управляются событиями. Синхронизирующее событие может вырабатываться в любом из синхронизируемых объектов, либо в каком-либо третьем объекте.

Рис. 2.50. Диаграмма состояний составного объекта (подсистемы)

Если синхронизирующее событие вырабатывается вне синхронизируемого объекта, оно может быть передано ему из другого объекта; на диаграмме это обозначается как показано на рисунке 2.51.

Рис. 2.51. Передача события из одного объекта другому

Если объект получает события из нескольких независимых объектов, то состояние, в которое он перейдет, может зависеть от порядка, в котором будут получены эти события (а этот порядок случаен, так как объекты независимы). Это называется условием конкуренции. Одной из задач проектирования является исключение нежелательных условий конкуренции. Это достигается с помощью синхронизации.

Синхронизация используется и в случае, когда в каком-либо состоянии требуется параллельно выполнить несколько активностей. В качестве примера рассмотрим устройство вывода ATM (рисунок 2.52). Оно одновременно (параллельно) выдает наличные деньги и карточку; обе эти операции можно рассматривать как составную активность, состоящую из двух параллельно работающих активностей (пунктирная линия на диаграмме состояний делит состояние на две области, в каждой из которых выполняется одна из указанных активностей). Разделение управления на два параллельных потока схематически показано в виде стрелки, которая разделяется на две: событие готов вызывает переход из состояния установка сразу в два параллельных подсостояния состояния выдача; переход в следующее состояние происходит по двум событиям деньги взяты и карточка взята; если какое-либо из этих событий произойдет раньше другого, перехода все равно не будет, пока не произойдет и второе событие (в этом и состоит синхронизация).

Рис. 2.52. Синхронизация в подсистеме

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

| |

Comments:

Copyright ©



Ограничения


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

| |

Comments:

Copyright ©



Описание операций


Процессы ДПД в конце концов должны быть реализованы как операции объектов. Каждый процесс нижнего (базового) уровня, так же как и процессы верхних уровней, в состав которых входят процессы более нижних уровней, реализуются как операции. При этом реализация процессов верхних уровней может отличаться от их представления на ДПД, так как при реализации обычно производится их оптимизация: в результате оптимизации процессы нижних уровней, составляющие процесс более высокого уровня могут "слиться", после чего они станут невидимы.

Все операции должны быть специфицированы. Спецификация операции содержит ее сигнатуру (имя операции, количество, порядок и типы ее параметров, количество, порядок и типы возвращаемых ею значений) и описание ее эффекта (действия, преобразования). Для описания эффекта операции можно использовать:

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

Пример описания операции (эффект ее описан на естественном языке) приведен на рисунке 2.65.

изменить_счет (счет, сумма, вид_проводки) -> деньги, квитанция если сумма снимается и больше баланса счета, то "отменить_проводку" если сумма снимается и меньше баланса счета, то "дебетовать_счет" и "выдать_деньги" если сумма вносится на счет то "кредитовать_счет" если запрос то "выдать_запрос" во всех случаях: квитанция должна содержать номер ATM, дату, время, номер счета, вид проводки, сумму проводки (если она есть), новый баланс счета

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

Внешняя спецификация операции описывает только те изменения, которые видны вне операции. Операция может быть реализована таким образом, что при ее выполнении будут использоваться некоторые значения, определенные внутри операции (например, в целях оптимизации), некоторые из этих значений могут даже быть частью состояния объекта.
Эти детали реализации операции скрыты от остальных объектов и не участвуют в определении внешнего эффекта операции. Изменения внутреннего состояния объекта, не видные вне его, не меняют значения объекта. Все нетривиальные операции можно разделить на три категории: запросы, действия и активности. Запросом называется операция без побочных эффектов над видимым извне объекта его состоянием (чистая функция). Запрос, у которого нет параметров, кроме целевого объекта, является производным атрибутом. Например, для точки на координатной плоскости, радиус и полярный угол - производные атрибуты; из этого примера видно, что между основными и производными атрибутами нет принципиальной разницы, и выбор основных атрибутов во многом случаен. Действием называется операция, имеющая побочные эффекты, которые могут влиять на целевой объект и на другие объекты системы, которые достижимы из целевого объекта. Действие не занимает времени (логически, оно совершается мгновенно). Каждое действие может быть определено через те изменения, которые оно производит в состоянии объекта, меняя значения его атрибутов и связей; в каком порядке производятся эти изменения, несущественно: мы считаем, что все они происходят одновременно и мгновенно. Наиболее распространенным способом описания действия является задание алгоритма его выполнения на компьютере. Активностью называется операция, выполняемая объектом, или над объектом, выполнение которой занимает определенное время. Активность имеет побочные эффекты. Активности могут быть только у активных объектов, так как пассивные объекты есть попросту склады данных. | |

Comments:

Copyright ©

Определение классов


Анализ внешних требований к проектируемой прикладной системе позволяет определить объекты и классы объектов, связанные с прикладной проблемой, которую должна решать эта система. Все классы должны быть осмыслены в рассматриваемой прикладной области; классов, связанных с компьютерной реализацией, как например список, стэк и т.п. на этом этапе вводить не следует.

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

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

Далее список возможных классов должен быть проанализирован с целью исключения из него ненужных классов. Такими классами являются:

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

После исключения имен всех ненужных (лишних) возможных классов будет получен предварительный список классов, составляющих проектируемую систему.

| |

Comments:

Copyright ©



Определение объектов и классов


В пункте сформулирована задача и приведена схема сети банковского обслуживания (). Анализируя эту постановку задачи, можно выделить возможные классы, сопоставив их существительным, упомянутым в ее предварительной формулировке; получится следующий список возможных имен классов (в алфавитном порядке):

ATM (банкомат) кассир программное обеспечение
банк кассовый терминал система
банковская сеть квитанция проверка безопасности
данные проводки клиент служба ведения записей
данные счета компьютер банка счет
деньги консорциум цена
доступ пользователь центральный компьютер
карточка проводка

Исследуем этот список, исключая из него имена классов в соответствии с рекомендациями п. :

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

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

ATM (банкомат) кассовый терминал проводка
банк клиент счет
карточка компьютер банка центральный компьютер
кассир консорциум

| |

Comments:

Copyright ©



Определение зависимостей


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

Аналогично тому, как имена возможных классов получались из существительных, встречающихся в предварительной постановке прикладной задачи, имена возможных зависимостей могут быть получены из глаголов или глагольных оборотов, встречающихся в указанном документе. Так обычно описываются: физическое положение (следует_за, является_частью, содержится_в), направленное действие (приводит_в_движение), общение (разговаривает_с), принадлежность (имеет, является_частью) и т.п. Пример выделения явных и неявных глагольных оборотов из предварительной постановки конкретной прикладной задачи рассмотрен в п.

Затем следует убрать ненужные или неправильные зависимости, используя следующие критерии:

зависимости между исключенными классами должны быть исключены, либо переформулированы в терминах оставшихся классов (см. пример в п. ); нерелевантные зависимости и зависимости, связанные с реализацией, должны быть исключены (см. пример в п. ); действия: зависимость должна описывать структурные свойства прикладной области, а не малосущественные события (см. примеры в п. ); тренарные зависимости: большую часть зависимостей между тремя или большим числом классов можно разложить на несколько бинарных зависимостей, используя в случае необходимости квалификаторы (см. примеры в п. ); в некоторых (очень редких) случаях такое разложение осуществить не удается; например, тренарная зависимость "Профессор читает курс в аудитории 628" не может быть разложена на бинарные без потери информации; производные зависимости: нужно исключать зависимости, которые можно выразить через другие зависимости, так как они избыточны (см.
пример в п. ); при исключении избыточных (производных) зависимостей нужно быть особенно осторожным, так как не все дублирующие одна другую зависимости между классами избыточны; в некоторых случаях другие зависимости позволяют установить только существование еще одной производной зависимости, но не позволяют установить кратность этой зависимости; например, в случае, представленном на рисунке 2.36, фирма имеет много служащих и владеет многими компьютерами; каждому служащему предоставлено для персонального использования несколько компьютеров, кроме того, имеются компьютеры общего пользования; кратность зависимости предоставлен_для_использования не может быть выведена из зависимостей служит и владеет; хотя производные зависимости и не добавляют новой информации, они часто бывают удобны; в этих случаях их можно указывать на диаграмме, пометив косой чертой.



Рис. 2.36. Неизбыточные зависимости

Удалив избыточные зависимости, нужно уточнить семантику оставшихся зависимостей следующим образом:

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

| |


Comments:

Copyright ©


Оптимизация разработки


Объектная модель, построенная на этапе анализа требований к программной системе, содержит информацию о логической структуре системы; на этапе разработки объектная модель уточняется и пополняется: в нее добавляются детали, связанные с необходимостью обеспечить более эффективный доступ к информационным структурам во время работы системы. Цель оптимизации разработки - заменить семантически корректную, но недостаточно эффективную модель, построенную на этапе анализа, более эффективной. В процессе оптимизации разработки выполняются следующие преобразования:

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

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

Рис. 3.9. Ускорение поиска с помощью производной зависимости

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

Рис. 3.10. Использование производных атрибутов для исключения повторных вычислений

Рис. 3.11. Использование производной зависимости

На рисунке 3.11 показано, как введение производной зависимости позволяет не перевычислять координаты перекрывающихся элементов окон в оконной системе для графического дисплея.

Производные атрибуты должны изменять свои значения, когда меняются их базовые значения. Для обеспечения этого пользуются одним из трех методов:

явное перевычисление: каждый производный атрибут определяется с помощью одного или нескольких базовых объектов; когда значения базовых объектов меняются, требуется изменить значения всех производных атрибутов, связанных с ними; периодическое перевычисление всех производных атрибутов (в момент изменения базового значения производные атрибуты перевычисляются); использование активных значений: активным называется значение, с которым связано некоторое множество зависимых значений; все зависимые значения группируются вокруг определяющих их активных значений и перевычисляются синхронно с ними.

| |

Comments:

Copyright ©



Организация системы классов, используя наследование


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

| |

Comments:

Copyright ©



Организация системы классов с использованием наследования


В рассматриваемом примере естественно определить суперклассы для объектов, определяющих различные терминалы: кассовый_терминал и ATM (банкомат), и для объектов, определяющих проводки: проводка_кассира и удаленная_проводка (с банкомата).

Внеся соответствующие изменения, получим объектную диаграмму, представленную на рисунке 2.39.

Рис. 2.38. Объектная диаграмма для банковской сети после уточнения атрибутов и добавления квалификаторов

Рис. 2.39. Объектная диаграмма для банковской с учетом наследования

| |

Comments:

Copyright ©



Передача параметров методам


Дополнительный параметр каждого метода, определяющий экземпляр структуры (класса), к которому следует применить этот метод, рекомендуется реализовывать как указатель. Хотя в языке C и допускаются параметры, имеющие тип структуры, передача значения структуры в качестве параметра связана с переписыванием значения этой структуры в автоматическую память соответствующей функции, что не только связано с потерей эффективности, но и семантически неверно, когда структура определяет объект, так как применение метода должно изменить значения соответствующих полей этой структуры (они представляют атрибуты объекта). Пример передачи параметров одному из методов (этот метод входит в список методов, приведенный в комментарии к определению структуры Window):

add_to_selections (shape, self) struct Window* self; struct Shape* shape;

| |

Comments:

Copyright ©



Подготовка словаря данных


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

| |


То же самое лицо, держащее счет и в другом банке рассматривается как другой клиент. Компьютер_банка - компьютер, принадлежащий банку, который взаимодействует с сетью ATM (банкоматов) и собственными кассовыми_терминалами банка. Банк может иметь свою внутреннюю компьютерную сеть для обработки счетов, но здесь мы рассматриваем только тот компьютер_банка, который взаимодействует с сетью ATM. Консорциум - объединение банков, которое обеспечивает работу сети ATM (банкоматов). Сеть передает в консорциум проводки банков. Проводка - единичный интегрированный запрос на выполнение некоторой последовательности операций над счетами одного клиента. Было сделано предположение, что ATM (банкоматы) только выдают деньги, однако для них не следует исключать возможности печати чеков или приема денег и чеков. Хотелось бы также обеспечить гибкость системы, которая в дальнейшем обеспечит возможность одновременной обработки счетов разных клиентов, хотя пока этого не требуется. Различные операции должны быть правильно сбалансированы. Счет - единичный банковский счет, над которым выполняются проводки. Счета могут быть различных типов; клиент может иметь несколько счетов. Центральный_компьютер - компьютер, принадлежащий консорциуму, который распределяет проводки и их результаты между ATM (банкоматами) и компьютерами_банков. Центральный_компьютер проверяет коды банков, но не выполняет проводок самостоятельно. | |


Comments:

Copyright ©


Преобразование классов в структуры данных


В языке C класс можно рассматривать как тип структуры, определенный в этом языке: поля структуры соответствуют атрибутам класса (при этом каждый атрибут может иметь простой, либо составной тип, т.е. тип структуры или массива). В таком случае каждый объект некоторого класса будет представлен конкретной структурой, экземпляром соответствующего типа структуры.

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

Например, класс Window можно представить как следующую структуру (struct) языка C:

struct Window { /* структура соответствует классу * class Window * для которого определены следующие методы (все методы имеют * дополнительный параметр, Window* this, определяющий экземпляр * структуры (объект), к которому применяется метод): * public: * Window(Length x0, Length y0, Length width, Length height, * Window* this); * D_Window (Window* this); -- соответствует деструктору ~Window() * void add_box (Length x, Length y, Length width, Length height, * Window* this); * void add_circle (Length x, Length y, Length radius, Window* this); * void clear_selections (Window* this); * void cut_selections (Window* this); * Group* group_selections (Window* this); * void move_selections (Length deltax, Length deltay, Window* this); * void redraw_all (Window* this); * void select_item (Length x, Length y, Window* this); * void ungroup_selections (Window* this); * private: * void add_to_selections (Shape* shape, Window* this); */ Length xmin; Length ymin; Length xmax; Length ymax; }; При этом предполагается, что тип Length введен через typedef floast Length; Ссылки на объекты класса Window реализуются как указатели языка C: struct Window* window; Length x1 = window->xmin;

Объект может быть при этом размещен статически, автоматически (в стеке), или динамически (в куче).

| |

Comments:

Copyright ©



Размещение объектов в памяти


Объекты, определенные в программе на языке C могут размещаться в памяти программы (статические глобальные объекты), в стеке (автоматически размещаемые локальные объекты), или в куче (динамические объекты). Время жизни статических объектов - это все время выполнения программы. Они используются для хранения глобальных переменных и констант, но при объектно-ориентированной разработке программ стараются использовать как можно меньше глобальных объектов, так как они нарушают модульную структуру программы. Глобальные объекты в языке C объявляются на внешнем уровне программного файла, вне составляющих его функций; инициализация таких объектов обычно производится при их порождении во время компиляции программы. Пример объявления статического (глобального) объекта:

static struct Window outer_window = {0.0, 0.0, 8.5, 11.0};

При вызове методов, использующих объявленную глобальную переменную, им следует передавать ее адрес (&outer_window).

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

Динамически размещаемые объекты необходимо использовать, когда во время компиляции программы неизвестно их количество. Такие объекты размещаются в куче по запросу (функции malloc или сalloc) во время выполнения программы. Будучи размещенным в памяти динамический объект сохраняется в ней до тех пор, пока не будет явным образом отменен (функция free). Пример функции размещения и инициализации динамического объекта:

struct Window* create_window(xmin, ymin, width, height); Length xmin, ymin, width, height; { struct Window* window; /*размещение объекта*/ window = (struct Window*)malloc(sizeof(struct Window)); /*инициализация объекта*/ window-> xmin = xmin; window-> ymin = ymin; window-> xmax = xmin + width; window-> ymax = ymin + height; return window; };

Перед удалением динамического объекта с помощью функции free необходимо удостовериться, что не осталось указателей на этот объект.

| |

Comments:

Copyright ©



Разработка алгоритмов, реализующих полученные операции


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

вычислительная сложность алгоритма: для алгоритмов, применяемых к достаточно большим массивам данных, важно, чтобы оценка их вычислительной сложности была разумной; например, вряд ли имеет смысл избегать косвенности в ссылках, особенно когда введение косвенности существенно упрощает понимание программы; в то же время замена пузырьковой сортировки с оценкой сложности n2 на алгоритм сортировки с оценкой n?log n всегда резко ускоряет вычисления; понятность алгоритма и легкость его реализации: для достижения этого можно даже пойти на небольшое снижение эффективности; например, введение рекурсии всегда снижает скорость выполнения программы, но упрощает ее понимание (рисунок 3.8); гибкость: большая часть программ рано или поздно должна быть модифицирована; как правило, высокоэффективный алгоритм труден для понимания и модификации; одним из выходов является разработка двух алгоритмов выполнения операции: простого, но не очень эффективного, и эффективного, но сложного; при модификации в этом случае изменяется более простой алгоритм, что обеспечивает работоспособность системы на период разработки более эффективного модифицированного алгоритма.

Рис. 3.8. Сравнение рекурсивного и нерекурсивного алгоритмов вычисления n!

Выбор алгоритмов связан с выбором структур данных, обрабатываемых этими алгоритмами. Удачный выбор структур данных позволяет существенно оптимизировать алгоритм.

Еще одним способом упрощения и оптимизации алгоритмов является введение внутренних (вспомогательных) классов. Эти классы не имеют соответствий в реальном мире; они связаны с реализацией, но могут существенно упростить ее (примеры: класс стек, класс двусвязный список и т.п.).

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

При распределении операций по классам руководствуются следующими соображениями:

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

| |

Comments:

Copyright ©



Разработка зависимостей


Зависимости - это "клей" объектной модели: именно они позволяют рассматривать модель как нечто целое, а не просто как множество классов.

Односторонние зависимости можно реализовать с помощью ссылок (указателей) (см. рисунок 3.14). При этом, если кратность зависимости равна единице, ей соответствует один указатель, если кратность больше единицы, то множество указателей.

Рис. 3.14. Реализация односторонней зависимости

На рисунке 3.15 показан способ реализации двусторонней зависимости с помощью указателей.

Рис. 3.15. Реализация двусторонней зависимости

На рисунке 3.16 показан способ реализации зависимости с помощью таблицы (как в реляционных базах данных).

Рис. 3.16. Реализация зависимости с помощью таблицы

При реализации зависимостей с помощью указателей атрибуты зависимостей (связей) переносятся в один из классов, участвующих в зависимости.

| |

Comments:

Copyright ©



Реализация наследования


В языке Eiffel список наследования помещается вслед за ключевым словом inherit:

class ITEM export cut, move, pick, ungroup feature cut is deferred end; move (deltax, deltay: REAL) is deferred end; pick (x, y: REAL): BOOLEAN is deferred end; ungroup () is deferred end class SHAPE export cut, draw, erase, move, pick, ungroup, write inherit ITEM feature * x, y: REAL; cut is <body> end; draw is <body> end; erase is <body> end; move (deltax, deltay: REAL) is <body> end; pick (x, y: REAL): BOOLEAN is <body> end; ungroup is <body> end; write (acolor: COLOR) is deferred end; end classrBOX export pick, write inherit SHAPE redefine pick, write feature width, height: REAL; Create (x0, y0, width0, height0:*REAL) is <body> end; pick (x, y: REAL): BOOLEAN is <body> end; write (acolor: COLOR) is <body> end end class CIRCLE export pick, write inherit SHAPE redefine pick, write feature radius: REAL; Create (x0, y0, radius0: REAL) is <body> end; pick (x, y: REAL): BOOLEAN is <body> end; write (acolor: COLOR) is <body> end end

Для обозначения абстрактных операций используется ключевое слово deferred; такие операции должны быть реализованы во всех подклассах. Переопределение свойств класса в подклассе отмечается в разделе redefine.

В языке Smalltalk описание класса Item, его подкласса Shape, а также подклассов Box и Circle класса Shape может иметь следующий вид:

class name Item superclass Object   class name Shape superclass Item instance variables x y instance methods cut draw erase move: aPoint ungroup   class name Box superclass Shape instance variables width height instance methods pick: aPoint write: aColor class methods createAt: aPoint width: widthSize length: lengthSize   class name Circle superclass Shape instance variables radius instance methods pick: aPoint write: aColor class methods createAt: aPoint radius: radiusSize

Все атрибуты суперкласса доступны всем его потомкам. Все методы могут быть переопределены в подклассах. Множественное наследование не поддерживается.

| |

Comments:

Copyright ©


Наследование в языке C реализуются через указатели. Рассмотрим, например, конкретные подклассы Box и Circle абстрактного класса Shape. На языке C их можно представить следующим образом:

struct Shape { struct ShapeClass* class; Length x; Length y; }; struct Box { struct BoxClass* class; Length x; Length y; Length width; Length height; }; struct Circle { struct CircleClass* class; Length x; Length y; Length radius; };

Указатель на структуры Box или Circle можно передать функции, ожидающей указатель на Shape, так как первые несколько членов структур Box и Circle идентичны первым членам структуры Shape (это позволяет привести тип указателя на структуру Box или Circle привести к типу указателя на структуру Shape).

| |

Comments:

Copyright ©



Реализация управления


Реализация управления связана с реализацией динамической модели объектов системы. Известны три подхода к реализации динамической модели:

процедурное управление: состоянию соответствует определенный фрагмент программы; управление через события: явная реализация конечного автомата; использование параллельных независимых задач.

Процедурное управление является традиционным способом реализации динамической модели; в этом случае состояние объекта определяется текущим оператором программы, а каждому переходу соответствует оператор ввода: следующее состояние определяется по текущему состоянию и вводимому имени события. Пример практического использования процедурного управления представлен на рисунке 3.12.

Рис. 3.12. Псевдокод, соответствующий динамической модели ATM

| |


В языке Eiffel для реализации зависимостей применяются конструкции, аналогичные соответствующим конструкциям языка C++. Поддерживаются параметризованные (родовые (generic)) контейнерные объекты (обычно эти объекты параметризуются относительно типов объектов, которые они содержат). Базовая библиотека классов системы Eiffel содержит контейнерный класс LINKED_LIST, который можно использовать для реализации зависимости типа "много к одному" между классами ITEM и GROUP:

class ITEM export get_group -- выборочный экспорт в класс GROUP set_group(GROUP), forget_group(GROUP)   feature get_group: GROUP is do Result := mygroup end; set_group(g:GROUP) is do mygroup := g end; forget_group is do forget(mygroup) end; end --ITEM   class GROUP export add_item, remove_item, get_items inherit ITEM feature items: LINKED_LIST[ITEM]; --параметризованный тип Create is do items.Create end; add_item(value:ITEM) is do items.finish; items.insert_right(value); value.set_group(Current) end; remove_item(value:ITEM) is do items.search(value,l); items.delete; value.forget_group end; get_items(number:INTEGER):ITEM is do Result := items end; end --GROUP

Язык Eiffel обеспечивает выборочный экспорт относительно любого свойства. В рассматриваемом примере в класс GROUP экспортируются свойства set_group и forget_group класса ITEM. Это поддерживает инкапсуляцию путем ограничения доступа по записи данных в объекты классов, участвующих в зависимости между ITEM и GROUP.

Операция forget языка Eiffel предназначена для освобождения памяти; она освобождает память, занимаемую объектом, который является ее операндом, а также присваивает объектной ссылке неопределенное значение.

В языке Smalltalk большую часть зависимостей помогает реализовать богатая системная библиотека классов. Например, для реализации зависимости типа "много-к-одному" между графическими объектами (Item) и группой, в которую они входят (Group) можно использовать библиотечный класс Set:

class name Item superclass Object   class name Shape superclass Item instance variables group instance methods cut draw erase move: aPoint ungroup --дополнительные методы getGroup |group --приватные методы putGroup: aGroup group <- aGroup   class name Group superclass Item instance variables items class methods new |((super new)putItems:(Set new)) instance methods pick: aPoint write: aColor addItem: anItem items add: anItem. anItem putGroup: self removeItem items remove: anItem. anItem putGroup: nil getItems |items copy --приватные методы putItems: aSet items <- aSet

Ввиду того, что в системе Smalltalk не производится контроль типов, нет никаких ограничений на тип объектов, которые можно добавить в группу: любой объект, который соответствует сообщению putGroup:, допустим. Строка приватные методы является комментарием, сообщающим программисту, что методы, перечисленные после этой строки лучше использовать как приватные; если он не последует этому совету, система все равно не зафиксирует ошибку, так как Smalltalk не поддерживает приватности методов и данных, и на самом деле все методы общедоступны.

| |

Comments:

Copyright ©




Для реализации зависимостей есть те же две возможности, что и в случае объектно-ориентированных окружений: отображение зависимостей на указатели (ссылки), или реализация зависимостей с помощью вспомогательных объектов.

Если зависимости отображаются на указатели (наиболее традиционный подход для бинарных зависимостей), для этих указателей предусматриваются дополнительные поля в структурах (записях), представляющих объекты взаимно-зависимых классов; если зависимость множественная (хотя бы в одну из сторон), то представляющий ее указатель ссылается не на класс, а на последовательность (в смысле STL) указателей объектов соответствующего класса. Поскольку все зависимости двусторонние, каждый объект из указанной последовательности тоже должен иметь указатель, определяющий рассматриваемую зависимость.

С помощью вспомогательных объектов обычно реализуются зависимости, которые трудно представить через указатели (например, зависимости между несколькими классами).

В качестве примера первого способа реализации зависимостей рассмотрим реализацию зависимости (типа "много к одному") между классами Item и Group:

struct Item { struct ItemClass* class; struct Group* group; }; struct Group { struct GroupClass* class; int item_count; struct Item** items; };

| |

Comments:

Copyright ©



События, состояния объектов и диаграммы состояний


Текущее состояние объекта характеризуется совокупностью текущих значений его атрибутов и связей. Во время работы системы составляющие ее объекты взаимодействуют друг с другом, в результате чего изменяются их состояния. Единицей влияния является событие: каждое событие приводит к смене состояния одного или нескольких объектов в системе, либо к возникновению новых событий. Работа системы характеризуется последовательностью происходящих в ней событий.

События

Событие происходит в некоторый момент времени (нередко оно используется для определения соответствующего момента времени). Примеры событий: старт ракеты, старт забега на 100 м, начало проводки (в банковской сети), выдача денег и т.п. Событие не имеет продолжительности (точнее, оно занимает пренебрежимо малое время).

Одно из событий может логически предшествовать другому, либо следовать за другим, либо они могут быть независимыми; примерами логически (причинно) связанных событий являются старт и финиш одного забега, начало проводки и выдача денег клиенту (в результате этой проводки), примерами независимых событий - старт ракеты и финиш забега, проводки, запущенные с разных ATM, и т.п. Если события не имеют причинной связи (т.е. они логически независимы), они называются независимыми (concurrent); такие события не влияют друг на друга. Независимые события не имеет смысла упорядочивать, так как они могут происходить в произвольном порядке. Модель распределенной системы обязательно должна содержать независимые события и активности.

События передают информацию с одного объекта на другой. Существуют классы событий, которые просто сигнализируют о том, что что-то произошло или происходит (примеры: загорание лампочки лифта, гудок в телефонной трубке). В программировании рассматриваются исключительные события (иногда их называют исключениями), которые сигнализируют о нарушениях работы аппаратуры, либо программного обеспечения.

Сценарии и трассы событий

Сценарием называется последовательность событий, которая может иметь место при конкретном выполнении системы.
Сценарии могут иметь разные области влияния: они могут включать все события, происходящие в системе, либо только события, возникающие и влияющие только на определенные объекты системы. На рисунке 2.44 приведен пример сценария пользования телефонной линией. Каждое событие в этом сценарии передает информацию с одного объекта на другой; например событие начинается длинный гудок передает сигнал от телефонной линии к вызывающему (пользователю). При анализе динамики работы системы необходимо составить и рассмотреть несколько сценариев, отражающих типичные варианты ее работы. вызывающий снимает трубку начинается длинный гудок вызывающий набирает цифру (9) гудок прекращается вызывающий набирает цифру (3) вызывающий набирает цифру (9) вызывающий набирает цифру ( ) вызывающий набирает цифру ( ) вызывающий набирает цифру ( ) вызывающий набирает цифру ( ) вызванный телефон начинает звонить вызывающий слышит гудки вызванный телефон отвечает гудки прекращаются телефоны соединены вызванный по телефону вешает трубку телефоны разъединены вызывающий вешает трубку
Рис. 2.44. Пример сценария: разговор по телефону
Следующим этапом после разработки и анализа сценариев является определение объектов, генерирующих и принимающих каждое событие сценария. Последовательности событий с привязкой к объектам проектируемой системы удобно представлять на диаграммах, называемых трассами событий. Пример трассы событий для разговора по телефону представлен на рисунке 2.45. Вертикальные линии изображают на этой диаграмме объекты, а горизонтальные стрелки - события (стрелка начинается в объекте, генерирующем событие, и заканчивается в объекте, принимающем событие). Более поздние события помещены ниже более ранних, одновременные - на одном уровне.


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


Реакция объекта на событие может включать некоторое действие и/или перевод объекта в новое состояние. Объект сохраняет свое состояние в течение времени между двумя последовательными событиями, которые он принимает: события представляют моменты времени, состояния - отрезки времени; состояние имеет продолжительность, которая обычно равна отрезку времени между двумя последовательными событиями, принимаемыми объектом, но иногда может быть больше. При определении состояний мы не рассматриваем тех атрибутов, которые не влияют на поведение объекта, и объединяем в одно состояние все комбинации значений атрибутов и связей, которые дают одинаковые реакции на события. Диаграммы состояний Диаграмма состояний связывает события и состояния. При приеме события следующее состояние системы зависит как от ее текущего состояния, так и от события; смена состояния называется переходом. Диаграмма состояний - это граф, узлы которого представляют состояния, а направленные деги, помеченные именами соответствующих событий, - переходы. Диаграмма состояний позволяет получить последовательность состояний по заданной последовательности событий.

Рис. 2.46. Диаграмма состояний телефонной линии
На рисунке 2.46 приведена, в качестве примера, диаграмма состояний телефонной линии. Другие примеры диаграмм состояний см. в последующих пунктах. | |

Comments:

Copyright ©

Условия


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

Условия могут использоваться как ограничения на переходы: условный переход выполняется только тогда, когда и произошло соответствующее событие, и выполнено условие этого перехода (диаграмма состояний, представленная на рисунке 2.47, демонстрирует это на примере автомобильного движения по магистралям "Север-Юг" и "Запад-Восток"). На диаграммах состояний условия записываются вслед за событиями в квадратных скобках.

Рис. 2.47. Диаграмма состояний, на которой указаны условия

| |

Comments:

Copyright ©



Уточнение атрибутов


На следующем этапе уточняется система атрибутов: корректируются атрибуты классов, вводятся, в случае необходимости, новые атрибуты. Атрибуты выражают свойства объектов рассматриваемого класса, либо определяют их текущее состояние.

Атрибуты обычно соответствуют существительным; например цвет_автомобиля (свойство объекта), позиция_курсора (состояние объекта). Атрибуты, как правило, слабо влияют на структуру объектной модели.

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

Наряду с атрибутами объектов необходимо ввести и атрибуты зависимостей между классами (связей между объектами).

При уточнении атрибутов руководствуются следующими критериями:

Замена атрибутов на объекты. Если наличие некоторой сущности важнее, чем ее значение, то это объект, если важнее значение, то это атрибут: например, начальник - это объект (неважно, кто именно начальник, главное, чтобы кто-то им был), зарплата - это атрибут (ее значение весьма существенно); город - всегда объект, хотя в некоторых случаях может показаться, что это атрибут (например, город как часть адреса фирмы); в тех случаях, когда нужно, чтобы город был атрибутом, следует определить зависимость (скажем, находится) между классами фирма и город. Квалификаторы. Если значение атрибута зависит от конкретного контекста, его следует сделать квалификатором (см. примеры в п. ). Имена. Именам обычно лучше соответствуют квалификаторы, чем атрибуты объектов; во всех случаях, когда имя позволяет сделать выбор из объектов некоторого множества, его следует сделать квалификатором (см. примеры в п. ). Идентификаторы. Идентификаторы объектов связаны с их реализацией. На ранних стадиях проектирования их не следует рассматривать в качестве атрибутов. Атрибуты связей. Если некоторое свойство характеризует не объект сам по себе, а его связь с другим объектом (объектами), то это атрибут связи, а не атрибут объекта. Внутренние значения. Атрибуты, определяющие лишь внутреннее состояние объекта, незаметное вне объекта, следует исключить из рассмотрения. Несущественные детали. Атрибуты, не влияющие на выполнение большей части операций, рекомендуется опустить.


Применяя критерии, сформулированные в п. , получим:

Карточка содержит код_банка и код_карточки; их можно считать атрибутами объектов класса карточка, но удобнее использовать в качестве квалификаторов, так как код_банка обеспечивает выбор банка, сокращая кратность зависимости консорциум - банк; для аналогичного использования кода_карточки необходимо добавить зависимость Банк выпускает карточки, квалификатором которой будет код_карточки.

После внесения перечисленных изменений диаграмма примет вид, представленный на .

| |

Comments:

Copyright ©



Уточнение наследования классов


Уточняя определения классов и операций, стараемся увеличить степень наследуемости: чем больше классов находятся в отношении наследования, тем меньше функций, реализующих операции этих классов необходимо будет запрограммировать. Для увеличения степени наследуемости следует:

Перестроить классы и операции. Выявить одинаковые (или взаимно однозначно соответствующие) операции и атрибуты классов и определить для этих классов абстрактный суперкласс. Использовать делегирование операций, когда наследование семантически некорректно.

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

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


Использование делегирования операций можно пояснить на следующем примере (рисунок 3.13). Класс стек близок классу список, причем операциям стека push и pop соответствуют очевидные частные случаи операций списка add и remove. Если реализовать класс стек как подкласс класса список, то придется применять вместо операций push и pop более общие операции add и remove, следя за их параметрами, чтобы избежать записи или чтения из середины стека; это неудобно и чревато ошибками. Гораздо лучше объявить класс список телом класса стек (делегирование), обращаясь к операциям списка через операции стека. При этом, не меняя класса список, мы заменяем его интерфейс интерфейсом класса стек.


Рис. 3.13. Реализация стека с использованием наследования(а) и делегирования(б)

| |


Comments:

Copyright ©


Вложенные диаграммы состояний


Диаграмма состояний компонента трансмиссия () имеет узел (состояние) вперед, который сам является диаграммой состояний (вложенная диаграмма состояний). Интерпретация этой диаграммы следующая: трансмиссия имеет три состояния: нейтральное, назад и вперед; состояние вперед имеет три подсостояния (они уточняют состояние вперед): первая, вторая и третья. На диаграммах состояний суперсостояние изображается как прямоугольник с закругленными углами, внутрь которого помещаются все его подсостояния.

| |

Comments:

Copyright ©



Выбор методов для операций


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

Методы, выбор которых возможен во время компиляции, на языке C могут быть реализованы как непосредственные вызовы соответствующих функций. Это прежде всего относится к тем операциям, которые реализуются только одним методом, что снимает проблему выбора. Например, все методы в классе Window уникальны (единственны). Однако наиболее общий подход состоит в определении для каждого класса дескриптора, содержащего указатели на все методы для каждой операции, видимой из этого класса, включая унаследованные операции. Каждый дескриптор класса является структурой языка C, элементами которой являются все операции, определенные в этом классе или унаследованные им от суперкласса. Следующий фрагмент программы содержит определения дескрипторов класса для классов Item, Shape, Box и Circle:

struct ItemClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); }; struct ShapeClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); }; struct BoxClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); }; struct CircleClass { char* class_name; void(* cut)(); void(* move)(); Boolean(* pick)(); void(* ungroup)(); void(* write)(); };

В дескрипторе класса определены операции, видимые в этом классе, но остается необходимость определить и проинициализировать дескрипторы класса для каждого класса: в каждом поле дескриптора класса должно стоять имя функции, которая реализует метод, определенный в этом классе или унаследованный им (например, класс Box наследует операцию move у класса Shape, но заменяет операции pick и write своими собственными методами:


struct BoxClass BoxClass = { "Box", Shape__cut, Shape__move, Box__pick, Shape__ungroup, Box__write, }; struct CircleClass CircleClass = { "Circle", Shape__cut, Shape__move, Circle__pick, Shape__ungroup, Circle__write, };

Если у класса есть атрибуты уровня класса, их тоже можно запомнить в дополнительных полях дескриптора класса. Например, можно поместить в дескриптор класса имя этого класса и использовать его во время отладки, или каким-либо другим образом. Отметим, что дескриптор класса не нужен абстрактному классу (например, Shape). Когда порождается новый объект, указатель на его дескриптор класса помещается в поле Class структуры, представляющей этот объект:

struct Circle * create_circle(x0,y0,radius0) Length x0,y0,radius0; { struct Circle * new_circle; new_circle = (struct Circle*)malloc(sizeof(struct Circle)); new_circle->class = &CircleClass; new_circle->x = x0; new_circle->y = y0; new_circle-> radius = radius0; return new_circle; };

Если выбор метода для операции должен быть сделан во время выполнения программы, то для определения нужной функции используется дескриптор класса. Например, вызов операции pick для формы (Shape), определяемой во время выполнения программы, требует следующего кода:

struct Shape* shape; Length x,y; Boolean status; status = (*shape->class->pick)(shape,x,y);

Динамический выбор метода требует двух дополнительных обращений к памяти по сравнению с непосредственным вызовом функции. | |


Comments:

Copyright ©