JĘzyki programowania obiektowego wiesław Porębski Ada95 Program Wykładu



Pobieranie 187.36 Kb.
Strona8/8
Data28.04.2016
Rozmiar187.36 Kb.
1   2   3   4   5   6   7   8

4.5.Typy abstrakcyjne


Podobnie, jak w innych językach obiektowych, hierarchia dziedziczenia w Adzie 95 może się zaczynać od typu abstrakcyjnego, to jest takiego, który ma pełną specyfikację, ale niepełną implementację.

Typy abstrakcyjne muszą być typami znakowanymi, ponieważ tylko takie typy mogą być rozszerzane. Dla typu abstrakcyjnego można deklarować abstrakcyjne podprogramy. Przykładowa deklaracja pakietu z typem abstrakcyjnym może mieć postać:

package Sets is

subtype El_Type is Natural; --podtyp typu Integer.

type Set is abstract tagged null record;

function Empty return Set is abstract;

function Union(Left, Right: Set) return Set is abstract;

function Intersection(Left, Right: Set) return Set is abstract;

procedure Take(Element:out El_Type;From:in out Set)is abstract;

end Sets;

Oczywiste jest, że tworząc typy pochodne od typu abstrakcyjnego, musimy podawać definicje wszystkich jego podprogramów; widać to choćby stąd, że funkcje Union i Intersection nie mogą zwrócić obiektu typu Set (typ Set nie ma wystąpień) – musi to być obiekt jednego z typów potomnych.


4.6.Hierarchie pakietów


W języku Ada 95 oprócz hierarchii dziedziczenia typów można budować hierarchie pakietów potomnych (child units). Jest to koncepcja bliska dziedziczeniu, ale różna od niego. Konceptualnie pakiet potomny jest częścią swojego pakietu rodzicielskiego, ale pakiet potomny może być kompilowany oddzielnie (po kompilacji swojego pakietu rodzicielskiego) bez rekompilacji , czy modyfikacji pakietu rodzicielskiego.

Ogólna postać deklaracji pakietu potomnego jest następująca:

nazwa_pakietu_ rodzicielskiego. nazwa_pakietu_potomnego;

Przykładowe deklaracje pakietów mogą mieć postać:

package A is ... end A; package A.B is ... end A.B; package A.C is ... end A.C;

package A.C.E is ... end A.C.E; package A.C.E.Y is ... end A.C.E.Y;

Pakiet potomny otrzymuje wszystkie atrybuty i operacje pakietu rodzicielskiego i może dodawać swoje własne. Ta cecha jest szczególnie użyteczna, jeżeli w pakiecie rodzicielskim (np. A) definiuje się prywatne typy znakowane. Wtedy część publiczna pakietu A jest dostępna dla części publicznej pakietu potomnego (np. A.B), a część prywatna A jest dostępna dla części prywatnej A.B.

Wymienioną własność wykorzystuje się często dla dekompozycji złożonego systemu na podsystemy. Na przykład zamiast deklarować trzy typy w pokazanym wcześniej pakiecie Accounts lepiej byłoby go podzielić na trzy: Accounts wprowadzający typ znakowany ACCOUNT, Accounts.Checking wprowadzający typ pochodny CHECKING_ACCOUNT i Accounts.Saving wprowadzający typ pochodny SAVINGS_ACCOUNT.


4.7.Polimorfizm statyczny i dynamiczny


Polimorfizm statyczny to termin używany (w kręgach związanych z językiem Ada) na określenie przeciążania operatorów, procedur i funkcji. Przy tym rodzaju polimorfizmu wszystkie wywołania operacji i rozróżnienie metod (method resolution) są wykonywane w fazie kompilacji. Polimorfizm dynamiczny oznacza, że proces ten zachodzi w fazie wykonania, a wiązanie operacji z metodą jest wiązaniem późnym. W języku Ada 95 dynamiczne dopasowanie metody do operacji (dispatch) jest możliwe tylko dla typów znakowanych i tylko dla operacji prymitywnych, którą to nazwą określa się operacje zdefiniowane w bezpośrednim zasięgu typu znakowanego. Wynika to stąd, że każdemu typowi znakowanemu towarzyszy tablica wskaźników do jego operacji prymitywnych (dispatch table), a każdy obiekt typu znakowanego zawiera znacznik („Tag”), który de facto jest adresem tej tablicy. Jeżeli więc w danym pakiecie zdefiniowano typ znakowany i typy od niego pochodne, to dopasowanie dynamiczne nie jest możliwe dla operacji, które zostały dodane w definicji typu pochodnego. Podobnie, jeżeli dany pakiet ma pakiety potomne, to dodane w nim operacje również nie są operacjami prymitywnymi.

Typy znakowane pozwalają tworzyć hierarchie powiązanych mechanizmem dziedziczenia typów pochodnych, nazywanych także rodzinami typów. W programowaniu obiektowym częsta jest sytuacja, gdy chcemy, aby pewna część programu operowała na tej hierarchii, ale bez decydowania a priori, który typ z hierarchii ma być przetwarzany w danej chwili. Ponieważ Ada jest językiem o typizacji silnej, potrzebny był mechanizm, dzieki któremu określone wystąpienie (obiekt) z dowolnego poziomu hierarchii może być przekazane jako parametr. Wprowadzony dla tego celu przez twórców języka Ada 95 mechanizm nazwano programowaniem klasowym. Każdy podprogram w programowaniu klasowym operuje na tzw. klasie wyprowadzeń. W języku Ada 95 klasą wyprowadzeń nazywa się zbiór typów, pochodnych od pewnego znakowanego typu bazowego T; elementy tego zbioru identyfikuje się za pomocą notacji T'Class (czytamy: "T tick class"), a T’Class nazywa się typem klasowym. Tak więc każdy typ pochodny od typu bazowego (bezpośrednio lub pośrednio) jest elementem tego zbioru, jak pokazano na rysunku poniżej.



Rys. 4-1. Klasy wyprowadzeń

Z rysunku odczytujemy, że typ klasowy Building’Class obejmuje typy:

Building, House, Block, Home, Flat oraz Cottage

zaś np. typ klasowy Block’Class zawiera typy: Block i Flat.

Użyteczność klasy wyprowadzeń dla osiągnięcia polimorfizmu pokażemy na następującym przykładzie. Załóżmy, że od typu bazowego Figure dziedziczą typy Rectangle i Circle, a Square jest typem pochodnym od Rectangle; typ Figure zawiera funkcję Area obliczającą pole figury, a typy pochodne zawierają redefinicje tej funkcji. Wówczas łatwo jest napisać procedurę (np. w pakiecie potomnym Figure.Dispatcher), której parametrem formalnym będzie dowolna z tych figur:

procedure Print_Area(F: in Figure'Class) is

begin


Put("Area = ");

Put(Area(F)); --wywołanie właściwej funkcji dla F

New_Line;

end Print_Area;

Jeżeli parametr aktualny F będzie np. typu Circle, to instrukcja Put(Area(F)); zostanie wykonana dla obiektu tego typu.

Zauważmy, że konstrukcja z klasą wyprowadzeń jest podobna do obligatoryjnej w C++ deklaracji dynamicznie wiązanej funkcji jako wirtualnej, za wyjątkiem tego, że tutaj klient (procedura Print_Area) musi jawnie wybierać pomiędzy wiązaniem statycznym a dynamicznym.


4.8.Typy dostępowe


W języku Ada 95 wprowadzono tak zwane typy dostępowe (access types). Wystąpienia (zmienne) tych typów mogą przechowywać wskazania lub odnośniki do obiektów innych typów. Konieczność wprowadzenia typów dostępowych wynika stąd, że bez nich byłoby prawie niemożliwe konstruowanie rekurencyjnych struktur danych, jak np. listy jedno- i dwukierunkowe, drzewa, grafy i macierze rzadkie. Cechą charakterystyczną takich struktur jest zdolność przechowywania zmiennej ilości informacji. Przykładem może być deklaracja elementu listy jednokierunkowej

type List_Node;

type List_Node_Access is access List_Node;

type List_Node is

record

Data: Integer;



Next: List_Node_Access;

end record;

gdzie type List_Node; jest niekompletną deklaracją typu, która pozwala uniknąć błędnego koła w definicji (typ List_Node zależy od definicji typu List_Node_Access, a typ List_Node_Access zależy od definicji List_Node).

Zmienne typu dostępowego mogą odwoływać się do pewnego tworzonego dynamicznie obiektu, albo mieć wartość null. Są one podobne do wskaźników w językach C lub C++. Jednakże Ada 95 nakłada na typy dostępowe pewne ograniczenia, które czynią je bardziej bezpiecznymi, niż wskaźniki C/C++. W szczególności:



  • zmienne dostępowe mogą jedynie zawierać wskazania na obiekty dynamiczne;

  • każda zmienna takiego typu jest domyślnie inicjowana na null;

  • nie dopuszcza się wykonywania działań arytmetycznych na tych zmiennych;

  • nie dopuszcza się konwersji zmiennych dostępowych do innych typów;

  • jeżeli zmienna typu dostępowego jest argumentem operacji składowej typu znakowanego, nie może przyjąć wartości null.

Dostęp do zawartości obiektu wskazywanego uzyskuje się dodając po nazwie zmiennej dostępowej kropkę i przyrostek all, jak pokazano w instrukcji (6) na rysunku 4-2.

Rys. 4-2. Operacje na listach
Instrukcja (6) kopiuje całą zawartość obiektu wskazywanego przez Current do obiektu wskazywanego przez Root. Natomiast instrukcja (7) zmienia wskazanie: zmienna Root zacznie od tej chwili wskazywać na węzeł Current. Zauważmy, że wykonanie instrukcji (7) wytworzyło niekorzystną sytuację, ponieważ węzeł typu List_Node, na który poprzednio wskazywała zmienna Root, stał się niedostępny. Powstał więc nieużytek w pamięci dynamicznej. Jeżeli kompilator Ady jest wyposażony w kolektor nieużytków (co nie zawsze ma miejsce), to pamięć ta zostanie odzyskana. Jeżeli nie, to należałoby przed instrukcją (7) umieścić wywołanie odpowiedniej procedury dealokacji. W języku Ada 95 istnieje procedura parametryzowana Unchecked_Deallocation

generic


type Object(<>) is limited private;

type Name is access Object;

procedure Unchecked_Deallocation(X: in out Name);

do której przekazuje się dwa parametry: typ i dostęp do tego typu. Jeżeli utworzymy wystąpienie tej procedury o nazwie „Free”:

procedure Free is new Unchecked_Deallocation(List_Node, List_Node_Access);

to możemy wywołać procedurę Free dla węzła Root

Free(Root);

Wykonanie Free(Root) spowoduje przypisanie do zmiennej Root wartości null i zwolnienie wskazywanej przez Root pamięci.

Zmienne dostępowe mogą pełnić rolę parametrów, przesyłanych do operacji definiowanych dla typów znakowanych. W takich przypadkach w języku Ada 95 stosuje się nowy pseudo-tryb access (oprócz in, out i in out), jak np. w deklaracji:



procedure Get(Agent: access Occupant; Direct_Object: access Occupant'Class);

Poprzedzenie drugiego parametru formalnego Occupant’Class słowem kluczowym access oznacza tutaj, że parametr wejściowy (Direct_Object) musi być typu Occupant lub dowolnym z jego typów pochodnych.


4.9.Zgłaszanie i obsługa wyjątków


Obsługa wyjątków była od początku integralną częścią języka. W języku Ada 95 wyjątek reprezentuje nieoczekiwane zdarzenie (błąd), które może spowodować zakończenie wykonywania programu lub jego nieprzewidziane zachowanie. Implementację obsługi wyjątków oparto na modelu z terminacją, który zakłada następującą sekwencję zdarzeń:

  • zgłoś wyjątek,

  • przekaż sterowanie do procedury jego obsługi,

  • obsłuż wyjątek i opuść jednostkę programową, w której zdarzył się błąd.

Przy tym wyjątek może być zgłaszany jawnie przez instrukcje kodu źródłowego, lub przez pewne nieoczekiwane zdarzenie podczas wykonywania programu.

Wyjątki deklaruje się w podprogramach jako zmienne typu exception, np. Singularity: exception; a zgłasza się (jawnie) w instrukcji raise: raise Singularity;. Jeżeli dany podprogram wywołuje inny, który zgłosił wyjątek, to środowisko wykonawcze opuszcza bieżącą sekwencję instrukcji (pomiędzy słowami kluczowymi begin i end) wywołanego podprogramu i zaczyna szukać odpowiedniej procedury obsługi. Jeżeli nie znajduje, to opuszcza wywołany podprogram, zwija jego stos i wraca do podprogramu wołającego. Jeżeli i tam nie znajdzie procedury obsługi, kończy wykonanie programu.

Niżej pokazano prosty przykład procedury, która próbuje otworzyć plik, a jeśli plik nie istnieje, tworzy go:

Procedure Open_Or_Create(File: in out File_Type;

Mode: File_Mode; Name: String) is

Begin


Open(File, Mode, Name);

exception

when Name_Error => Create(File, Mode, Name);

end Open_Or_Create;


4.10.Współbieżność


Już w swojej starszej wersji językAda był wyposażony w mechanizmy współbieżnego wykonywania fragmentów programu, nazywanych zadaniami (tasks). Ada 95 zapewnia współbieżne wykonywanie zadań na poziomie wątków (threads) programowych. Przypomnijmy, że wątki są jednostkami wykonania w obrębie jednego procesu; każdy wątek ma własny stos wykonania, ale współdzieli z procesem tę samą przestrzeń adresową. Zadanie Ady definiuje się jako specjalny typ; niżej pokazano fragmenty deklaracji i definicji.

package Threads is

task type Zadanie is

entry Start(Message: String; Count: Natural);

end Zadanie;

end Threads;


package body Threads is

task body Zadanie is

Maximum_Count: Natural;

R: String;

begin

accept Start(Message: String; Count: Natural) do



R := Message; --kopiuj dane rendezvous

Maximum_Count := Count; --do zmiennych lokalnych

end Start;

--Dalsze instrukcje

end Zadanie;

end Treads;


Instrukcja entry, nieco podobna do deklaracji procedury, deklaruje, jaki rodzaj żądania (komunikat) może być kierowany do danego zadania. Ciało zadania definiuje czynności, jakie będzie wykonywać zadanie po jego uruchomieniu (aktywacji). Instrukcja accept czeka na żądanie od innego zadania; jeżeli otrzyma odpowiednie parametry aktualne, zostanie uruchomiona (fragment kodu pomiędzy do i end Start), a zadanie, które przesłało komunikat, zostaje na ten czas zawieszone. Fragment procesu, w którym zadania komunikują się pomiędzy sobą, nosi nazwę rendezvous (spotkanie). W pokazanym przykładzie w trakcie spotkania następuje kopiowanie danych, przesłanych przez inne zadanie do zmiennych lokalnych. Po zakończeniu spotkania mogą być wykonywane obydwa zadania.

Ponieważ zadania są typami, tworzy się je i używa tak samo, jak zmienne innych typów, np.

Zadanie_1 : Zadanie;

Zadanie_2 : Zadanie;

Zadanie_1.Start("Hej, tu zadanie 1", 10);

Zadanie_2.Start("Hej, tu zadanie 2", 20);

Sterowanie dostępem do zasobów przydzielonych zadaniom odbywa się za pomocą tzw. typów ochronnych, których obiekty zapobiegają utracie spójności danych. Typ ochronny można traktować jako zaawansowaną formę „semafora” lub „monitora”. Zwykle zawiera on dane, do których dostęp jest kontrolowany przez pewien zbiór operacji. Przykładowa deklaracja i definicja takiego typu może mieć postać:

protected type Zasoby is

entry Zajmij;

procedure Zwolnij;

private

Zajety: Boolean := False;



end Zasoby;

protected body Zasoby is

entry Zajmij when not Zajety is

begin


Zajety := True;

end Zajmij;

procedure Zwolnij is

begin


Zajety := False;

end Zwolnij;

end Zasoby;
Składnia dla tworzenia i użycia wystąpienia typu ochronnego jest taka sama, jak dla innych typów:

Sterowanie: Zasoby;

Sterowanie.Zajmij;

--Instrukcje

Sterowanie.Zwolnij;

4.11.Podsumowanie


Język Ada, mimo swego wojskowego rodowodu, nadaje się do zastosowań naukowych, przemysłowych i handlowych. Ze względu na osiągalną niezawodność, Ada 95 jest wykorzystywana w implementacjach systemów informacyjnych dużej skali, systemów rozproszonych i w obliczeniach naukowych. Tym niemniej, nadal dominują zastosowania wojskowe. Jako charakterys­tyczne przykłady można podać wykorzystanie środowiska programowego Ady i zintegrowanej z nim metody projektowania HOOD (ang. Hierarchical Object-Oriented Design) w projekcie stacji kosmicznej European Space Agency (ESA) oraz w konstrukcji urządzeń ostrzegaw­czych obrony powietrznej.

Ada jest językiem o silnej typizacji statycznej. Typy Ady nie są klasami w sensie używanym w technologii obiektowej, ponieważ składniowo nie zapewniają hermetyzacji, chociaż zapewniają ukrywanie informacji. Można definiować i używać metody polimorficzne wykorzystując pojęcie klasy wyprowadzeń.

Ada zawiera dogodne mechanizmy dla tworzenia fragmentów oprogramowania, które mogą być wielokrotnie używane w różnych projektach. Można w tym celu wykorzystywać dziedziczenie, parametryzowane jednostki programowe (pakiety i/lub podprogramy) oraz pakiety potomne.

Oprócz ewidentnych zalet, Ada 95 nie jest wolna od wad. Zasadniczą wadą jest duża złożoność języka: Ada 95 dodała do skomplikowanych konstrukcji języka Ada 83 całkowicie nowy zbiór konstrukcji, z wielu potencjalnymi interakcjami zarówno pomiędzy nimi samymi, jak i z konstrukcjami starymi. Inna jest także, a przy tym bardziej złożona, interpretacja podstawowych pojęć, które zdecydowały o powodzeniu języków obiektowych. Jako przykład można wymienić co najmniej pięć dość zawikłanych koncepcji, z których każda pokrywa pewne aspekty klas:



  1. Pakiety, które są modułami, ale nie typami, mogą być parametryzowane i oferować coś podobnego do dziedziczenia w postaci pakietów potomnych.

  2. Znakowane typy rekordowe, które są typami, ale nie modułami i oferują pewną postać dziedziczenia, choć inaczej niż klasy nie pozwalają na syntaktyczną inkluzję metod w deklaracjach typu.

  3. Zadania, które są modułami, ale nie typami i nie pozwalają na dziedziczenie.

  4. Typy zadaniowe (task types), które są modułami i typami, ale nie mogą być parametryzowane (choć mogą być włączane do typów parametryzowanych) i nie mają dziedziczenia.

  5. Typy ochronne (protected types), które są typami i mogą zawierać metody.

Standard języka Ada 95 jest oficjalnie zdefiniowany w dokumencie Ada Language Reference Manual (http://www.adahome.com/rm95/). Publicznie dostępny jest kompilator GNAT (GNU Ada Translator) na platformy MS DOS, OS/2, Windows 95/NT i Linux (ftp ftp://cs.nyu.edu/pub/gnat/), oraz Free Aonix Ada 95 Compiler na platformy Windows 95/NT, SUN Solaris, HP-UX. Z biblioteki publicznej Aonix (ftp://ftp.aonix.com/pub/ada/public/pal) można także otrzymać Ada to Java Compiler. Kompilacja skrośna z Ady do Javy pozwala pisać aplety (aplet jest programem w języku Java, który jest uruchamiany automatycznie, gdy użytkownik WWW przegląda „stronę” WWW zawierającą ten program) w języku Ada 95. Istnieje też wiele kompilatorów komercyjnych i programów narzędziowych ułatwiających projektowanie systemów implementowanych w języku Ada.

Ada 95 zawiera szereg pakietów i pewne specjalne dyrektywy kompilatora (pragmy) dla łączenia fragmentów programów napisanych w C, C++, COBOL i Fortran z programami Ady. Dyrektywa Import „importuje” podprogram z innego języka do programu Ady, dyrektywa Export „eksportuje” podprogram Ady do innego języka, zaś dyrektywa Convention określa model pamięci w „obcym” języku dla implementacji danego typu.

Dostępne są także interfejsy języka Ada 95 do pewnych środowisk programowych, w tym: Win32 API, X11Ada, SAMeDL (interfejs do SQL) oraz narzędzia wiążące programy Ady z językiem IDL (Interface Definition Language) specyfikacji CORBA (Common Object Request Broker Architecture).


LITERATURA

  1. Pyle I.C. Ada. Wyd. Naukowo-Techniczne, Warszawa, 1986.

  2. Caverly P., Goldstein P. Introduction to Ada: A Top-Down Approach for Programmers. Wadsworth Inc., 1986.

  3. Texel P. Introductory Ada. Packages for Programming. Wadsworth Inc., 1986.

  4. Rosen J.P. What Orientation Should Ada Objects Take?. Communications of the ACM, vol. 35, No. 11(November 1992), str. 71-76.

  5. Riehle R. Inaugurating an Ada Column. JOOP, vol. 8/No. 4, pp.62-69, August 1995.

  6. Riehle R. Satisfying Software Requirements Extensions. JOOP, vol. 8/No. 5, pp.78-84, September 1995.

  7. Riehle R. Reuse through Generic Templates. JOOP, vol. 8/No. 7, pp.66-70, Nov-Dec 1995.

  8. Riehle R. Reliability: Does Language Matter?. JOOP, vol. 9/No. 1, pp.87-92, March-April 1996.

  9. Riehle R. Dynamic Polymorphism. JOOP, vol. 9/No. 3, pp.60-74, June 1996.

  10. Riehle R. Managing Runtime Faults. JOOP, vol. 9/No. 5, pp.73-77, Sept. 1996.

  11. Riehle R. Ada Pointers: Access to Information. JOOP, vol. 9/No. 6, pp.76-81, October 1996.

  12. Riehle R. Ada and the Notion of Class. JOOP, vol. 9/No. 7, pp.66-74, Nov/Dec 1996.

  13. Smyda J. Ada95. http://www.republica.pl/korcala/ada/

  14. Wheeler D.A. Ada 95 Lovelace Tutorial. http://www.adahome.com/Tutorials/Lovelace/ lovelace.html, 1998.

  15. Ada Reference Manual, v. 6.0. http://www.adahome.com/

  16. Johnston S. Ada-95: A guide for C and C++ programmers. http://www.ankh-morpork.com/.

  17. Huzar Z. i in. Ada 95. Wyd. Helion, 1998.

  18. ftp://ftp.cs.tu-berlin.de/pub/gnat/gnat3.13p/winnt/

  19. http://www.pwr.wroc.pl/POLITECHNIKA/oprogramowanie


1   2   3   4   5   6   7   8


©absta.pl 2016
wyślij wiadomość

    Strona główna