Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



Реализация класса PeekbackStack с помощью закрытого наследования от IntArray работает, но необходимо ли это? Помогло ли нам наследование в данном случае? Нет.

Открытое наследование – это мощный механизм для поддержки отношения “ЯВЛЯЕТСЯ”. Однако реализация PeekbackStack по отношению к IntArray – пример отношения “СОДЕРЖИТ”. Класс PeekbackStack содержит класс IntArray как часть своей реализации. Отношение “СОДЕРЖИТ”, как правило, лучше поддерживается с помощью композиции, а не наследования. Для ее реализации надо один класс сделать членом другого. В нашем случае объект IntArray делается членом PeekbackStack. Вот реализация PeekbackStack на основе композиции:

class PeekbackStack {

private:

   const int static bos = -1;

 

public:

   explicit PeekbackStack( int size ):

   stack( size ), _top( bos ) {}

 

   bool empty() const { return _top == bos; }

   bool full() const { return _top == size()-1; }

   int top() const { return _top; }

 

   int pop() {

    if ( empty() )

         /* обработать ошибку */;

    return stack[ _top-- ];

   }

 

   void push( int value ) {

    if ( full() )

         /* обработать ошибку */;

    stack[ ++_top ] = value;

   }

   bool peekback( int index, int & value ) const;

 

private:

    int _top;

IntArray stack;

};

inline bool

PeekbackStack::

peekback( int index, int & value ) const

{

   if ( empty() )

   /* обработать ошибку */;

 

   if ( index < 0 || index > _top )

 {

  value = stack[ _top ];

  return false;

   }

 

   value = stack[ index ];

   return true;

}

Решая, следует ли использовать при проектировании класса с отношением “СОДЕРЖИТ” композицию или закрытое наследование, можно руководствоваться такими соображениями:

· если мы хотим заместить какие-либо виртуальные функции базового класса, то должны закрыто наследовать ему;

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

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

Открытие отдельных членов

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

is.size();

Разработчик способен оградить некоторые члены базового класса от эффектов неоткрытого наследования. Вот как, к примеру, открывается функция-член size() класса IntArray:

class PeekbackStack: private IntArray {

public:

// сохранить открытый уровень доступа

using IntArray:: size;

//...

};

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

template < class Type>

class PeekbackStack: private IntArray {

public:

using intArray:: size;

//...

 

protected:

using intArray:: size;

using intArray:: ia;

//...

};

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

На практике множественное наследование очень часто применяется для того, чтобы унаследовать открытый интерфейс одного класса и закрытую реализацию другого. Например, в библиотеку классов Booch Components включена следующая реализация растущей очереди Queue (см. также статью Майкла Вило (Michaeel Vilot) и Грейди Буча (Grady Booch) в [LIPPMAN96b]):

template < class item, class container >

class Unbounded_Queue:

private Simple_List< item >, // ð å à ë è ç à ö è ÿ

public Queue< item >      // è í ò å ð ô å é ñ

{... }

Защищенное наследование

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

// увы: при этом не ï î ä ä å ð æ è â à å ò ñ ÿ дальнейшее наследование

// PeekbackStack: все члены IntArray теперь закрыты

class Stack: private IntArray {... }

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

class PeekbackStack: public Stack {... };

класс Stack должен наследовать IntArray защищенно:

class Stack: protected IntArray {... };

Композиция объектов

Есть две формы композиции объектов:

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

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

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

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

Если ответ на первый вопрос положительный, то, как правило, лучше применить композицию по значению. (Как правило, но не всегда, поскольку с точки зрения эффективности включение больших объектов не оптимально, особенно когда они часто копируются. В таких случаях композиция по ссылке позволит обойтись без ненужных копирований, если применять при этом подсчет ссылок и технику, называемую копированием при записи. Увеличение эффективности, правда, достигается за счет усложнения управления объектом. Обсуждение этой техники не вошло в наш вводный курс; тем, кому это интересно, рекомендуем прочитать книгу [KOENIG97], главы 6 и 7.)

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

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

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

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

class ZooAnimal {

public:

//...

const Endangered* Endangered() const;

void addEndangered( Endangered* );

void removeEndangered();

//...

protected:

Endangered *_endangered;

//...

};

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

class DisplayManager {... };

class DisplayUNIX: public DisplayManager {... };

class DisplayPC: public DisplayManager {... };

Наш класс ZooAnimal не является разновидностью класса DisplayManager, но содержит экземпляр последнего посредством композиции, а не наследования. Возникает вопрос: использовать композицию по значению или по ссылке?

Композиция по значению не может представить объект DisplayManager, с помощью которого можно будет адресовать либо объект DisplayUNIX, либо объект DisplayPC. Только ссылка или указатель на объект DisplayManager позволят нам полиморфно манипулировать его подтипами. Иначе говоря, объектно-ориентированное программирование поддерживается только композицией по ссылке (подробнее см. [LIPPMAN96a].)

Теперь нужно решить, должен ли член класса ZooAnimal быть ссылкой или указателем на DisplayManager:

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

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

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

Конечно, маловероятно, что для каждого подобъекта ZooAnimal в нашем приложении будет нужен собственный подтип DisplayManager для отображения. Скорее всего мы ограничимся статическим членом в классе ZooAnimal, указывающим на объект DisplayManager.

Упражнение 18.6

Объясните, в каких случаях имеет место наследование типа, а в каких – наследование реализации:

(a) Queue: List         // очередь: список

(b) EncryptedString: String // зашифрованная строка: строка

(c) Gif: FileFormat

(d) Circle: Point       // окружность: точка

(e) Dqueue: Queue, List

(f) DrawableGeom: Geom, Canvas // рисуемая фигура: фигура, холст

Упражнение 18.7

Замените член IntArray в реализации PeekbackStack (см. раздел 18.3.1) на класс deque из стандартной библиотеки. Напишите небольшую программу для тестирования.

Упражнение 18.8

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


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 284; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.027 с.)
Главная | Случайная страница | Обратная связь