Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатика
ИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханика
ОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторика
СоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансы
ХимияЧерчениеЭкологияЭкономикаЭлектроника

TransmitAmperes();

}.

Эта процедура сначала вызывает одноименную процедуру суперкласса TTelemetryDatas::transmit(). Та передаст заголовок пакета (intIdent и timStamp), после чего в подклассе передаются его собственные данные (напряжение и ток).

Определим экземпляры двух описанных выше классов:

TTelemetryDatas * objTelemetry;

TElectricalDatas * objElectrical;

и свободную подпрограмму:

void transmitFreshData (TTelemetryDatas * d; const TTime & t)

{

if (d -> currentTime() >= t) d -> transmit();

} .

Теперь выполним следующие два оператора:

transmitFreshData(objTelemetry, TTime(60));
transmitFreshData(objElectrical, TTime(120)); .

Что при этом произойдет? В первом операторе будет передан уже известный нам заголовок пакета. Во втором будет передан он же, плюс четыре числа в формате с плавающей точкой, содержащие результаты измерений электрических параметров. Почему это так? Ведь функция transmitFreshData ничего не знает о классе объекта, она просто выполняет d -> transmit()! Это и есть пример полиморфизма. Переменная d может обозначать объекты разных классов. У этих классов есть общий суперкласс и они, хотя и по разному, могут реагировать на одно и то же сообщение, каждый раз правильно понимая его смысл.

Методы и свободные подпрограммы, подобные transmitFreshData , принято называть полиморфными операциями. Традиционные типизированные языки типа Pascal, C основаны на той идее, что функции и процедуры, а, следовательно, и операнды должны иметь определенный тип. Это свойство называется мономорфизмом, то есть каждая переменная и каждое значение относятся к одному определенному типу. В противоположность мономорфизму полиморфизм допускает отнесение значений и переменных к нескольким типам.

При отсутствии полиморфизма код программы вынуждено содержит множество операторов выбора switch или if. Например, на языке C невозможно образовать иерархию классов телеметрических данных; вместо этого придется определить одну большую запись с вариантами, включающую все разновидности данных. Для выбора варианта нужно проверить метку, определяющую тип записи. На языке C операция TransmitFreshData может быть написана следующим образом:

const int Electrical = 1;
const int Propulsion = 2;
const int Spectrometer = 3;

void Transmit_Fresh_Data(TDatas Data, TTimes Time)
{

if (Data.Current_Time >= Time)
switch (Data.Kind)

{

case Electrical: Transmit_Electrical_Data(Data); break;
case
Propulsion: Transmit_Propulsion_Data(Data); break;

case Spectrometer: Transmit_ Spectrometer _Data(Data); break;

};

} .

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

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

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

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

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

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

Очевидно, одиночное наследование в данном случае не отражает реальности, так что придется прибегнуть к множественному. Если мы составим структуру классов, в которой конечные классы (листья) могут быть сгруппированы во множества по разным ортогональным признакам (как в нашем примере, где такими признаками были способность приносить дивиденды и возможность страховки) и эти множества перекрываются, то это служит признаком невозможности обойтись одной структурой наследования, в которой бы существовали какие-то промежуточные классы с нужным поведением. Мы можем исправить ситуацию, используя множественное наследование, чтобы соединить два нужных поведения там, где это необходимо. Приведем пример на языке С++, получившаяся структура классов показана на рис. 1.4[6]. На нем класс Security (ценные бумаги) наследует одновременно от классов InterestBearingItem (источник дивидендов) и Asset (имущество). Сходным образом, BankAccount (банковский счет) наследует сразу от трех классов: InsurableItem (страхуемое) и уже известным Asset и InterestBearingItem.

Вот как это выражается на C++. Сначала вводится (незаконченное в примере) описание базовых классов:

class TAsset...
class TInsurableItem...
class TInterestBearingItem...
.

Теперь промежуточные классы; каждый наследует от нескольких суперклассов:

class TBankAccount: public TAsset, public TInsurableItem, public TInterestBearingItem...
class TRealEstate: public TAsset, public TInsurableItem...
class TSecurity: public TAsset, public TInterestBearingItem...
.

Наконец, листья:

class TSavingsAccount: public TBankAccount...
class TCheckingAccount: public TBankAccount...
class
TStock : public TSecurity ...
class TBond : public TSecurity ... ,

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

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

Конфликт имен происходит, когда в двух или более суперклассах случайно оказывается элемент (переменная или метод) с одинаковым именем. Представьте себе, что как T Asset, так и T InsurableItem содержат атрибут presentValue, обозначающий текущую стоимость. Так как класс T RealEstate (движимое и недвижимое имущество) наследует обоим этим классам, как понимать наследование двух операций с одним и тем же именем? Это, на самом деле, главная беда множественного наследования: конфликт имен может ввести двусмысленность в поведение класса с несколькими предками.

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

Множественное наследование

 

 

Рис. 1.4

1.4.4. Агрегация. Отношение агрегации между классами имеет непосредственное отношение к агрегации между их экземплярами. Пример. Рассмотрим класс TTemperatureControllers:

class TTemperatureControllers

{

public:

TTemperatureControllers();

~TTemperatureControllers();

void process(TTemperatureRamp & objTemperatureRamp);

TMinute schedule(TTemperatureRamp & objTemperatureRamp);

private

Theaters * objHeater;

} .

Как явствует из рис. 1.5, класс TTemperatureControllers это, несомненно, целое, а экземпляр класса THeaters (нагреватель) - одна из его частей.

Агрегация

 

 

Рис. 1.5

 

Различают два вида агрегации: физическое и косвенное включение.

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

Агрегация не требует обязательного физического включения. Например, акционер владеет акциями, но они не являются его физической частью. Более того, время жизни этих объектов может быть совершенно различным, хотя концептуально отношение целого и части сохраняется и каждая акция входит в имущество своего акционера. Поэтому агрегация может быть очень косвенной. Например, объект класса TShareholders (акционер) может содержать ключ записи об этом акционере в базе данных акций. Это тоже агрегация без физического включения. «Лакмусовая бумажка» для выявления агрегации такова: если (и только если) налицо отношение «целое/часть» между объектами, их классы должны находиться в отношении агрегации друг с другом.


Отношение использования

Рис. 1.6

Решая, с чем вы имеете дело - с наследованием или агрегацией - будьте осторожны. Если вы не уверены, что налицо отношение общего и частного (is а), вместо наследования лучше применить агрегацию или что-нибудь еще.

1.4.5. Использование. Рассмотрим снова пример из раздела «Агрегация»:

class TTemperatureControllers

{

public:

TTemperatureControllers();

~TTemperatureControllers();

void process(TTemperatureRamp & objTemperatureRamp);

TMinute schedule(TTemperatureRamp & objTemperatureRamp);

private

Theaters * objHeater ;

} .

На рис. 1.6 приведен пример, в котором иллюстрируется связь между классами TemperatureControllers и TemperatureRamp, получившая название отношения использования.

Класс TTemperatureRamp упомянут как параметр метода process; это дает нам основания сказать, что класс TTemperatureControllers пользуется услугами класса TTemperatureRamp.

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

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

1.4.6. Инстанцирование. Инстанцирование является аналогом параметризованного абстрактного типа данных.

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

template <class Item> class TQueue

{
public:

TQueue();
TQueue(const TQueue<Item>&);
virtual ~Queue();
virtual TQueue<Item>& operator=(const TQueue<Item>&);
virtual int operator==(const TQueue<Item>&) const;
int operator!= (const TQueue<Item>&) const;
virtual void clear();
virtual void append(const Item&);
virtual void pop();
virtual void remove(int at);
virtual int length() const;
virtual int isEmpty() const;
virtual const Item& front() const;
virtual int location(const void*);

protected:
...
};

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

 

Инстанцирование

 

Рис. 1.7

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

TQueue<int> intQueue;
TQueue<DisplayItem*> itemQueue;

Объекты intQueue и itemQueue - это экземпляры совершенно различных классов, которые даже не имеют общего суперкласса. Тем не менее, они получены из одного параметризованного класса TQueue.

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

Отношения между параметризованным классом TQueue, его инстанцированием для класса TDisplayItem и экземпляром itemQueue показаны на рис. 1.7.

1.4.7. Метаклассы. Как было сказано, любой объект является экземпляром какого-либо класса. Что будет, если мы попробуем и с самими классами обращаться как с объектами? Для этого нам надо ответить на вопрос, что же такое класс класса? Ответ - это метакласс. Иными словами, метакласс - это класс, экземпляры которого суть классы. Метаклассы венчают объектную модель в чисто объектно-ориентированных языках. Классы доставляют программисту интерфейс для определения объектов. Если так, то желательно, чтобы и сами классы были объектами, так, чтобы ими можно было манипулировать, как всеми остальными описаниями.

Понятию «метакласс» в абстрактных типах данных соответствует понятие «тип типов» (раздел 4).

Хотя в C++ метаклассов нет, но есть средства поддержки и переменных класса, и операций метакласса. Конкретно, в C++ можно вводить специальные методы класса. Главной целью введения методов класса является получение возможности непосредственно работать с классами, а также - создавать такие инструменты разработчика, как броузеры классов и объектов.

Контрольные вопросы

1) Сформулируйте своими словами понятие «ассоциации».

2) Сформулируйте своими словами понятие «наследование».

3) Какие средства используются в C++ для описания отношения наследования?

4) Чем одиночное наследование отличается от множественного наследования?

5) Что такое одиночный полиморфизм?

6) Что такое множественный полиморфизм?

7) Сформулируйте своими словами понятие «агрегация».

8) Какие средства используются в C++ для описания отношения агрегации?

9) Сформулируйте своими словами понятие «использование».

10) Какие средства используются в C++ для описания отношения использования?

11) Сформулируйте своими словами понятие «инстанцирование».

12) Какие средства используются в C++ для описания отношения инстанцирования?

13) Сформулируйте своими словами понятие «метакласс».

14) Какие средства используются в C++ для реализации метакласса?

 


Дата добавления: 2015-10-29; просмотров: 119 | Нарушение авторских прав


Читайте в этой же книге: Отношения между объектами | Virtual float temperatureAt(int Minute); | Отношения между классами | I was a wandering sheep. | I did not love... | Protestantism and Capitalism: A Causal Connection? | The Working of Competitive Markets | The Impact of Smith on Policy | Causes of the Wealth of Nations | Determinants of the Wealth of a Nation |
<== предыдущая страница | следующая страница ==>
Double dubFuelCell2Amperes();| Glossary of Nadsat Language

mybiblioteka.su - 2015-2024 год. (0.019 сек.)