to get the file
Transkrypt
to get the file
Instrukcja Podstawy programowania 2 laboratoryjna 7 Temat: Obiektowość Przygotował: mgr inż. Tomasz Michno 1 Wstęp teoretyczny 1.1 Obiektowość w Turbo Pascalu Wszystkie programy pisane na laboratoriach były tworzone strukturalnie, tzn. program używał wielu zmiennych, funkcji i procedur, które tak naprawdę nie były ze sobą połączone. Dopiero przekazywanie zmiennych jako parametry oraz wywoływanie procedur i funkcji w odpowiedniej kolejności powodowało, że program stawał się „jedną całością”. Wprowadzenie rekordów pozwoliło na zebranie powiązanych ze sobą zmiennych w jednym miejscu, jednak mimo to kod nadal był oddzielony od danych, na których operował. Mimo prostoty programowanie strukturalne posiada wiele wad, m. in. w przypadku większych programów trudno jest zapanować nad zmiennymi, jak również programiści korzystając z funkcji i procedur mogą używać ich w niepoprawny sposób. Dodatkowo dochodzi jeszcze problem w przypadku gdy udostępniamy nasz kod innym osobom – programista posiada przez cały czas pełen dostęp do wszystkich zmiennych, nawet do tych których nie powinien zmieniać. Często trudno jest również zrozumieć kolejność oraz sposób korzystania ze zmiennych i podprogramów. Rozwiązaniem tych problemów jest programowanie obiektowe, które pozwala m.in. na powiązanie ze sobą zmiennych i procedur/funkcji oraz określenie do których z nich programiści będą posiadali pełny dostęp. Programowanie obiektowe w dużym stopniu jest zbliżone do tworzenia własnych typów danych za pomocą rekordów. Odpowiednikiem typu rekordowego (rekord zadeklarowany w sekcji type) jest klasa, która w przeciwieństwie do rekordu pozwala na deklarowanie procedur i funkcji w jej wnętrzu oraz określanie dostępu do poszczególnych jej elementów. Odpowiednikiem zmiennej o typie rekordu zadeklarowanego w sekcji type jest obiekt. Dzięki takiemu rozwiązaniu możliwe jest tworzenie komputerowych odpowiedników obiektów ze świata rzeczywistego (np. drzew, samochodów itp. ). Przykład 1. Rozpatrzmy przykład czajnika o maksymalnej pojemności 3 litrów, dla którego zadeklarujemy jedynie 2 procedury – wyświetlenia ilości wody w czajniku (ileWody) oraz dolania do niego wody (dolejWody, w tej procedurze powinno być sprawdzenie, czy nie przekroczono pojemności czajnika). Na początku napiszemy kod za pomocą programowania strukturalnego. Dla ułatwienia późniejszej rozbudowy programu czajnik zdefiniujemy jako rekord. Listing 1. Program w wersji strukturalnej program czajnikStrukturalnie; uses CRT; type TCzajnik = record iloscWody : real; end; var czajnik : Tczajnik; procedure dolejWody(var cz : TCzajnik; dolanoLitrow : real); begin cz.iloscWody := cz.iloscWody + dolanoLitrow; if( cz.iloscWody > 3) then begin cz.iloscWody:=3; writeln('Wlano za duzo wody. Wszystko powyzej 3 litrow sie wylalo!'); end; end; procedure ileWody(var cz : TCzajnik); begin writeln('W czajniku jest ', cz.iloscWody:2:2,' l'); end; BEGIN clrscr; czajnik.iloscWody:=0; ileWody(czajnik); dolejWody(czajnik, 5); ileWody(czajnik); readln; END. Poniżej znajduje się wynik działania powyższego programu. Jak widać realizuje on poprawnie wszystkie założenia. Wyobraźmy sobie teraz sytuację, w której udostępniamy komuś nasz kod. Osoba ta stwierdziła, że zamiast wywoływać procedurę dolania wody, sama zmieni wartość zmiennej czajnik.iloscWody. W takim przypadku możliwe jest dolanie dowolnej ilości wody, bez żadnego błędu podczas kompilacji, ani komunikatu o przepełnieniu czajnika: {jeśli dostaniemy się do zmiennej nic nas nie powstrzyma przed dolaniem nawet 1000 litrów wody} czajnik.iloscWody:=1000; ileWody(czajnik); Wynik działania powyższego kodu: O ile w tym przykładzie takie zachowanie jedynie spowoduje niespełnienie założeń, o tyle w rzeczywistych programach jest niedopuszczalne. Jeżeli założymy że pewne zmienne będą miały wartości tylko z określonego przedziału, może nastąpić sytuacja w której program będzie działał nieprawidłowo (np. przy obliczeniach matematycznych będzie generował nieprawidłowe wyniki) lub nawet zakończy swoje działanie błędem. Warto byłoby w jakiś sposób ograniczyć dostęp do zmiennej iloscWody, jednak w programowaniu strukturalnym nie jest to możliwe w prosty i czytelny sposób. Z pomocą w takim wypadku przychodzi programowanie obiektowe. Listing 2. Program w wersji obiektowej program czajnikObiektowo; uses CRT; type TCzajnik = object private iloscWody : real; public procedure dolejWody(dolanoLitrow : real); procedure ileWody; end; procedure TCzajnik.dolejWody(dolanoLitrow : real); begin iloscWody := iloscWody + dolanoLitrow; if( iloscWody > 3) then begin iloscWody:=3; writeln('Wlano za duzo wody. Wszystko powyzej 3 litrow sie wylalo!'); end; end; procedure TCzajnik.ileWody; begin writeln('W czajniku jest ', iloscWody:2:2,' l'); end; var czajnik : TCzajnik; BEGIN clrscr; czajnik.ileWody; czajnik.dolejWody(5); czajnik.ileWody; readln; END. Listing 2 zawiera wiele nowych elementów. Pierwszym z nich jest definicja klasy: TCzajnik = object private iloscWody : real; { Zmienna atrybut} public procedure dolejWody(dolanoLitrow : real); procedure ileWody; end; Jak już wspomniano, klasa może być traktowana w dużym uproszczeniu jako odpowiednik rekordu. Jej deklaracja wygląda bardzo podobnie do deklaracji rekordu – znajdują się w niej zmienne (nazywane w programowaniu obiektowym atrybutami klasy) oraz procedury/funkcje (nazywane metodami klasy). Słowo private oznacza, że wszystkie atrybuty i metody znajdujące się po nim będą niewidoczne poza klasą. Słowo public jest jego przeciwieństwem – pozwala na pełny dostęp z dowolnego miejsca. Jak można zauważyć, deklaracje procedur są pozbawione parametru, w którym przekazywana była zmienna przechowująca czajnik – nie jest to potrzebne w tym przypadku, ponieważ gdy procedury/funkcje znajdują się wewnątrz klasy, posiadają pełny dostęp do wszystkich jej atrybutów (z których można korzystać, tak jakby były zmiennymi globalnymi). Oprócz samej deklaracji procedur w ciele klasy, należy napisać ich implementację. Tworzy się ją w identyczny sposób, co dla zwykłych procedur/funkcji, z jedną różnicą – przed nazwą podporgramu należy podać nazwę klasy oraz kropkę: procedure TCzajnik.ileWody; begin writeln('W czajniku jest ', iloscWody:2:2,' l'); end; Po stworzeniu kodu klasy i jej metod, możemy przejść do deklaracji w sekcji var zmiennej, która będzie obiektem. W tym wypadku tworzy się ją identycznie jak inne zmienne własnych typów (np. rekordów). Należy również zwrócić uwagę, że obiektów można używać również jako atrybuty innych klas, rekordów, parametry funkcji itp. Po zadeklarowaniu obiektu, możemy już z niego korzystać w zwykły sposób – dostęp do atrybutów wygląda identycznie jak w rekordach, metody również wywołuje się w analogiczny sposób: obiekt.metoda(parametry), np.: czajnik.ileWody; czajnik.dolejWody(5); czajnik.ileWody; 1.2 Konstruktory i destruktory Konstruktor i destruktor jest czymś, co jeszcze bardziej odróżnia klasy od rekordów. Konstruktor to specjalna procedura wywoływana w celu zainicjalizowania obiektu, tzn. przypisania prawidłowych wartości początkowych atrybutów i wykonanie czynności, które muszą zostać zrobione dla każdego obiektu przed pierwszym użyciem. Podobnie jak zwykła procedura, konstruktor może być bezparametrowy, jak i może posiadać dowolną liczbę parametrów. Destruktor jest procedurą wywoływaną pod koniec życia obiektu – zaraz przed jego całkowitym usunięciem z pamięci. Jest przydatny np. w celu zwalniania pamięci utworzonej w konstruktorze lub innych metodach. W języku TurboPascal w celu odróżnienia konstruktorów i destruktorów od zwykłych procedur podczas ich deklaracji należy używać zamiast słowa kluczowego procedure słowa construktor (dla konstruktora) i destructor (dla destruktora). Przykład 2 program p; uses CRT; type Tpies = object private imie : string; public constructor init; constructor initParam(noweImie : string); destructor dest; procedure dajGlos; end; var pies, pies2 : Tpies; constructor TPies.init; begin writeln('Konstruktor bezparametrowy'); imie:='Szarik'; end; constructor TPies.initParam(noweImie : string); begin writeln('Konstruktor z parametrem'); imie:=noweImie; end; destructor TPies.dest; begin writeln('Destruktor'); end; procedure Tpies.dajGlos; begin writeln(imie,': Hau! Hau!'); end; begin clrscr; pies.init; {wywołanie konstruktora bezparametrowego} pies2.initParam('Azor'); {wywołanie konstruktora parametrowego} pies.dajGlos; pies2.dajGlos; pies.dest; pies2.dest; readln; end. Obiekty można również tworzyć dynamicznie, tak jak zwykłe typy danych. W takim wypadku konstruktory i destruktory wywołuje się w funkcjach new i dispose, np.: pies2:=new(WSKTpies, initParam('Azor')); dispose(pies2, dest); Przykład 3 program p; uses CRT; type Tpies = object private imie : string; public constructor init; constructor initParam(noweImie : string); destructor dest; procedure dajGlos; end; WSKTPies = ^TPies; var pies, pies2 : WSKTpies; constructor TPies.init; begin writeln('Konstruktor bezparametrowy'); imie:='Szarik'; end; constructor TPies.initParam(noweImie : string); begin writeln('Konstruktor z parametrem'); imie:=noweImie; end; destructor TPies.dest; begin writeln('Destruktor'); end; procedure Tpies.dajGlos; begin writeln(imie,': Hau! Hau!'); end; begin clrscr; pies:=new(WSKTpies, init); pies2:=new(WSKTpies, initParam('Azor')); pies^.dajGlos; pies2^.dajGlos; dispose(pies, dest); dispose(pies2, dest); readln; end. Konstruktory i destruktory pełnią bardzo ważną rolę przy dziedziczeniu i polimorfizmie, o którym będzie następna instrukcja. 2 Zadania 1. Napisz program, w którym stworzysz klasę reprezentującą konto bankowe. Obiekt powinien posiadać atrybuty: imię i nazwisko właściciela, numer konta i ilość pieniędzy na koncie oraz metody wpłać i wypłać. Przy wypłacaniu należy sprawdzać, czy nie został przekroczony limit debetowy (np. saldo konta może mieć minimum -1000 zł). Stwórz jeden obiekt konta bankowego i przetestuj kod, wykonując kilka operacji wpłat i wypłat. 2. Do programu dodaj konstruktor, w którym zainicjujesz obiekt – imię, nazwisko oraz ilość pieniędzy na koncie powinna być podawana przez parametr, natomiast numer konta powinien być wartością losową. 3. Do klasy konta bankowego dodaj metodę, która będzie służyła do przelewania pieniędzy między kontami (jako parametr podaj obiekt klasy konto bankowe oraz sumę do przelania). Stwórz 3 obiekty konta bankowego i przetestuj jej działanie.