Объектно-ориентированный язык программирования

Тип работы:
Курсовая
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Содержание

Введение

1. Понятие наследование

2. Базовый и производный классы

3. Конструкторы производного класса

4. Перегрузка функций

5. Иерархия классов

5.1 Абстрактный базовый класс

5.2 Конструкторы и функции

6. Наследование и графика

6.1 Общее и частное наследование

6.2 Уровни наследования

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

7. Конструкторы при множественном наследовании

8. Неопределенность при множественном наследовании

9. Включение: классы в классах

10. Роль наследования при разработке программ

Заключение

Список использованных источников

Введение

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

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

1. абстракции — формальное о качествах или свойствах предмета путем мысленного удаления некоторых частностей или материальных объектов;

2. инкапсуляции — механизма, связывающего вмести код и данные, которыми он манипулирует, и защищающего их от внешних помех и некорректного использования;

3. наследования — процесса, с помощью которого один объект приобретает свойства другого, т. е. поддерживается иерархической классификации;

4. полиморфизма — свойства, позволяющего использовать один и тот же интерфейс для общего класса действий.

Разработка объектно-ориентированных программ состоит из следующих последовательных работ:

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

· определение закрытых данных (данных состояния) для выбранных объектов;

· определение второстепенных объектов и их закрытых данных;

· определение иерархической системы классов, представляющих выбранные объекты;

· определение ключевых сообщений, которые должны обрабатывать объекты каждого класса;

· разработка последовательности выражений, которые позволяют решить поставленную задачу;

· разработка методов, обрабатывающих каждое сообщение;

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

· кодирование, отладка, компоновка и тестирование.

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

1. Понятие наследования

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

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

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

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

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

Рис. 1. Наследование

2. Базовый и производный классы

объект ориентированный язык программирование

Помните пример COUNTPP3 из главы 8 «Перегрузка операций»? Эта программа использует объект класса Counter как счетчик. Он мог быть инициализирован нулем или некоторым числом при использовании конструктора, увеличен методом operator++() и прочитан с помощью метода get_count ().

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

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

Во-вторых, в некоторых ситуациях мы просто не имеем доступа к исходному коду класса, например, если он распространяется как часть библиотеки классов.

Во избежание этих проблем мы можем использовать наследование для создания классов на базе Counter.

Программа начинается с описания класса Count, которое не изменилось с момента его первого появления в COUNTPP3 (с одним небольшим исключением, которое мы рассмотрим позднее).

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

Вслед за описанием класса Count в программе определен новый класс, CountDn. Он включает в себя новый метод operator--(), который уменьшает счетчик. В то же время CountDn наследует все возможности класса Counter: конструктор и методы.

В первой строке описания класса CountDn указывается, что он является производным классом от Counter.

class CountDn: public Counter

Для этого используется знак двоеточия (не путать с двойным двоеточием, которое является операцией, использующейся для определения области действия), за ним следует ключевое слово public и имя базового класса Counter. Таким образом, мы установили отношение между классами. Другими словами, эта строка говорит нам о том, что CountDn является наследником класса Counter (значение ключевого слова public мы рассмотрим позднее).

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

Вспомним, что при наследовании базовый класс остается неизменным. В функции main () программы COUNTEN мы определили объект типа Counter: Counter c2; // Объект базового класса

Такие объекты ведут себя так, как если бы класс CountDn не существовал.

Заметим также, что наследование не работает в обратном направлении. Базовому классу и его объектам недоступны производные классы. В нашем случае это означает, что объекты класса Counter, такие, как c2, не могут использовать метод operator--() класса CountDn. Если мы хотим иметь возможность уменьше- ния счетчика, то объект должен быть класса CountDn, а не класса Counter.

3. Конструкторы производного класса

Это потенциальная проблема в программе COUNTEN. Что будет, если мы захотим инициализировать значением объект класса CountDn? Сможем ли мы воспользоваться конструктором класса Counter с одним аргументом? Ответ будет отрицательным. Как мы видели в программе COUNTEN, компилятор будет использовать конструктор базового класса без аргументов. Мы должны написать новый конструктор для производного класса. Это показано в программе COUNTEN2:

// counten2. cpp

// конструкторы в производных классах

#include < iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Counter

{

protected: // заметьте, что тут не следует использовать private

unsigned int count; // счетчик

public:

Counter (): count () // конструктор без параметров

{ }

Counter (int c): count (c) // конструктор с одним параметром

{ }

unsigned int get_count () const // получение значения

{ return count; }

Counter operator++ () // оператор увеличения

{ return Counter (++count); }

};

///////////////////////////////////////////////////////////

class CountDn: public Counter

{

public:

CountDn (): Counter () // конструктор без параметров

{ }

CountDn (int c): Counter (c)// конструктор с одним параметром

{ }

CountDn operator-- () // оператор уменьшения

{ return CountDn (--count); }

};

///////////////////////////////////////////////////////////

int main ()

{

CountDn c1; // переменные класса CountDn

CountDn c2 (100);

cout < < «nc1 = «< < c1. get_count (); // выводим значения на экран

cout < < «nc2 = «< < c2. get_count ();

++c1; ++c1; ++c1; // увеличиваем c1

cout < < «nc1 = «< < c1. get_count (); // показываем результат

--c2; --c2; // уменьшаем c2

cout < < «c2 = «< < c2. get_count (); // показываем результат

CountDn c3 = --c2; // создаем переменную c3 на основе c2

cout < < «nc3 = «< < c3. get_count (); // показываем значение

cout < < endl;

return 0;

}

Программа использует два новых конструктора класса CountDn. Это конструктор без аргументов:

CountDn (): Counter ()

{ }

В этом конструкторе использована новая для нас возможность: имя функции, следующее за двоеточием. Она используется конструктором класса CountDn для вызова конструктора Counter () базового класса. Когда мы запишем в функции main ()

CountDn c1; компилятор создаст объект класса CountDn и вызовет конструктор класса CountDn для его инициализации. Конструктор в свою очередь вызовет конструктор класса Counter, который выполнит нужные действия. Конструктор CountDn () может выполнять и свои операции, кроме вызова другого конструктора, но в нашем случае это не требуется, поэтому пространство между скобками пусто.

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

В строке:

CountDn c2 (100);

функции main () используется конструктор класса CountDn с одним аргументом.

Этот конструктор вызывает соответствующий конструктор с одним аргументом из базового класса:

CountDn (int c): Counter (с) // параметр с передается в конструктор класса Counter{ }

Такая конструкция означает, что аргумент с будет передан от конструктора CountDn () в Counter (), где будет использован для инициализации объекта.

В функции main () после инициализации объектов c1 и c2 мы увеличиваем один из них и уменьшаем другой, а затем выводим результат. Конструктор с одним аргументом также используется в выражениях присваивания:

CountDn c3 = --c2;

4. Перегрузка функций

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

Рассмотрим пример, основанный на программе STAKARAY. Эта программа моделировала стек, простое устройство хранения

данных. Она позволяла помещать числа в стек, а затем извлекать их. При попытке отправить в стек слишком много чисел программа могла зависнуть по причине переполнения массива st [ ]. А при попытке извлечения количества чисел, большего, чем находится в стеке, результат мог быть бессмысленным, так как начинали считываться данные, расположенные в памяти за пределами массива.

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

В этой программе класс Stack тот же, что и в программе STAKARAY, за исключением того, что данные класса объявлены как protected.

5. Иерархия классов

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

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

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

Рис. 2. Диаграмма классов UML для примера EMPLOY

Пример нашей программы начинается с описания базового класса employee. Этот класс содержит фамилии служащих и их номера. Он порождает три новых класса: manager, scientist и laborer. Классы manager и scientist содержат добавочную информацию об этих категориях служащих. Это показано на рис. 2.

5.1 Абстрактный базовый класс

Заметим, что мы не определяли объекты класса employee. Мы использовали его как общий класс, единственной целью которого было стать базовым для производных классов.

Класс laborer выполняет те же функции, что и класс employee, так как не имеет никаких отличий от него. Может показаться, что класс laborer в данном случае лишний, но, создав его, мы подчеркнули, что все классы имеют один источник -- класс employee. Кроме того, если мы захотим в будущем модифицировать класс Laborer, то нам не потребуется делать изменения в классе employee.

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

5.2 Конструкторы и функции

Ни в базовом, ни в производном классах нет конструкторов, поэтому компилятор, наталкиваясь на определения типа

manager m1, m21;

использует конструктор, установленный по умолчанию для класса manager, вызывающий конструктор класса employee.

Методы getdata () и putdata () класса employee принимают от пользователя имя и номер и выводят их на дисплей. Методы getdata () и putdata () классов manager и scientist используют одноименные методы класса employee и проделывают свою работу. Метод getdata () класса manager запрашивает у пользователя должность и сумму взносов в гольф-клуб, a putdata () выводит эти значения. В классе scientist эти методы оперируют значением количества публикаций.

6. Наследование и графика

В программе CIRCLES главы 6 «Объекты и классы» мы видели программу, в которой класс был создан для вывода на экран кругов. Конечно, кроме кругов существует еще множество других фигур: квадраты, треугольники. Слово «фигура» является обобщением, не затрагивающим определенный тип. Используем это при создании программы более сложной, но более понятной, чем программа, которая рисует различные фигуры, не используя их общих характеристик.

В частности, мы создадим класс shape как базовый класс и три производных класса: circle, rect (для прямоугольников) и tria (для треугольников). Как и в других программах, мы используем здесь функции консольной графики. Обратитесь к приложению Д «Упрощенный вариант консольной графики», приложению В «Microsoft Visual C++» или приложению Г «Borland C++ Builder», чтобы понять, как встроить графические файлы в программу в вашем компиляторе.

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

Общие характеристики всех фигур, такие, как их расположение, цвет, стиль заполнения, размещены в классе shape. Отдельные фигуры имеют более определенные атрибуты. Например, круг имеет радиус, а прямоугольник имеет ширину и высоту. Метод draw () класса shape содержит в себе действия, определенные для каждой из фигур: устанавливает их цвет и стиль заполнения. Перегрузка метода draw () в классах circle, rect и tria отвечает за прорисовку характерной для каждой фигуры формы.

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

6.1 Общее и частное наследование

С++ предоставляет огромное количество способов для точного регулирования доступа к членам класса. Одним из таких способов является объявление производного класса. В наших примерах мы использовали объявление типа:

class manager: public employee

которое представлено в примере EMPLOY.

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

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

Как же решить вопрос о том, какой из спецификаторов использовать при наследовании? В большинстве случаев производный класс представляет собой улучшенную или более специализированную версию базового класса. Мы рассмотрели примеры таких производных классов (вспомните класс CountDn, добавляющий новую операцию к классу Counter, или класс manager, являющийся специализированной версией класса employee). В случае, когда объект производного класса предоставляет доступ как к общим методам базового класса, так и к более специализированным методам своего класса, имеет смысл воспользоваться общим наследованием.

Рис. 3. Частное и общее наследование

Однако в некоторых ситуациях производный класс создается для полной модификации действий базового класса, скрывая или изменяя первоначальный его вид. Например, представим, что мы создали отличный класс Array, представляющий собой массив, который обеспечивает защиту против обращения к элементам, лежащим за пределами массива. Затем предположим, что мы хотим использовать класс Array как базовый для класса Stack вместо использования обычного массива. Создавая производный класс Stack, мы не хотим, чтобы с его объектами можно было работать как с массивами, например использовать операцию [ ] для доступа к элементам данных. Работа с объектами класса Stack должна быть организована как со стеками, с использованием методов push () и pop (). Тогда мы маскируем класс Array под класс Stack. В этой ситуации частное наследование позволит нам скрыть все методы класса Array от доступа через объекты производного класса Stack.

6.2 Уровни наследования

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

class A

{ };

class В: public A

{ };

class C: public В

{ };

Здесь класс В является производным класса A, а класс C является производным класса B, Процесс может продолжаться бесконечно -- класс D может быть производным класса C и т. д.

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

Рис. 4. UML диаграмма классов программы EMPLOY2

Бригадир наблюдает за выполнением работ, контролируя группу рабочих, и отвечает за выполнение нормы выработки своей группой. Эффективность работы бригадира измеряется в процентах выполнения нормы выработки. Поле quotas класса foreman представляет собой этот процент.

Заметим, что иерархия классов отлична от схемы организации. На схеме организации показаны линии команд. Результатом иерархии классов является обобщение схожих характеристик. Чем более общим является класс, тем выше он находится в схеме. Таким образом, рабочий -- это более общая характеристика, чем бригадир, который имеет определенные обязанности. Поэтому класс Laborer показан над классом foreman в иерархии классов, хотя бригадир, скорее всего, имеет большую зарплату, чем рабочий.

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

Класс может быть производным не только от одного базового класса, а и от многих. Этот случай называется множественным наследованием. На рис. 5 показан случай, когда класс C является производным двух классов: A и В.

Рис. 5. Диаграмма классов UML при множественном наследовании

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

class A

{

};

class В

{

};

class C: public A, public В

{

};

Базовые классы класса C перечислены после двоеточия в строке описания класса и разделены запятыми.

Рассмотрим пример множественного наследования. Пусть нам для некоторых служащих необходимо указать их образование в программе EMPLOY. Теперь предположим, что в другой программе у нас существует класс student, в котором указывается образование каждого студента. Тогда вместо изменения класса employee мы воспользуемся данными класса student с помощью множественного наследования.

В классе student содержатся сведения о школе или университете, которые закончил студент, и об уровне полученного им образования. Эти данные хранятся в строковом формате. Методы getedu () и putedu () позволяют нам ввести данные о студенте и просмотреть их.

Информация об образовании нужна нам не для всех служащих. Предположим, что нам не нужны записи об образовании рабочих, а необходимы только записи об ученых и менеджерах. Поэтому мы модифицируем классы manager и scientist так, что они будут являться производными классов employee и student, как показано на рис. 6.

Рис. 6. Диаграмма классов UML программы EMPMULT

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

class student

{ };

class employee

{ };

class manager: public employee, private student

{ };

class scientist: private employee, private student

{ };

class laborer: public employee

{ };

Эти методы доступны классам manager и scientist, поскольку названные классы наследуются от класса student.

Рассмотрим небольшой пример работы программы EMPMULT:

Введите данные для менеджера 1

Введите фамилию: Иванов

Введите номер: 12

Введите должность: Вице-президент

Введите сумму взносов в гольф-клуб: 1 000 000

Ведите название школы или университета: СПбГУ

Введите полученную ученую степень: бакалавр

Введите данные для ученого 1

Введите фамилию: Петров

Введите номер: 764

Введите количество публикаций: 99

Введите название школы или университета: МГУ

Введите полученную ученую степень: кандидат наук

Введите данные для ученого 2

Введите фамилию: Сидоров

Введите номер: 845

Введите количество публикаций: 101

Введите название школы или университета: МГУ

Введите полученную ученую степень: доктор наук

Введите данные для рабочего 1

Введите фамилию: Строкин

Введите номер: 48 323

Как мы видим, программы EMPLOY и EMPLOY2 работают примерно одинаково.

7. Конструкторы при множественном наследовании

Программа EMPMULT не имеет конструкторов. Рассмотрим пример, показывающий, как работают конструкторы при множественном наследовании.

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

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

Мы используем класс Distance из предыдущего примера для хранения длины материала. Наконец, мы создали класс Lumber который является производным классов Туре и Distance.

Главной особенностью этой программы является использование конструкторов производного класса Lumber. Они вызывают подходящие конструкторы классов Туре и Distance.

Конструкторы без аргументов.

В классе Туре конструктор без аргументов выглядит следующим образом:

Туре ()

{ strcpy (dimensions, «N/A»): strcpy (grade, «N/A») }

Этот конструктор присваивает значениям полей dimensions и grade строку «N/A» (недоступно), поэтому при попытке вывести данные для объекта класса Lumber пользователь будет знать, что поля пусты.

Нам уже знаком конструктор без аргументов в классе Distance:

Distance (): feet (0), inches (0.0)

{ }

Конструктор без аргументов класса Lumber вызывает конструкторы обоих

классов -- Туре и Distance.

Lumber: Type (), distance (), quantity (0), price (0.0)

{ }

Имена конструкторов базового класса указаны после двоеточия и разделены запятыми. При вызове конструктора класса Lumber начинают работу конструкторы базовых классов Туре () и Distance (). При этом инициализируются переменные quantity и price.

Конструктор со многими аргументами.

Конструктор класса Туре с двумя аргументами выглядит следующим образом:

Туре (string di, string gr): dimensions (di), grade (gr)

{ }

Этот конструктор копирует строковые аргументы в поля класса dimensions и grade.

Конструктор класса Distance тот же, что и в предыдущей программе:

Distance (int ft, float in): feet (ft), inches (in)

{ }

В конструктор класса Lumber включены оба этих конструктора, которые

получают значения для аргументов. Кроме того, класс Lumber имеет и свои аргументы: количество материала и его цена. Таким образом, конструктор имеет шесть аргументов. Он вызывает два конструктора, которые имеют по два аргумента, а затем инициализирует два собственных поля.

8. Неопределенность при множественном наследовании

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

Классы В и C являются производными класса A, а класс D является производным классов В и C. Трудности начинаются, когда объект класса D пытается воспользоваться методом класса A. В этом примере объект objD использует метод func (). Однако классы В и C содержат в себе копии метода func (), унаследованные от класса A. Компилятор не может решить, какой из методов использовать, и сообщает об ошибке.

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

9. Включение: классы в классах

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

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

Включение называют взаимоотношением типа «имеет». Мы говорим, что библиотека имеет книги (в библиотеке есть книги) или накладная имеет строки (в накладной есть строки). Включение также называют взаимоотношением типа «часть целого»: книга является частью библиотеки.

В ООП включение появляется, когда один объект является атрибутом другого объекта. Рассмотрим случай, когда объект класса A является атрибутом класса B:

class A

{ };

class В

{

A objA;

};

В диаграммах UML включение считается специальным видом объединения.

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

Включение в диаграммах UML показывается так же, как объединение, за исключением того, что включение имеет стрелку в виде ромба. На рис. 7 показано, как это выглядит.

Рис. 7. Включение на диаграммах классов UML

Композиция: сложное включение.

Композиция -- это более сложная форма объединения. Она обладает всеми его свойствами, но имеет еще и такие, как:

¦ часть может принадлежать только одному целому;

¦ время жизни части то же, что и целого.

Машина имеет двери (помимо других деталей). Двери не могут принадлежать другой машине, они являются ее неотъемлемой частью. В комнате есть пол, потолок и стены. Если включение -- это взаимоотношение типа «имеет», то композиция -- это взаимоотношение типа «состоит из».

В диаграммах UML композиция показывается так же, как и включение, за исключением того, что ромб стрелки закрашен. Это представлено на рис. 8.

Рис. 8. Композиция в диаграммах классов UML

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

10. Роль наследования при разработке программ

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

Это связано не только с использованием классов, но и с использованием наследования. Рассмотрим его этапы.

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

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

Программисты В и Г затем пишут приложения, использующие класс DistSign.

Программист Б может не иметь доступа к исходному коду, реализующему класс Distance, а программисты В и Г могут не иметь исходного кода класса DistSign. Но в C++ существует возможность повторного использования кода, поэтому программист Б может использовать и дополнять работу программиста А, а В и Г могут использовать работы Б (и А).

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

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

Заключение

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

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

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

В диаграммах UML наследование называют обобщением. Оно представляется в виде треугольной стрелки, указывающей на базовый класс.

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

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

Список использованных источников

1. В. А. Скляров. Язык C++ и объектно-ориентированное программирование: Справочное издание. — Минск: Вышэйшая школа, 2007. 480с.

2. Лафоре Р. Объектно-ориентированное программирование в С++ (4-е издание, 2004 год). — 361 с.

3. Т. Сван. Освоение Borland C++ 4. 5: Пер. с англ. — Киев: Диалектика, 1996. 544с.

4. В. В. Подбельский. Язык C++: Учебное пособие. — Москва: Финансы и статистика, 199 560с.

5. Ирэ Пол. Объектно-ориентированное программирование с использованием C++: Пер. с англ. — Киев: НИИПФ ДиаСофт Лтд, 199 480с.

6. Уинер Р. Язык Турбо Си: Пер с англ. -М.: Мир, 2010. -384 с., ил.

ПоказатьСвернуть
Заполнить форму текущей работой