logo
Языки программирования

14.5. Объектно-ориентированное программирование на языке Ada 95

В языке Ada 83 наличие пакетной конструкции обеспечивает полную поддержку инкапсуляции, а наличие производных типов частично обеспе­чивает наследование. Полного наследования нет, потому что, когда вы произ­водите новый тип, то можете добавлять только новые операции, но не новые компоненты данных. Кроме того, единственный полиморфизм — это стати­ческий полиморфизм вариантных записей. В языке Ada 95 поддерживается полное наследование за счет того, что программисту дается возможность рас­ширить запись производного типа. Чтобы обозначить, что родительский тип записи пригоден для наследования, его нужно объявить как теговый (tagged) тип записи:

package Airplane_Package is

type Airplane_Data is tagged

record

ID:String(1..80);

Speed: Integer range 0..1000;

Altitude: Integer range 0..100;

end record;

end Airplane_Package;

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

Чтобы создать абстрактный тип данных, тип должен быть объявлен как приватный и полное объявление типа дано в закрытой части:

package Airplane_Package is

type Airplane_Data is tagged private;

procedure Set_ID(A: in out Airplane_Data; S: in String);

function Get_ID(A: Airplane_Data) return String;

procedure Set_Speed(A: in out Airplane_Data; I: in Integer);

function Get_Speed(A: Airplane_Data) return Integer;

procedure Set_Altitude(A: in out Airplane_Data; I: in Integer);

function Get_Altitude(A: Airplane_Data) return Integer;

private

type Airplane_Data is tagged

record

ID:String(1..80);

Speed: Integer range 0..1000;

Altitude: Integer range 0.. 100;

end record;

end Airplane_Package;

Подпрограммы, определенные внутри спецификации пакета, содержащей объявление тегового типа (наряду со стандартными операциями на типе), на­зываются примитивными операциями, или операциями-примитивами (primitive operations) и являются подпрограммами, которые наследуются. Наследование выполняется за счет расширения (extending) тегового типа:

with Airplane_Package; use Airplane_Package;

package SST_Package is

type SST_Data is new Airplane_Data with

record

Mach: Float;

end record;

procedure Set_Speed(A: in out SST_Data; I: iri Integer);

function Get_Speed(A: SST_Data) return Integer;

end SST_Package;

Значения этого производного типа являются копиями значений родительско­го типа Airplane_Data вместе с (with) дополнительным полем записи Mach. Операции, определенные для этого типа, являются копиями элементарных подпрограмм; эти операции могут быть замещены. Конечно, для производно­го типа могут быть объявлены другие самостоятельные подпрограммы.

В языке Ada нет специального синтаксиса для вызова подпрограмм-примитивов:

A: Airplane_Data;

Set_Speed(A, 100);

С точки зрения синтаксиса объект А — это обычный параметр; И по его типу компилятор может решить, какую именно подпрограмму вызвать. Параметр называется управляющим, Потому что он управляет тем, какую подпрограмму выбрать. Управляющий параметр не обязан быть первым параметром, и их мо­жет быть несколько (при условии, что все они того же типа). Сравните это с языком C++, который использует специальный синтаксис, чтобы вы-звать подпрограмму, объявленную в классе:

C++

Airplane_Data а;

a.set_speed(100);

Объект а является отличимым получателем (distinguished receiver) сообщения set_speed. Отличимый получатель является неявным параметром, в данном случае обозначающим, что скорость (speed) будет установлена (set) для объ­екта а.

Динамический полиморфизм

Перед обсуждением динамического полиморфизма в языке Ada 95 мы долж­ны коснуться различий в терминологии языка Ada и других объектно-ориен­тированных языков.

В языке C++ термин класс обозначает тип данных, который используется для создания экземпляров объектов этого типа. Язык Ada 95 продолжает ис­пользовать термины типы и объекты даже для теговых типов и объектов, которые известны в других языках как классы и экземпляры. Слово класс ис-| пользуется для обозначения набора всех типов, которые порождаются от об-|щего предка, в языке C++ мы их назвали семейством классов. Нижеследую­щее обсуждение лучше всего провести в правильной терминологии языка Ada 95; будьте внимательны и не перепутайте новое применение слова класс с его использованием в языке C++.

С каждым теговым типом Т связан тип, который обозначается как T'Class

и называется типом класса (class-wide type)". T'Class покрывает (covered) все

типы, производные от Т. Тип класса — это неограниченный тип, и объявить

объект этого типа, не задав ограничений, нельзя, подобно объявлению

неограниченного массива:

type Vector is array(lnteger range <>) of Float;

V1: Vector; -- Запрещено, нет ограничений

type Airplane_Data is tagged record . . . end record;

A1: Airplane_Data'Class: -- Запрещено, нет ограничений

Объект типа класса может быть объявлен, если задать начальное значение:

V2: Vector := (1 ..20=>0.0); -- Правильно, ограничен

Х2: Airplane_Data; -- Правильно, конкретный тип

ХЗ: SST_Data; -- Правильно, конкретный тип

А2: Airplane_Data'Class := Х2; -- Правильно, ограничен

A3: Airplane_Data'Class := ХЗ; --Правильно, ограничен

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

procedure P(S: String; С: in Airplane_Data'Class) is

Local_String: String := S;

Local_Airplane: Airplane_Data'Class := C;

Begin

end P;

Динамический полиморфизм имеет место, когда фактический параметр имеет тип класса, в то время как формальный параметр — конкретного типа, принадлежащего классу:

with Airplane_Package; use Airplane_Package;

with SST_Package; use SST_Package;

procedure Main is

procedure Proc(C: in out Airplane_Data'Class; I: in Integer) is

begin

Set_Speed(C, I); -- Какого типа С ??

end Proc;

A: Airplane_Data;

S: SST_Data;

begin -- Main

Proc(A, 500); -- Вызвать с Airplane_Data

Proc(S, 1000); -- Вызвать с SST_Data end Main:

Фактический параметр С в вызове Set_Speed имеет тип класса, но имеются две версии Set_Speed с формальным параметром либо родительского типа, ли­бо производного типа. Во время выполнения тип С будет изменяться от вызо­ва к вызову, поэтому динамическая диспетчеризация необходима, чтобы снять неоднозначность вызова.

Рисунок 14.6 поможет вам понять роль формальных и фактических парамет­ров в диспетчеризации. Вызов Set_Speed вверху рисунка делается с фактиче­ским параметром типа класса. Это означает, что только при вызове подпрограм­мы мы знаем, имеет ли фактический параметр тип Airplane_Data или SST_Data. Однако каждое обтъявление процедуры, показанное внизу рисунка, имеет фор­мальный параметр конкретного типа. Как показано стрелками, вызов должен быть отправлен в соответствии с типом фактического параметра.

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

Set_Speed(A, 500);

Set_Speed(S, 1000);

Точно так же, если формальный параметр имеет тип класса, то никакая диспет­черизация не нужна. Вызовы Ргос — это вызовы отдельной однозначной про-

цедуры; формальный параметр имеет тип класса, который соответствует фак­тическому параметру любого типа, относящегося к классу. Что касается рис. 14.7, то, если бы объявление Set_Speed было задано как:

procedure Set_Speed(A: in out Airplane'Class: I: in Integer);

то любой фактический параметр класса «вписался» бы в формальный пара­метр класса. Никакая диспетчеризация не нужна, потому что каждый раз вы­зывается одна и та же подпрограмма.

При ссылочном доступе указуемый объект так же может иметь CW-тип. Ука­затель при этом может указывать на любой объект, тип которого покрывается CW-типом, и диспетчеризация осуществляется просто раскрытием указателя:

type Class_Ptr is access Airplane_Data'Class;

Ptr: Class_Ptr := new Airplane_Data;

if (...) then Ptr := new SST_Data; end if;

Set_Speed(Ptr.all); -- На какой именно тип указывает Ptr??

Yandex.RTB R-A-252273-3
Yandex.RTB R-A-252273-4