Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

I 610 I

Часть III

 

double vol = cyl2.getVolume();

// отсутствует в Circle

cout «

" Circumference of first cylinder: " « length « endl;

cout «

" Volume of the second cylinder: " «

vol « endl;

cout «

" Diameter of the first cylinder: " «

diam « endl;

cout «

"Area of first cylinder: " « cyl1.getArea()« endl;

return 0;

}

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

В правилах C+-I- об этом ничего не говорится. Вы можете переписать базовую функцию с тем же интерфейсом, но делать это не обязательно. По какой-то причине многие программисты С+Н- верят, что интерфейс должен быть тот же. Это не так. Изменяет ли проектировщик производного класса интерфейс метода или сохраняет тот, что и в базовом классе, наименование базового метода скры­ вается от клиентской программы производного класса. Это метод производного класса, который вызывается из клиентской программы. Клиентская программа может использовать базовый метод, но в этом случае потребуется явное задание от объекта базового класса выдать команду компилятору и визуальную подсказку для читателя.

Cylinder

су11(2.5,6.0),

су12(5.0,

 

7.5);

 

 

 

/ /

Инициализация данных

double

length = cyl1.getLength()

;

 

 

 

/ /

подобно Circle

cyl1.set(3.0) ;

 

 

 

 

 

 

 

 

 

 

double diam = 2 * cyl1 .getRadiusO;

 

 

 

 

 

 

double vol = cyl2.getVolume();

 

 

 

 

 

/ /

отсутствует

в Circle

cout

«

"

Circumference of f i r s t

 

cylinder

: "

« length

« endl;

 

cout

«

" Volume of the second cylinder

:

"

«

vol « endl;

 

cout

«

"

Diameter

of

the f i r s t

cylinder:

"

«

diam «

endl;

 

cout

«

"

Side area

of

f i r s t cylinder:

"

 

 

 

 

 

 

«

cyl1.Circle::getArea()

«

endl;

 

 

/ /

визуальная

подсказка

Достоинства и недостатки наследования и композиции

Наследование является хорошим абстрактным средством. Оно явно подчер­ кивает концептуальные связи между классами, если они существуют. Например, общность классов Circle и Cylinder лучше всего отражается в структуре про­ граммы получением производного класса Cylinder из класса Circle. Эти связи' наследования показаны в программе Cylinder. Программисты клиентской части и лица, осуществляющие сопровождение, не должны отдельно изучать классы, сравнивать тексты программы.

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

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

Глава 14 • Выбор между наследованием и ко1н^позицией

611

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

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

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

Если количество общих методов относительно велико, а число дополнитель­ ных сервисов небольшое, то более подходящим способом является наследование. Многие программисты раздражены необходимостью записывать "бессмысленные" однострочные методы — методы базового класса будут непосредственно наследо­ ваться производным классом. Переопределение базовых методов в производном классе привлекает эстетически и открывает способ для использования полимор­ физма (см. следующую главу).

При выборе наследования используйте его наиболее простым способом, на­ пример общедоступным выводом. Избегайте защищенного и закрытого наследо­ вания.

Часто наследование используется для ускорения работы, без ясной концеп­ туальной связи "is а" между классами. Если есть сомнения, что между классами существует естественная связь "является", следует использовать не наследование, а композицию.

Унифицированный язык моделирования

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

Во второй части книги рассматривается написание классов. Навыки связыва­ ния в одно целое данных и операций и реализация классов как серверов и клиен­ тов являются решающими для создания высококачественных программ на C+ + .

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

Впрограмме на C++ трудно визуально представить связи между классами, реализованными программой. Более того, когда программа достаточно усложня­ ется, становится трудно визуально представить связи между классами, которые предполагается реализовать.

Цели использования UML

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

I 612

Чость ill # Прогром1^л11ро8ан1^е с агрвтроваытет и насдедование1У1

Вам надо знать, какие объекты реального мира должны представляться в про­ грамме C + + как классы и какие связи между ними должны быть реализованы

впрограмме C + + как связи между классами. В этом состоит задача объектноориентированного проектирования, в котором для описания классов и их связей

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

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

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

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

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

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

Объектно-ориентированный анализ и объектно-ориентированное проектиро­ вание используют другой подход. Объекты описываются в них как комбинация данных, поведения и связей с другими программными компонентами. Как можно видеть, описание данных и поведения в UML играют скорее базовую роль. Нота­ ция UML в большей степени концентрируется на связях классов и объектов.

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

Глава 14 • Выбор между наследованием и композицией

|

613

|

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

 

средства для описания элементов данных и функций-членов. Языки не дают про­

 

граммистам собственные средства для описания взаимосвязей программных ком­

 

понентов.

 

 

 

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

 

Следовательно, программисты описывают эти связи. Они используют следуюндие

 

методики: элементы данных с типами, определенными пользователем, или элемен­

 

ты данных, которые являются указателями или ссылками на другие объекты.

 

Основная задача любой нотации при проектировании, включая UML, состоит

 

в том, чтобы помочь разработчикам описать связи объектов. Когда программа

 

пишется на С4- + , именно программист принимает решение о связи объектов

 

друг с другом. Если возможно реализовать программу таким образом, чтобы она

 

могла выполнять то, что она должна делать, замечательно. Используя нотацию

 

UML, разработчики могут сравнить различные проектные решения и выбрать

 

такие взаимосвязи, которые (1) достаточны для выполнения задания, и (2) упро-

 

идают связи между программными объектами.

 

 

 

В UML объединены три различные нотации для построения графических моде­

 

лей компьютерных систем. Эти модели помогают разработчикам проанализиро­

 

вать требования к системе. Они обычно описывают функциональные возможности

 

системы, интерфейс пользователя, интерфейсы с другими системами, производи­

 

тельность и надежность. Графические методы пытаются представить эти требова­

 

ния в виде связей между системными компонентами.

 

 

 

Одна система нотации была разработана Гради Бучем (Grady Booch) и его

 

компанией Rational Software Corporation. Она включала несколько представлений

 

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

 

объектов на диаграммах Буча имели неправильную форму "облака" и их трудно

 

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

 

моделирование требует поддержки компьютерных инструментальных средств. Ин­

 

струментальное средство Rational Rose, разработанное его компанией, является

 

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

 

С созданием UML Rational Rose была модифицирована для поддержки нотации

 

UML.

 

 

 

Другая нотация была разработана Джеймсом Рамбо (James Rumbaugh) и его

 

коллегами в General Electric. Она получила название Object Modeling Technique

 

(ОМТ). В дополнение к объектной модели, которая описывает связи между объек­

 

тами в системе, нотация ОМТ также включала две другие модели: динамическую

 

и функциональную. Хотя эти две модели были не совсем объектно-ориентирован­

 

ными, они представляли адаптацию двух, хорошо известных методик

анализа

 

и проектирования: диаграмм переходов состояний и диаграмм потоков данных.

 

Этот синтез сгладил переход к объектно-ориентированному подходу для разработ­

 

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

 

ятно, это основная причина того, что ОМТ стала новым стандартным подходом.

 

Третья нотация разработана в Швеции Иваром Якобсоном (Ivar Jacobson)

 

и его компанией Objective Systems. Он представил на рынке свою нотацию под

 

названиями Object-Oriented Software Engineering (OOSE) и Objectory. Эта нота­

 

ция включает так называемые варианты использования ("use cases"), описываю-

 

и|ие взаимодействия между системой и такими внешними действуюндими лицами,

 

как системный оператор или другие системы.

 

 

 

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

614 Часть lil * Программирование с агрегированием и наследованием

Однако недостатки традиционного подхода к разработке систем были настоль­ ко серьезными, что отрасли требовалось нечто, обещающее улучшение по сравне­ нию с существующим состоянием дел. Какой подход выбрать? В дополнение к нотациям, разработанным Бучем, Рамбо и Якобсоном, существовали нотации, описанные Шлеер (Shlaer) и Меллором (Mellor), Йордоном (Yourdon) и Коадом (Coad), Коулменом (Coleman) и др. Фактически это очень длинный список. Все нотации похожи и все они являются вариациями, или расширениями, диаграмм "сушд4ость-связь" для проектирования баз данных, созданных П. Ченом (Р. Chen) в 1976 г.

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

внотациях, чем в методологии, они были незначительными.

В1995 г. Буч, Рамбо и Якобсон, "трое друзей", как их назвали, решили создать унифицированную нотацию, которая стала бы доминирующим языком моделиро­ вания. UML является результатом их сотрудничества. Они написали книги, опи­ сывающие язык UML и способы его использования. Инструментальное средство Rational Rose обеспечивает полную поддержку нотации UML.

Недостаток состоит в том, что нотация UML объединяет различные идеи и, следовательно, сложна. Описывающие ее книги объемны. Однако, когда проекти­ ровщик принимает неверное решение в отношении моделирования, нет такого компилятора, который указал бы на ошибку (в отличие от компилятора C++, помогающего выявлять сделанные ошибки). Именно поэтому процесс овладения языком UML более медленный, чем, например, процесс изучения C++.

Хорошая новость заключается в том, что вам совсем не требуется быть специа­ листом в UML. В этой книге описываются основы UML, которых достаточно для обсуждения связей объектов в программах C++.

Основы UML: нотация обозначений для классов

Объекты UML рассматриваются как экземпляры классов, а классы являются описаниями типов объектов. Класс описывает атрибуты и поведение объекта одного типа. Главным источником классов, которые требуется включить в свою модель UML, является анализ понятий и сущностей области приложения. Для бизнес-приложения классами в модели будут, например Клиент, Предмет, По­ ставка, Требование, Счет и т. д. Для системы реального времени классы будут включать Датчик, Дисплей, Карточку, Клиента, Кнопку, Двигатель и Замок.

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

А)

Имя

В) 1

Точка

С) Прямоугольник

 

Атрибуты

1

х: int

thickness: int

 

Операции

 

у: int

pt1,pt2: Point

 

 

operator = ()

move()

 

 

 

 

 

 

set()

pointInO

move() geto

Рис. 14.3. Примеры на языке

UML общего гиаблона класса

и двух конкретных

классов

Глава 14 • Выбор ivieMAy наследованием и ко1\лпозицией

[ 615 ^

элементами данных или полями, а операции — функциями-членами или метода­ ми. На рис. 14.3(a) представлен общий вид класса в UML. На рис. 14.3(b) пока­ зан пример класса Point с атрибутами х и у, операциями set(), get() и move()

иоператором присваивания. На рис. 14.3(c) представлен пример класса Rectangle

сатрибутами толщины pt1 и pt2 и операциями move() и pointIn().

Можно заметить, что UML позволяет указать тип атрибутов либо как прими­ тивный (встроенный) тип, либо как библиотечный класс (например, строка), либо как один из классов, определенных в приложении (например, Point). Язык UML разрешает определить намного больше, чем просто имя и тип атрибута. Можно указать, является ли атрибут статическим (атрибут области действия — класс), множество разрешенных значений (если атрибут является перечислимым типом), начальное значение атрибута (если имеется), или даже видимость атрибута (обще­ доступную, закрытую или защищенную). Это необязательно, поскольку часто, особенно в начале процесса анализа и проектирования, разработчики могут не знать точно типы и другие свойства атрибута. Они уточняются позже, во время итеративного процесса проектирования или даже на этапе программирования.

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

Описание класса в UML может содержать столько деталей, сколько позволяют спецификации класса, записанные на С+-Ь. Уделим внимание конкретным дета­ лям нотации UML для атрибутов и операций. Более того, чтобы упростить работу с диаграммами классов, разработчики не выбирают операции класса и обсуждают связи, используя в классах только два раздела — для имени класса и для атрибу­ тов. Для более сложных диаграмм (а большинство диаграмм классов являются сложными) можно опустить часть с атрибутами и представить класс прямоуголь­ ником только с именем класса. Это наиболее удобный способ для обсуждения связей классов.

Основы UML: нотация для связей

 

 

Сущности реального мира в предметной

области могут быть связаны друг

 

с другом. Эти связи представляются на диаграмме классов. Для обозначения связи

 

между классами используется термин "ассоциация". Такая связь означает, что

 

один объект знает о существовании другого объекта, соединен с другим объектом,

 

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

 

одного класса имеется объект другого класса. Это очень важное определение.

 

Ассоциации, будучи реализованными, используются для доступа к некоторому

 

объекту через другие объекты в программе C+ + .

 

 

 

 

На рис. 14.4 представлены примеры ассоциаций. На рис. 14.4(a) показано, что

 

ка>кдый объект Circle ассоциируется с объектом Cylinder, но характер ассоциации

А)

Круг

Цилиндр

В)

 

Person

владеет

принадлежит

Саг

 

 

 

 

 

 

 

 

 

 

 

транспортное

 

 

 

 

 

 

 

владелец

 

регистрируется

 

 

 

 

 

 

 

 

 

средство

 

 

С)

 

 

принадлежит

Саг

 

 

документ

регистрирует

 

 

 

 

 

 

 

 

 

Регои! 1

владеет

 

 

 

 

 

Registration

 

 

 

 

 

 

 

 

подтвержу]1ается

влад(злец

трансп<эртное

 

 

 

 

 

 

 

 

 

 

ср вдство

 

 

 

 

 

 

УДОСТОЕюряет

докуллент

 

 

 

 

 

 

 

 

 

Registration

 

 

 

 

 

 

 

 

 

Рис. 1 4 . 4 , Примеры ассоциаций классов UML

I 616 Часть III • Програмтирошаиив с агрегирование.

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

Ассоциации между объектами обычно являются двунаправленными: если объект Circle связан с объектом Cylinder, то объект Cylinder связан с объектом Circle. Нотация UML позволяет выразить дополнительную информацию о связях между объектами, указывая имена ассоциаций и назначая роли объектам, ассо­ циированным друг с другом.

На рис. 14.4(b) показано, что объект Person может быть связан с объектом Саг, а объект Саг с объектом Person и объектом Registration. У каждой ассоциа­ ции есть две метки, одна для прохождения ассоциации в одном направлении, другая для прохождения ее в противоположном направлении. Чтобы избежать путаницы в отношении направления, в котором текст метки соединяет объекты, можно поместить рядом с ним небольшие стрелочки.

На рис. 14.4(a) не удалось найти подходяш,ее название для ассоциации межлу объектами Circle и Cylinder. Они связаны между собой. На рис. 14.4(b) показа­ но, что объект Person владеет объектом Саг, а объект Саг принадлежит объекту Person. К тому же демонстрируется, что объект Саг регистрируется объектом Registration, а объект Registration регистрирует объект Саг. Определяется роль каждого объекта в связи. Объект Person играет роль владельца, объект Саг — транспортного средства, а объект Registration — документа.

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

Часто сравнение различных имен связей в модели классов представляется подобным описанию теории относительности простыми терминами. Основная проб­ лема описания ассоциаций состоит в том, что любое решение является относитель­ ным. На рис. 14.4(c) ассоциации между классами Person, Car и Registration описываются с помош,ью различных связей. Другая возможность состоит в ассо­ циации каждого класса с двумя другими классами. Какой вариант лучше и почему? На этот вопрос нет точного ответа.

В C + + ассоциации можно реализовать ссылками, которые указывают из одно­ го объекта на другой ассоциированный объект. Другой популярной методикой реализации ассоциации в языке C + + является использование идентификатора объекта как атрибута другого класса. Например, класс Person может иметь атрибут, который идентифицирует объект Саг, ассоциированный с экземпляром Person.

Основы UML:

нотация для агрегации и обобщения

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

Нотация, используемая в UML для агрегации, такая же, как и для ассоциа­ ции — это связь между классами. Для указания агрегированного объекта на конце линии между связью и агрегированным объектом изображается незакра­ шенный ромб. Ромб можно присоединять только с одной стороны, а не с обеих.

 

 

Глава 14 • Выбор между наследованием и композицией

617

А)

Круг

< ^

Цилиндр

С)

Круг

D)

Счет

 

 

 

 

 

 

7\

 

5

 

В)

Цилиндр

 

Круг

 

Цилиндр

Сберегательный

Текущий

 

 

 

счет

счет

 

 

 

 

Рис. 14 . 5 . Примеры

совместно

используемой агрегации,

композиции и наследования

На рис. 14.5(a) показано, что объект Circle является частью объекта Cylinder. Фактически не закрашенный ромб указывает, что агрегация используется со­ вместно, и часть может присутствовать одновременно более чем в одной агрега­ ции. В композиции совместное использование не допускается. Нотация UML для композиции такая же, как для совместно используемой агрегации. Однако ромб, присоединенный к агрегации, является сплошной фигурой, а не пустой. На рис. 14,5(b) представлена нотация для композиции, в которой объект Circle явля­ ется частью объекта Cylinde г, но не может быть частью любого другого объекта.

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

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

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

Конкретный класс (подкласс) связи обобш^ения наследует все атрибуты, опера­ ции и ассоциации от обш,его класса (суперкласса) связи. Нотация UML для этой связи использует сплошную линию для указания связи между классами на диа­ грамме классов. Отличить в связи подкласс от суперкласса на диаграмме можно по небольшому незакрашенному треугольнику на конце линии связи, обозначаю- ш.ей суперкласс.

На рис. 14.5(c) показаны два класса Circle и Cylinder, связанные обобш,ением. В этом случае Circle интерпретируется как обобидение (суперкласс), а Cylinder — как специализация (подкласс).

Если класс используется как суперкласс для нескольких специализаций, то каждый класс представляется на диаграмме классов отдельно, а каждый класс специализации связывается с суперклассом отдельной связью с отдельным тре­ угольником, обозначаюш^им суперкласс. Обычно применяется только один тре­ угольник, указывающий на суперкласс, и с ним связывается ка>едый подкласс. На рис. 14.5(d) представлен класс Account, который используется как обобш^ение, и два других класса, SavingsAccount и CheckingAccount, которые представляют различные специализации класса Account.

Рис. 14.6.
Примеры UML, показывающие мноэюествепностпь связей
Rectangle
<ЗН Rectangle
Sample
0...8
Sample

618 Часть l!N Программирование с агретрованием и наследованием

Если подкласс используется как обобщение для другого класса, этот другой класс становится его специализацией. Класс может быть наследником одного класса и применяться как базовый класс для другого класса. На диаграммах клас­ сов UML появляется древовидная иерархия наследования.

Основы UML: нотация для множественности

А) Point

В) Point

С) History

D)History

Большинство связей являются бинарными, т. е. два класса связываются между собой. В действительности это не так. Вспомним предшествуюндее обсуждение классов Person, Саг и Registration. Это тернарная связь: она включает объекты трех классов. Трудности, с которыми вы сталкивались во время обсуждения этой связи, возникали из-за того, что тернарную связь пытались представить как набор бинарных связей.

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

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

Поэтому большинство связей на диаграммах классов UML являются бинарны­ ми. Каждая связь соединяет два объекта двух различных классов — один объект на одном конце связи и другой объект на другом конце связи.

Иногда объект одного класса может быть связан более чем с одним объектом другого класса. Например, объект класса Supervisor может быть ассоциирован с несколькими объектами класса TeamMember. На диаграмме класса UML все еще будет одна связь между классами Supervisor и TeamMember, но потребуется исполь­ зовать дополнительную нотацию UML для указания множественности.

На рис. 14.6 представлен пример обозначения мно­ жественности на диаграммах классов. На рис. 14.6(a) показаны два класса (Point и Rectangle) в приложении, где каждый объект Rectangle ассоциируется только с двумя объектами Point. Нотация UML, которая при­ меняется к ассоциациям, может использоваться и для агрегаций. На рис. 14.6(b) показана связь между двумя классами. Point и Rectangle, которая интерпретируется скорее как агрегация, чем как общая ассоциация.

Обратите внимание, на рис. 14.3(c) в классе Rectangle у класса Point имеются два атрибута, pt1 и pt2. Это означает, что любой объект класса Rectangle ассо­ циируется точно с двумя объектами класса Point. Следовательно, связь между классами на рис. 14.6(a) или 14.6(bJ отображает ту же информацию анализа и проектирования, что и диаграмма классов. Некоторых специалистов огорчает эта избыточность, и они рекомен­ дуют использовать только один способ для представле­ ния данной информации.

Глава 14 • Выбор между наследованием и композицией

Гб19'1

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

Если в конец ассоциации или агрегации ничего не добавлено, значит, для функ­ ционирования связи требуется ровно один объект этого класса. На рис. 14.6(a) и 14.6(b) показано, что присутствие одного объекта класса Rectangle является обязательным.

Иногда связь между объектами не фиксируется, а ее множественность изменя­ ется во время выполнения. Например, класс History в главе 12 ассоциирован с классом Sample. Фактически связь является композицией: объект класса History содержит массив объектов класса Sample. Как можно видеть из листинга 14.2, в массиве отсутствуют действительные объекты Sample. Во время выполнения программы появляются выборки измерений и информация сохраняется в массиве до тех пор, пока завершится программа либо количество объектов Sample будет равно восьми.

UML позволяет представить этот вид переменной множественности, указывая диапазон ассоциированных значений. На рис. 14.6(c) приведен пример, в котором количество ассоциированных объектов может изменяться от нуля до восьми. Если количество объектов не может быть меньше одного, то диапазон начинается с 1, а не с О (например, 1...8).

Часто диапазоны объектов в связи являются искусственными. Почему в Sample не может быть больше восьми объектов? Потому что язык C+-h не допускает определение объекта без указания его длины. Число 8 подходит, как и любое другое число. В предметной области нет никаких указаний на то, что 8 лучше, чем 10, 20, 100 или любое иное число.

С концептуальной точки зрения количество выборок в истории не должно ограничиваться. По этой же причине реализация не должна заставлять проектировш^ика фиксировать конкретное число. В листинге 12.7 показан класс-контей­ нер с динамически выделяемой памятью, который реализует эту концептуальную модель. На рис. 14.6(d) представлено обозначение неограниченной множествен­ ности.

Учебный пример: магазин проката

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

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

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

Чтобы сделать пример более интересным с точки зрения использования насле­ дования, добавлена следуюш^ая деталь: данные о фильмах сохраняются в файле с добавлением буквы, указываюш,ей категорию фильма ("f" — для художествен­ ных фильмов, "с" — для комедий, "h" — для фильмов ужасов). Когда данные

Соседние файлы в предмете Программирование на C++