Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ВВЕДЕНИЕ В ОБЪЕКТНО Ориентированное программиро...docx
Скачиваний:
19
Добавлен:
29.08.2019
Размер:
1.01 Mб
Скачать

Наследование, агрегация

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

Решением такой проблемы «похожие, но различные» является наследование.

Наследование - это второй основополагающий принцип ООП. (Первый - инкапсуляция).

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

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

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

В Object Pascal объявление класса, который является потомком другого класса имеет вид;

TNewObject=class(TOldObject);

где TNewObject - класс потомок TOldObject - класс предок.

В Object Pascal все классы являются потомками класса TObject. Поэтому, если строить дочерний класс прямо от TObject, то в определении его можно не упоминать. Следующие выражения эквивалентны:

TMyObject=class(TObject);

TMyObject=class;

Первый вариант, хотя и более длинный, предпочтительнее - для устранения возможных неоднозначностей. Класс TObject будет рассмотрен отдельно.

Унаследованные от класса-предка поля и методы доступны в дочернем классе; если имеет место совпадение имен методов, то говорят, что они перекрываются. Кроме этого новый класс может продекларировать новые поля, методы и свойства. Но класс не может удалить то, что было продекларировано в классе-предке.

По тому, какие действия происходят при вызове, методы делятся на три группы:

1 – статические,

2 - виртуальные и динамические,

3 - перегружаемые.

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

Статические методы, а также любые поля в объектах-потомках ведут себя одинаково: можно без ограничений перекрывать старые имена и при этом изменять тип методов. Код нового статического метода полностью перекрывает (заменяет собой) код старого метода. Доступ к перекрытым полям класса-предка из класса-потомка невозможен. Для доступа к перекрытым методам класса-предка из класса-потомка используется зарезервированное слово inherited.

TOb1 = class

i : Extended;

Procedure SetData(AValue:Extended) ;

end;

T0b2 = class (TOb1)

i:Integer;

procedure SetData(AValue:integer);

. . .

end;

procedure TOb1.SetData;

begin

i:=0.1;

end;

procedure T0b2.SetData;

begin

i:=1;

inherited SetData(0.99);

end;

В этом примере разные методы с одинаковым именем SetData присваивают значения разным полям с именем i. Прячем в классе потомке поле i непосредственно недоступно, хотя и доступно через соответствующие методы. Обратите внимание на то, что при распределении памяти под объект учитываются все поля всех классов предков и создаваемого класса, в том числе и имеющие перекрывающиеся имена. Рассмотрим вопрос совместимости переменных типа класс. Известно, что присвоение может быть выполнено только для переменных одного типа, за исключением того, что тип класса совместим с типами всех его предков. То есть допустимо присваивание переменной типа класс значение типа класс, являющийся непосредственным или отдаленным потомком данного класса. Например:

Type

TFigure = class(TObject);

TRectangle = class (TFigure) ;

TSquare = class (TRectangle);

Var

Fig:TFigure;

Переменной Fig могут быть присвоены значения типов TFigure, TRectangle, и TSquare. Вернемся к рассмотрению конструкторов класса

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

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

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

Все, сказанное про конструктор, справедливо и для деструктора. Только, так как порядок действий обратный, то и вызов деструктора предка производится не первым, а последним действием в деструкторе потомка. Например:

Множественное наследование, агрегация

Язык Object Pascal допускает только одиночное наследование, т.е. каждый класс может непосредственно наследовать только от одного класса. Язык С+ + и др. допускает так множественное наследование. В этом случае новый класс может наследовать сразу от родительских классов. При этом возникают две проблемы: конфликты имен между различными предками и повторное наследование. Первый случай - это когда в двух или большем числе предков определено поле или метод с одинаковым именем. В разных языках программирования этот конфликт решается по-разному. В С+ + этот конфликт должен быть явно разрешен вручную. Повторное наследование — это класс наследует двум классам, а они порознь наследуют одному и тому же четвертому. Получается структура наследования и надо решать, должен ли самый нижний класс получить одну или две копии самого верхнего? В С++ это остается на усмотрение программиста. При множественном часто используется прием создания классов-примесей. Идея примесей — можно комбинировать (смешивать) небольшие классы, чтобы строить классы с более сложным поведением, примесь ничем не отличается от класса, но их использование различно. Примесь не предназначена для порождения самостоятельно используемых экземпляров — она смешивается с другими I. Классы, сконструированные целиком из примесей, называют агрегатными. Существуют классы, но не поддерживающие наследование, например Visual Basic. Такие называют объектными, в отличие от объектно-ориентированных. Что же делать в этом случае, если хочется, чтобы объект мог использовать методы и свойства другого объекта. Ответ - использовать агрегацию, т.е. включить объект как поле. То же самое можно рекомендовать, если в языке с единичным наследованием нужно, чтобы новый класс объединял свойства нескольких классов.

Рассмотрим агрегацию в языке Object Pascal. Агрегация — это вхождение одного объекта в другой как поля. Причем, это поле будет не собственно объектом, а указателем на объект. Такой тип агрегации называют агрегацией по ссылке. В случае агрегации по ссылке важным является то, что конструкторы и деструкторы полей - объектов должны быть вызваны явно в конструкторах и деструкторах владеющего ими объекта. Например:

TForraX=class(TRect)

B1,b2:TButtonX;

E1:TExitX;

Constructor Create;

Destructor Destroy;

End;

Constructor TFormX.Create;

Begin

Inherited Create;

B1:=TButtonX.Create;

B2:=TButtonX.Create;

E1:=TEditX.Create;

End;

Destructor TFormX.Destroy;

Begin

B1.Destroy;

B2.Destroy;

E1.Destroy;

Inherited Destroy;

End;

В этом примере класс TFormX связан отношением наследования с классом TRect и агрегирует три объекта - два типа TButtonX и один типа TEditX. В описании конструктора и деструктора класса TFormX необходимо вызвать конструкторы и деструкторы как класса предка, так и агрегированных объектов, но формат вызова различен и происходящие при этом действия также различны. При вызове конструктора класса предка не происходит распределения динамической памяти. Под агрегируемые объекты память распределяется, возвращаемые указатели присваиваются соответствующим полям TFormX. Аналогично при вызове деструкторов.