Typy pochodne 1
Wstęp
Często dogodnie jest wprowadzić nowy typ, który
jest podobny do istniejącego, niemniej jednak
różny
.
Niech T będzie pewnym typem. Możemy napisać:
type S is new T;
W takim przypadku mówimy, że S jest
typem
pochodnym
(
derived type
) typu T, który
nazywamy
typem macierzystym
(
parent type
).
Mówimy czasami, że S należy do tej samej
klasy
co typ T.
Typy pochodne 2
Jeżeli T jest typem rekordowym, to typ S jest też
typem rekordowym, a jego składowe mają te
same identyfikatory.
Zbiór wartości typu pochodnego
jest kopią
zbioru
wartości typu macierzystego. Oznacza to, że
są
to różne typy
i
nie można
wartości jednego
typu podstawiać do obiektów drugiego typu.
Konwersja jest jednak możliwa.
Zapis literałów i agregatów typu pochodnego jest
taki taki sam, oraz domyślne wyrażenia
początkowe typu, albo jego składowych są takie
same jak w przypadku typu macierzystego.
Typy pochodne 3
Operacje podstawowe
Definicja. Do
operacji podstawowych
typu
zaliczamy:
1. Zdefiniowane wstępnie operacje podstawienia,
zdefiniowana wstępnie relacja równości,
odpowiednie atrybuty.
2. W przypadku typu pochodnego, operacjami
podstawowymi są operacje podstawowe
odziedziczone po typie macierzystym, które
mogą być ponownie zdefiniowane.
3. W przypadku typu zadeklarowanego w pakiecie
definicyjnym, podprogramy zadeklarowane w
tym pakiecie, posiadające parametry formalne,
lub wynik tego typu.
Typy pochodne 4
W
artości typu wyliczeniowego są także operacjami
podstawowymi, ponieważ są traktowane jak
identyfikatory funkcji bezparametrowych o
wartościach typu wyliczeniowego.
Przykład.
W przypadku typu Boolean, literały False
i True są operacjami podstawowymi, ponieważ są
traktowane jakby były funkcjami, takimi jak:
function True return Boolean is
begin
return Boolean’Val(1);
end;
Typy pochodne 5
Ogólna idea jest taka, że typ pochodny posiada pewne
operacje podstawowe, dziedziczone po typie
macierzystym i można do zbioru tych operacji dodać
nowe operacje podstawowe.
Przykład.
Pakiet definicyjny Wektory_Na_Plaszczyznie.
Program Test_Wektory_Na_Plaszczyznie.
Operacje dziedziczone
mogą być zastąpione
przez nowe
operacje.
Rozumiemy przez to zastąpienie operacji dziedziczonych
przez
jawnie
zadeklarowane podprogramy o tych
samych identyfikatorach jak podprogramy należące do
zbioru operacji podstawowych typu macierzystego,
przy czym podprogramy zastępujące
muszą być
zadeklarowane w tym samym obszarze deklaracji, w
którym definiowany jest typ pochodny.
Typy pochodne 6
Deklarując typ pochodny
należy
przestrzegać dwóch
zasad:
1.
Nie można tworzyć typu pochodnego z typu
prywatnego przed podaniem pełnej deklaracji tego
typu.
2.
Jeżeli tworzymy typ pochodny w tym samym pakiecie
definicyjnym, w którym deklarujemy typ macierzysty,
to typ pochodny dziedziczy wszystkie operacje po typie
macierzystym i nie można dodać nowych operacji do
typu macierzystego po deklaracji typu pochodnego.
Mimo że, każdy typ pochodny jest różny, to dzięki
pokrewieństwu typów pochodnych, wyprowadzonych
od wspólnego przodka, wartość jednego typu
pochodnego można łatwo zamienić na wartość innego
typu powstałej klasy.
Typy pochodne 7
Przykład.
Niech będą dane deklaracje:
type Light is new Colour;
type Signal is new Colour;
type Flare is new Signal;
Typy te tworzą hierarchię typów, która zaczyna się
od typu Colour. Możemy swobodnie dokonywać
konwersji wartości tych typów. Na przykład
możemy pisać:
L : Light;
F : Flare;
...
F := Flare(L);
zamiast
F := Flare(Signal(Colour(L)));
Typy pochodne 8
Podstawową zaletą
wprowadzania typów
pochodnych jest unikanie mieszania obiektów,
koncepcyjnie należących do różnych typów.
Przykład.
Załóżmy, że chcemy liczyć jabłka i pomarańcze.
W tym celu możemy napisać
type Apples is new Integer;
type Oranges is new Integer;
...
No_Of_Apples : Apples;
No_Of_Oranges : Oranges;
Obydwa typy pochodzą od typu Integer, dzięki czemu
obydwa dziedziczą operację dodawania, co pozwala pisać
No_Of_Apples := No_Of_Apples + 1;
No_Of_Oranges := No_Of_Oranges + 1;
Typy pochodne 9
Nie wolno oczywiście pisać:
No_Of_Apples := No_Of_Oranges;
ale zamiast tego trzeba użyć konwersji
No_Of_Apples := Apples'(No_Of_Oranges);
Przypuśćmy, że dwie procedury obsługują sprzedaż obydwu
rodzajów owoców
procedure Sell (N : Apples);
procedure Sell (N : Oranges);
Możemy wywołać jedną z nich
Sell (N : No_Of_Oranges);
natomiast wywołanie
Sell (6);
jest niejednoznaczne i w celu usunięcia tej niejednoznaczności
trzeba pisać
Sell (Oranges'(6));
Typy pochodne 10
Jeżeli podprogram jest dziedziczony, to w
rzeczywistości
nie jest tworzony nowy
podprogram
.
Wywołanie podprogramu dziedziczonego jest
wywołaniem podprogramu macierzystego, przy
czym parametry rodzajów in i in out są niejawnie
konwertowane na typ macierzysty tuż
przed
wywołaniem, a parametry rodzajów in out i out są
konwertowane niejawnie zaraz
po
wywołaniu
podprogramu.
Pisząc
My_Apples + Your_Apples
mamy
Apples(Integer(My_Apples) + Integer(Your_Apples))
Typy pochodne 11
Zajmijmy się teraz ograniczeniami. Możemy tworzyć typy
pochodne ograniczone.
Przykład.
type Probability is new Float range 0.0..1.0;
Jest to równoważne dwóm deklaracjom:
type Anonim is new Float;
subtype Probability is Anonim range 0.0..1.0;
Oznacza to, że podtyp Probability jest podtypem
ograniczonym anonimowego typu pochodnego. Zauważmy,
że zbiór wartości typu pochodnego jest kopią zbioru
wartości typu macierzystego Float. Operacje "+" , ">" i
inne, działają w całym nieograniczonym zbiorze wartości.
Typy pochodne 12
Przykład.
Niech
P : Probability;
Można napisać
P > 2.0
Mimo, że
nie można
podstawić wartości 2.0 do zmiennej P.
Podane wyrażenie jest zawsze nieprawdziwe, chyba że
zmienna P nie jest zainicjowana odpowiednio i przez
przypadek ma złą wartość.
Rozpatrzmy teraz ograniczenia występujące w przypadku
dziedziczonych podprogramów.
Podprogram odziedziczony
jest podprogramem macierzystym
w którym wszystkie egzemplarze (
instances
) typu
macierzystego
są wymienione
na typ pochodny.
Podtypy są wymienione na równoważne podtypy z odpowiednimi
ograniczeniami, a domyślne wyrażenia inicjujące są
konwertowane przez dodanie konwersji typów.
Dowolny parametr, albo wynik innego typu pozostaje
niezmieniony.
Typy pochodne 13
Przykład.
Niech
type T is ...;
subtype S is T range L..R;
function F (X : T; Y : T := E; Z : Q) return S;
Przy czym E jest wyrażeniem inicjującym typu T, natomiast typ
Q nie należy do klasy T, a więc jest typem całkowicie
niezwiązanym.
Jeżeli napiszemy
type TT is new T;
to z tego wynika, że napisaliśmy też
subtype SS is TT range TT(L)..TT(P);
a nagłówek dziedziczonej funkcji F ma postać
function F (X : TT; Y : TT := TT(E); Z : Q)
return SS;
Typy pochodne 14
W nagłówku typ T został zastąpiony
przez
TT, podtyp S
przez
SS,
dokonana została
konwersja wyrażenia
E na wartość typu TT,
natomiast typ Q
został niezmieniony
. Warto zauważyć, że
identyfikatory parametrów formalnych
zostały takie same
.
Typy pochodne są pewną alternatywą do typów prywatnych. Typy
pochodne
mają zaletę
dziedziczenia literałów, ale często
mają wadę
dziedziczenia zbyt wielu rzeczy po typie
macierzystym.
Przykład.
Weźmy pod uwagę deklaracje
type Length is new Float;
type Area is new Float;
Wprowadzenie tych typów zabezpiecza przed mieszaniem
długości i powierzchni, ale dziedziczona jest możliwość
mnożenia dwóch egzemplarzy typu Length, w wyniku czego
dostaniemy wartość typu Length. Poza tym, dziedziczymy
wiele nieistotnych operacji jak np. potęgowanie.
Typy pochodne 15
Takie niepożądane operacje można, jeżeli trzeba, zastąpić
podprogramami abstrakcyjnymi
, takimi jak:
function "*" (Left, Right : Length) return
Length
is abstract;
Podprogram abstrakcyjny
nie ma treści
i
nie można go
wywołać
. Każda próba wywołania wykrywana jest jako błąd
podczas kompilacji.
Zalecenie
. Jeżeli jest wiele niepożądanych operacji
dziedziczonych po typie macierzystym, często lepiej użyć
typu prywatnego i zdefiniować te operacje których
potrzebujemy.
Zadanie
. Zadeklarować pakiet Metrics zawierający deklaracje
typów pochodnych Length i Area wraz z odpowiednimi
nagłówkami różnych operacji "*", "/", "**".