Абстракция PeekbackStack должна обеспечить доступ к элементам стека по принципу "последним пришел, первым ушел". Однако наличие дополнительного интерфейса IntArray не позволяет гарантировать такое поведение.

Проблема в том, что открытое наследование описывается как отношение "ЯВЛЯЕТСЯ". Но PeekbackStack не является разновидностью массива IntArray, а лишь включает его как часть своей реализации. Открытый интерфейс IntArray не должен входить в открытый интерфейс PeekbackStack.

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

В приведенном ранее определении PeekbackStack достаточно заменить слово public в списке базовых классов на private. Внутри же самого определения класса public и private следует оставить на своих местах:

class PeekbackStack : private IntArray { ... };

<p>18.3.1. Наследование и композиция</p>

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

<p>18.3.2. Открытие отдельных членов</p>

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

is.size();

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

class PeekbackStack : private IntArray {

public:

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

using IntArray::size;

// ...

};

Перейти на страницу:

Похожие книги