Wykład XVIII
Obiektowy typ danych
Podstawy informatyki
Semestr II Transport
Złożoność
oprogramowania
jak pokazuje wykres
zależność między wielkością,
a złożonością programu nie
jest liniowa – stworzenie 2
razy większej aplikacji nie
zajmuje 2 razy więcej czasu,
lecz trwa dłużej
istnieje pewna graniczna
wielkość programu, po której
przekroczeniu człowiek
przestaje panować nad jego
złożonością
wielkość
zł
o
żo
n
o
ść
Zależność między wielkością
i złożonością programu
Pokonanie problemu
złożoności
Istnieje jakieś rozwiązanie problemu skoro istnieje
wiele bardzo złożonych i skomplikowanych
aplikacji, które całkiem nieźle działają.
Nie możemy pokonać złożoności problemu –
ponieważ użytkownicy żądają coraz lepszych
(bardziej złożonych aplikacji), ale możemy
budować aplikacje z małych części.
Tworzymy zatem stosunkowo małe moduły,
sprawdzamy je i testujemy, a gdy okażą się
niezawodne, łączymy je w większą całość.
Cykl życia aplikacji
Analiz
a
Projektowa
nie
Kodowa
nie
Testowan
ie
Eksploata
cja
Konserwa
cja
czas
E
ta
p
y
t
w
o
rz
e
n
ia
a
p
lik
a
cj
i
Analiza
definiowanie i zrozumienie problemu
określenie czy do rozwiązania problemu
niezbędny jest komputer
oszacowanie niezbędnych środków (wielkość
zespołu programistów, twórców dokumentacji,
ilość komputerów
Rezultatem analizy powinna być
specyfikacja systemu, zawierająca opis
wszystkich jego funkcji
Projektowanie
tworzymy szczegółowy projekt każdego
rozwiązania
opisujemy zależności między modułami
określamy kolejność wywoływania procedur
projektujemy wygląd formularzy i postać raportów
wszystkie elementy konsultujemy z
zamawiającym (zwykle wielokrotnie aż do
zaakceptowania przez niego naszych
propozycji)
Kodowanie
Stworzenie programu na bazie
projektu (jeśli jest on dobrze
wykonany to jest to program „pisze się
sam”)
Ta faza właściwie się nigdy nie kończy
(trwa przez cały czas życia aplikacji),
gdyż każda zmiana i poprawka w
programie będzie wymagała zmiany
kodu źródłowego programu
Testowanie
głównym celem testowania jest sprawdzenie,
czy stworzony przez nas produkt spełnia
określone wcześniej założenia. Każdą funkcję
powinniśmy skonfrontować z założeniami
testowanie odbywa się zwykle w 2 krokach:
– testowanie poszczególnych modułów (unit
testing) – sprawdzanie poprawności działania
modułów składających się na aplikację
– testowanie całości (integration testing) –
sprawdzenie czy moduły poprawnie współpracują
i komunikują się ze sobą
Eksploatacja,
konserwacja
konserwacja aplikacji to zwykle około 80%
czasu jaki jej poświęcamy
uzupełniamy program o nowe (drobne) funkcje
(„zachcianki klientów”)
usuwamy niezaplanowane funkcje (błędy)
programu
nadzorujemy eksploatację programu
na tym etapie zwraca się wysiłek włożony w
tworzenie założeń, specyfikacji, projektu, opisu
kodu źródłowego czy instrukcji obsługi
Projektowanie obiektowe
(OOD, OOP)
Wszystkie języki którymi posługują się ludzie opierają
się na dwóch podstawowych składnikach:
rzeczownikach (obiektach)
czasownikach (operacjach)
Jeśli aplikacja, która ma być modelem fragmentu
rzeczywistości, języki
programowania powinny być zbudowane w ten sam sposób.
OOD, OOP – (Object Oriented Development,
Object Oriented Programming –
Programowanie zorientowane obiektowo)
Filozofia programowania
obiektów
Filozofia obiektów jest (jak wszystkie genialne
pomysły) bardzo klarowny. W miejsce
dotychczasowego rozdziału programu na dwa
światy: danych i operującego na nich kodu –
dokonano wnikliwej analizy istniejących
między nimi związków (Te dwa światy stawały
się bowiem nierzadko dwiema „otchłaniami”, a
efektem tego rozdzielenia była coraz bardziej
uciążliwa praca projektantów i programistów).
postanowiono więc, że odtąd program
rozumiany będzie jako współpraca kilku (wielu)
mikroświatów.
Definicja obiektu
Obiekt to rzecz, którą
charakteryzuje pewien stan – zbiór
opisujących go wartości
Zachowanie obiektu zdefiniowane
jest za pomocą listy operacji które
obiekt może wykonać
Każdy obiekt jest członkiem pewnej
klasy obiektów
Kolejność tworzenia aplikacji
obiektowej
W projektowaniu obiektowym, każdy moduł powinien być
odpowiednikiem obiektu lub klasy obiektów z otaczającego
nas świata (w modelu obiektowym rzeczywistość jest
zbiorem współpracujących ze sobą obiektów)
Podczas budowy aplikacji obiektowej kolejność działań
powinna być następująca:
1. Zdefiniuj obiekty i ich atrybuty
2. Zidentyfikuj operacje związane z każdym
obiektem
3. Ustal jak obiekt będzie widziany przez inne
obiekty
4. Ustal zasady porozumiewania się obiektów
5. Zaimplementuj każdy obiekt
Obiekty
Jeśli identyfikacja obiektów będzie sprawiała
ci kłopoty to wypisz rzeczowniki pojawiające
się gdy precyzujesz zadania aplikacji
Przykład
Program obsługujący system ogrzewania
Obiekty: źródło ciepła, czujnik
temperatury, nagrzewnica...
Obiekty mogą składać się z innych obiektów
Operacje (metody)
Kolejnym krokiem jest definicja operacji
(zwanych przez nas metodami), które dany
obiekt wykonuje lub my wykonujemy na nim.
Przykład
– Termostat może być kalibrowany
– Czujnik temperatury pozwalać musi na
odczyt bieżącego wskazania
Ważna jest odpowiednia kolejność operacji
związanych z obiektem (samochód najpierw
zapalamy, a dopiero później wrzucamy bieg i
ruszamy)
Widoczność
Określa właściwe relacje pomiędzy
obiektami (które własności i metody są
dostępne dla innych obiektów)
Przykład
– W systemie ogrzewania termostat musi
widzieć obiekt czujnika temperatury i
umieć odczytać jego wskazania
– Czujnik temperatury nie musi zdawać
sobie sprawy, że coś takiego jak termostat
istnieje
Odwoływanie się do
obiektu
Określa jak obiekty widzą się nawzajem
(precyzyjny sposób i zasady współpracy)
Interfejs składa się ze zbioru reguł i
funkcji, na podstawie którego z naszego
obiektu mogą korzystać inne obiekty
Projektując interfejs decydujemy o
jakości i prawdziwie modularnym
charakterze aplikacji
Implementacja
Ostatnim krokiem jest zaprogramowanie
obiektu (stworzenie kodu źródłowego)
obsługującego metody (operacje
związane z obiektem)
Praca nad obiektem może być rozłożona
na etapy (nie musimy od razu
implementować wszystkich funkcji)
Stosując powyższe zasady można
stworzyć bardzo spójny i logicznie
poprawny system
Klasy, a obiekty
Nie należy utożsamiać pojęcia klasy i
obiektu.
Klasa to wzorzec, przepis
Obiekt to reprezentant klasy
Przykład
– Samochód to klasa obiektów
– Poszczególne spotykane pojazdy (stojący
przed nami Fiat 126p) to obiekty.
Enkapsulacja
Polega na ścisłym powiązaniu kodu oraz
danych służących temu samemu celowi,
poprzez zamknięcie ich w ramach
jednego bytu – typu obiektowego.
Końcowym wynikiem enkapsulacji są
stanowiące wyraźne części aplikacji
komponenty, które wielokroć pod
prostym w obsłudze interfejsem ukrywają
złożone działania obiektu
Dziedziczenie
Tworząc złożone aplikacje, nie sposób
nie zauważyć podobieństwa między
poszczególnymi fragmentami ich
kodu. Stąd pomysł, by przy
definiowaniu nowych typów
obiektowych nie zaczynać
każdorazowo tworzenia obiektu od
nowa, lecz wykorzystać cech obiektów
już istniejących.
Ilustracja zjawiska
dziedziczenia
Im dalej od korzenia, tym więcej konkretnych
cech rozważanego obiektu.
Lobo
Malinówka
Owoc
Jabłko
Melon
Arbuz Miodownica
Arbuz
bezpestkowy
Polimorfizm
Oznaczę tę cechę, którą można nazwać
„wielopostaciowością” (jest to zarazem
najbardziej złożone pojęcie związane z
programowaniem obiektowym)
Efekt wykonania określonego
fragmentu kodu związanego z
obiektem zależy od jego konkretnego
egzemplarza (instance)
Pojęcia związane z
obiektami
pole (field, data member) – stanowi daną
składową obiektu, na wzór pola rekordu
metoda (method, member function) – jest
procedurą lub funkcją działają na rzecz pól
obiektu
właściwość (property) – stanowi połączenie
koncepcji pola i metody i określa sposób dostępu
do pól i metod obiektu. Operowanie
właściwościami w miejsce operowania polami i
metodami pozwala uniezależnić sposób
korzystania z obiektu od jego szczegółów
implementacyjnych
Zastosowania
dziedziczenia
Dzięki temu mechanizmowi możemy tworzyć
nowe obiekt na podstawie już istniejących.
Nowe obiekty przejmują (dziedziczą) z
obiektów bazowych pola, metody i
właściwości, my zaś definiujemy tylko nowe,
dodatkowe składniki obiektu lub zmieniamy
stosownie do naszych potrzeb istniejące
cechy.
Dzięki temu mechanizmowi możemy
wykorzystać dotychczasowy dorobek i nie
tworzyć wszystkiego od podstaw.
Deklarowanie zmiennej
obiektowej
type
TJakisObiekt=class; lub
TJakisObiekt=class(TObiektPrzodka);
W pierwszym wypadku deklarujemy dziedziczenie cech z
obiektu TObject w drugim z obiektu TObiektPrzodka
Posiadając już zdefiniowany typ obiektowy, możemy
zdefiniować jego egzemplarz (instance)
var
JakisObiekt: TJakisObiekt;
Deklarując zmienną w postaci wyżej podanej, deklarujemy
jedynie wskaźnik do konkretnego egzemplarza obiektu.
Tworzenie obiektu
Stworzenie konkretnego egzemplarza obiektu (m. in.
przydzielenie pamięci, choć to nie wszystko) odbywa się za
pomocą jednego z konstruktorów (constructors)
obiektu. Każdy obiekt języka Object Pascal posiada
przynajmniej jeden konstruktor – o nazwie Create.
Zależnie od konkretnego typu, może on posiadać
dodatkowo określony zestaw parametrów.
Konstruktory w Delphi należy wywoływać jawnie
JakisObiekt:=TJakisObiekt.Create;
Zwróćmy uwagę na sposób wywołania konstruktora: jest on
wywoływany na rzecz określonego typu w przeciwieństwie
do innych metod które wywołuje się na rzecz konkretnych
egzemplarzy. Dzieje się tak dlatego, że w momencie
wywołania konstruktora nie istnieje jeszcze egzemplarz
obiektu.
Destrukcja obiektu
Po wykorzystaniu obiektu należy zwolnić zajętą przez
niego pamięć, wykorzystując uprzednio charakterystyczne
dla danego typu czynności kończące. Zadanie to wykonują
destruktory (destructors). Każdy obiekt w Object Pascalu
zawiera destruktor Destroy.
Teoretycznie możliwe jest aktywowanie destruktora na
rzecz konkretnego egzemplarza, który mamy zamiar
zniszczyć
JakisObiekt.Destroy;
nie jest to jednak zalecane, ponieważ próba powtórnej
destrukcji obiektu zakończy się błędem
Kłopotu tego unikniemy, jeśli zamiast destuktora
wywołamy metodę Free
JakisObiekt.Free;
Pola
Służą do przechowywania danych związanych z obiektem
Deklarujemy je i korzystamy z nich podobnie jak w rekordach
TJakisTyp=class
{...}
Pole1, Pole2: Boolean;
InnePole: String;
{...}
end;
Korzystając z pól odwołujemy się nie do klasy, a do konkretnej
instancji obiektu. Jeśli instancja została zadeklarowana
następująco: var JakisTyp: TJakisTyp to odwołanie do pola
może mieć postać:
JakisTyp.Pole1:=TRUE; lub with JakisTyp do
Pole1:=TRUE;
Metody
Metoda jest procedurą lub funkcją
działają na rzecz pól obiektu
Metody pobudzają obiekt do życia
(trudno to powiedzieć o polach, które
są najwyżej „pożywką” dla metod)
Przykładami metod są dopiero co
omawiane konstruktory i destruktory
Deklarowanie własnej
metody
Przebiega dwuetapowo:
Umieszczenie nagłówka metody wewnątrz definicji
typu obiektowego
type
TDyskoteka=class
Taniec: Boolean;
procedure ZatanczSambe;
end;
Konkretyzacja treści metody
procedure TDyskoteka.ZatanczSambe;
begin
Taniec:=TRUE;
end;
Korzystanie z metody
Odwołanie do metody obiektu podobnie jako
odwołanie do pola obiektu ma postać odwołania
kwalifikowanego
var
Makim: TDyskoteka;
{...}
Maxim.ZatanczSambe;
lub
with Maxim do ZatanczSambe;
Odwołania do pól i metod obiektu wewnątrz jego
metody nie mają jednak postaci kwalifikowanej, gdyż
treść metody jest zakresem (scope) ich widoczności
Typy metod
W Object Pascalu istnieją 4 typy metod obiektowych:
Przykład
TJakasKlasa=class
procedure Statyczna;
procedure Wirtualna; virtual;
procedure Dynamiczna; dynamic;
procedure Komunikacyjna(var M: TMessage);
message
wm_SomeMessage;
end;
statyczne
dynamiczne
wirtualne
zarządzające
komunikatami
Metody statyczne
Jest to domyślna postać metody
obiektowej
Jej adres jest znany już w czasie
kompilacji
Jej wykonanie jest bardzo efektywne
Nie daje możliwości wykorzystania
polimorfizmu (przedefiniowywania
metody)
Metody wirtualne
Zjawisko dziedziczenia wiąże się z możliwością
przedefiniowywania (overriding) metod. Oznacza to, że
metoda o tej samej nazwie może mieć różne działanie
dla typów macierzystego i pochodnego. Znając jej
nazwę nie można określić konkretnego adresu jeśli nie
znamy konkretnego egzemplarza obiektu (a właściwie
jego typu), dla którego metoda zostaje aktywowana.
Jednym z mechanizmów wykorzystywanych do realizacji
polimorfizmu są struktury zwane tablicami VMT
(virtual method tables) zawierająca adresy wszystkich
metod, niezależnie czy zostały one przedefiniowane czy
nie względem typu macierzystego. W związku z tym
mechanizmem metody wirtualne powodują pewien
stopień obciążenia pamięci, za to są niezwykle szybkie.
Metody dynamiczne
Koncepcyjnie nie różnią się od metod
wirtualnych, są jednak w mniejszym stopni
pamięciochłonne.
Dla każdego typu posiadającego metody
dynamiczne kompilator utrzymuje tablicę DMT
(Dynamic Method Table) zawierającą adresy
tylko tych metod, które zostały przedefiniowane
w stosunku do typu macierzystego.
Skutkuje to mniejszym obciążeniem pamięci,
lecz znacznie mniej efektywnym wykonaniem,
gdyż proces poszukiwania właściwego adresu
jest bardziej złożony niż w przypadku tablic VMT.
Metody zarządzające
komunikatami
Stanowią ukłon w stronę
„klasycznego” programowania w
Windows i służą do bezpośredniego,
niskopoziomowego zarządzania
wybranymi kategoriami
komunikatów systemowych
(messages)
Przedefiniowywanie metod
Metody deklarowane jako virtual i dynamic mogą być w
ramach typu pochodnego przedefiniowywane
(overriding).
Przedefiniowywana metoda musi być specyfikowana z
kwalifikatorem override.
Przykład
TJakisObiektPochodny=class(TJakisObiekt)
procedure Wirtualna; override;
procedure Dynamiczna; override;
end;
Użycie w miejsce override kwalifikatora virtual lub
dynamic spowodowałoby utworzenie zupełnie nowych
metod, bez związku z ich poprzedniczkami (pomimo
identycznych nazw)
Self, inherited
W treści metody przedefiniowana zmienna Self
symbolizuje egzemplarz obiektu, na rzecz którego metoda
została wywołana. Jest ona w sposób niewidoczny dla
programistów przekazywana jako dodatkowy parametr do
wszystkich wywoływanych metod obiektu
Istnieje możliwość odwoływania się wewnątrz
przedefiniowanej metody do jej przodka. Służy do tego
identyfikator inherited. Wskazuje on, że wywołanie
metody dotyczy klasy bazowej.
constructor TJakisObiekt.Create(AOwner: TObject);
begin
inherited Create(AOwner);
{...}
end;
Właściwości
Natura właściwości (properties) jest
ideologicznie bardziej zbliżona do pola, gdyż
jest ona częścią obiektu przechowującą wartość
określonego typu.
Od pola odróżnia ją jednak sposób
odczytywania i zmiany przechowywanej
wartości. W przypadku pola, np. nadanie mu
wartości następuje przez proste przypisanie, dla
właściwości sposób odczytu i nadania wartości
właściwości jest określany przez programistę.
Definiowanie
właściwości
TMojObiekt=class
JakasWartosc: Integer;
procedure SetJakasWartosc(AWartosc: Integer);
property Value:Integer read JakasWartosc write
SetJakasWartosc;
end;
procedure TMojObiekt.SetJakasWartosc(AWartosc: Integer);
begin
if JakasWartosc<>AWartosc then JakasWartosc:=AWartosc;
end;
Interpretacja definicji
właściwości
Definicja właściwości zawiera słowa kluczowe read i
write, określają sposób odwoływania się do wartości
właściwości.
Po słowie read występuje nazwa pola JakasWartosc
co oznacza, że odczytanie bieżącej wartości następuje
przez bezpośrednie odczytanie wartości tego pola
Po słowie write następuje jednak nazwa metody, co
oznacza, że nadawanie wartości właściwości odbywa
się na drodze wywołania metody SetJakasWartosc.
Oznacza to że równoważne są konstrukcje
– JakasWarrosc:=Liczba
– SetJakasWartosc(Liczba)
Cel stosowania
właściwości
Intencją mechanizmu właściwości jest
ukrycie przed użytkownikiem szczegółów
implementacyjnych:
jedna właściwość jest wygodniejsza do
zapamiętania niż dwie metody (lub pola)
realizacja właściwości może się zmieniać
wraz z rozwojem obiektu, nie pociągając
za sobą zmiany w sposobie korzystania z
obiektu
Widoczność elementów
obiektu
private – elementy opatrzone tą klauzulą widoczne są
jedynie wewnątrz modułu, w którym zdefiniowano dany
obiekt. Ma to na celu ukrycie tych pól, które pełnią rolę
pomocniczą;
protected – klauzura udostępnia wskazane elementy
obiektu jedynie metodą i właściwościom jego obiektów
pochodnych (descendants). Uniemożliwia to wykorzystanie
pewnych elementów obiektu do celów innych niż
definiowanie klas pochodnych;
public – powoduje pełną dostępność elementów obiektu w
każdym miejscu programu. Konstruktory i destruktory
zawsze są elementami publicznymi;
published – określa elementy stanowiące tzw. informację
czasu rzeczywistego (RTTI – RunTime Type Information) –
jest ona m. in. wyświetlana w Object Inspektorze.
Definiowanie klas
widoczności - przykład
TJakisObiekt=class
private
AZmiennaPrywatna: Integer;
protected
procedure AProtectedProcedure;
function ProtectMe: Byte;
public
constructor APublicConstructor;
destructor APublicKiller;
published
property Wlasciwosc read AZmiennaPrywatna write
AZmiennaPrywatna;
end;