Лекция: Наследственная иерархия

 

Обобщение (наследование) – это такое отношение между классами, когда один класс повторяет структуру и поведение другого класса (одиночное наследование) или других классов (множественное наследование).

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

 

class Point{… }; //Точка

class Со1ог{… }; //Цвет

 

Мы можем определить Shape следующим образом:

 

еnum Kind { circle, triangle, square }; // перечисление: окружность,

// треугольник, квадрат

 

class Shape {

Kind k, //поле типа (какая фигура?)

Point center, // центр фигуры

Color col, //цвет фигуры

public:

void move (Point to); // переместить

void draw ( ); // нарисовать

void rotate (int); // повернуть

Point isCenter ( );// возвращает значение центра фигуры

};

 

«Поле типа» k необходимо, чтобы такие операции, как draw и rotate, могли определить, с каким видом фигуры они имеют дело. Функцию draw ( ) можно определить следующим образом:

 

void Shape::draw ( ) {

switch (k) {

case circle:... break; // нарисовать окружность

case triangle:... break; // нарисовать треугольник

case square:... // нарисовать квадрат

}

}

 

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

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

Сначала мы описываем класс, который определяет общие свойства всех фигур:

 

class Shape {

Point center;

Color col;

.. .

public:

void move (Point to) { center =to; /*...*/ draw ( ); };

virtual void draw ( ) = 0;

virtual void rotate (int angle)=0;

.. .

};

 

Описание virtual означает, что функция является виртуальной, т.е. может быть замещена в классе, производном от данного. Функция, интерфейс вызова которой мо­жет быть определен, а реализация – нет, объявляется чисто виртуальной, для чего используется синтаксис "= 0". Чисто виртуальная функция не имеет реализации в суперклассе и должна быть замещена в производном классе. Таким образом, виртуальные функции являются полиморфными.

Например, ре­ализации функций draw и rotate могут быть определены только для конкретных фигур, поэтому эти функции вообще не реализованы в классе Shape. Чисто виртуальные функции впервые реализуются в производном классе.

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

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

 

class Circle: public Shape {

int radius;

public:

void draw ( ){...};

void rotate (int) {};//функция ничего не делает

};

 

Класс Circle является производным от Shape, а класс Shape является базовым (родительским) для класса Circle. Альтернативная терминология называет Circle и Shape подклассом и суперклассом (или надклассом) соответственно.

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

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

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

Для этой цели описание класса разделяется на три части:

– открытую (public), видимую всем клиентам;

– защищенную (protected), видимую самому классу, его подклассам и друзь­ям (friend);

– закрытую (private), видимую только самому классу и его друзьям.

Таким образом, язык C++ позволяет достаточно гибко найти компромисс между принципами наследования и инкапсуляции.

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

Поведение суперклассов также наследуется. Например, с помощью операции move класса Shape можно переместить экземпляр класса Circle. При этом в производном классе допускается добавление новых и переопределение существующих мето­дов.

Например, опишем класс круг – SolidCircle – закрашенная окружность:

 

class SolidCircle: public Circle {

Color fillcol; // цвет заполнения

.. .

void draw ( ){ Circle:: draw ( );… };

};

 

Функция draw сначала вызывает аналогичную функцию родительского класса, которая рисует границу круга, а затем сама заполняет ее цветом.

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

 


еще рефераты
Еще работы по информатике