dziedziczenie - cs.put.poznan.pl - dziedziczenie.pdf · tarka portier s relacja między typami...
TRANSCRIPT
Dziedziczenie
Sieci klas (dziedziczenia, typów)
Odwzorowanie modelu: abstrakcja generalizacji
Osoba
Związkowiec
Kierowca
Sekretarka
Portier
Prezes
Relacja między typami danych
Figura
jest (is a)
Kwadrat
obwód, pole, obróć, przesuń
obwód, pole, obróć, przesuń
Implementacja: dziedziczenie kodu i struktur danych
Osoba
dziedziczenie
Student
imię, nazwisko, wiek, płeć
uczelnia, rok_studiów, kierunek, nr_indeksu
Przykład sieci dziedziczenia
Osoba
nadklasa
specjalizacja
podklasa
Mężatka
generalizacja Relacja podtypu
AKO:a kind of (is a)
łańcuch
dziedziczenia
Panna
podklasa
nadklasa
Relacja podtypu
AKO
Kobieta
Nomenklatura
Klasa bazowa (Nadklasa): klasa, po której się dziedzi-czy
Klasa pochodna (Podklasa): klasa dziedzicząca
Generalizacja: abstrakcja polegająca na usunięciu bądź uogólnieniu pewnych cech z opisu klasy
Specjalizacja: dodanie lub uszczegółowienie pewnych cech klasy
Relacja podtypu – a kind of: określa zależność funk-cjonalną między klasą dziedziczoną i dziedziczącą; kla-sa dziedzicząca zawiera w sobie funkcjonalność klasy dziedziczonej. Relacja ta jest przechodnia.
Implementacja dziedziczenia
Osoba Nazwisko Płeć Adres Przeprowadź się
Kowalski Nazwisko: Kowalski
Płeć: Mężczyzna
Adres: Poznań, ul. Piwna 7
Pracownik Etat Awansuj Płaca Daj podwyżkę Zakład pracy Zmień pracę
Prezes Gabinet Sekretarka Zmień sekretarkę Samochód służbowy Zmień samochód
Morzy Nazwisko: Morzy
Płeć: Mężczyzna
Adres: Poznań, ul. Krucza 2
Etat: Portier
Płaca: 1200 zł
Zakład pracy: Hurtownia A&A
Tarzan Nazwisko: Tarzan
Płeć: Mężczyzna
Adres: Poznań, ul. Browarna 13
Etat: Prezes
Płaca: 12 000 zł
Zakład pracy: Hurtownia A&A
Gabinet: 100 m2
Sekretarka: 180 cm
Samochód służbowy: Volvo S80
Wsparcie dla jednoczesnej otwartości i stabilności
modułów programowych
Wymaganie otwartości modułów jest konsekwencją po-trzeby rozwoju i utrzymania systemów informatycznych.
Wymaganie stabilności modułów jest wynikiem potrzeby osiągnięcia stabilizacji eksploatowanych systemów in-formatycznych.
Równoczesne spełnienie tych dwóch wymagań jest bar-dzo kłopotliwe.
Moduły programowe powinny być jednocześnie otwarte i stabilne.
Moduły programowe powinny być otwarte na dalszy rozwój: dodanie nowej funkcjonalności lub zmianę im-plementacji.
Eksploatowane moduły powinny być stabilne. Modyfi-kowanie poprawnie działającego modułu może spo-wodować jego błędne działanie.
Konsekwencje braku stabilności modułu
Współdzielenie kodu przez modyfikację
Dany jest system informatyczny SI1 obejmujący moduły programowe A, B, C i D.
B A C D
Chcemy wykorzystać moduł A do budowy nowego sys-temu SI2 składającego się z modułów X, Y, Z i zaadop-towanego modułu A. Chcąc utrzymywać tylko jedną wersję modułu A, rozszerzamy funkcjonalność modułu A, tak by spełniał on jednocześnie wymagania systemów SI1 i SI2.
B’ A’
Z
C’ D’
Y
X
SI1
SI2 propagacja modyfikacji
Moduł A stracił stabilność. Jego modyfikacja do postaci A’ może spowodować niepoprawne działanie systemu SI1 i wymusić modyfikacje modułów B, C i D.
Rozwiązania tradycyjne Utrzymywanie wielu wersji modułów
Współdzielenie kodu przez tworzenie wersji
Dla zapewnienia stabilności modułu A, budowy systemu SI2 zostanie wykorzystana nowa wersja modułu A.
SI2
utrzymywanie ścieżki powiązania
B A C D
A’
Z Y
X SI1
Równoległe utrzymywanie wielu wersji modułów jest kłopotliwe. Pewne zmiany muszą być aplikowane nieza-leżnie na kilku wersjach modułów, co wiąże się z więk-szą pracochłonnością i jest potencjalną przyczyną błę-dów niespójności.
Zastosowanie dziedziczenia
Współdzielenie kodu przez dziedziczenie
Dziedziczenie jako mechanizm współdzielenia imple-mentacji modułów programowych wspiera równocześnie otwartość i stabilność oprogramowania.
B A C D
A’
Z Y
X SI1
SI1
Współdzielenie implementacji
Wspólny kod modułów programowych powiązanych związkiem dziedziczenia jest łatwiejszy i mniej praco-chłonny w utrzymaniu.
Dziedziczenie klas jest silnym mechanizmem umożliwiającym konstruowanie kodu wielokrot-nego użytku
Własności relacji dziedziczenia
Czym klasa pochodna może się różnic od klasy bazowej?
1. Dodawanie nowych cech: metod i zmiennych 2. Redefinicja cech (przesłanianie – ang. overriding)
Redeklaracja kowariantna metod
Redeklaracja kowariantna zmiennych 3. Implementacja cech abstrakcyjnych
CZWOROKĄT
FIGURA
WIELOKĄT
PROSTOKĄT
obwód*
obrót(int)*
obwód++
wierzchołki
obwód+
obrót(int)+
TRÓJKĄT
obwód++
wysokość
cecha – definicja nowej cechy cecha* – definicja cechy abstrakcyjnej cecha+ – implementacja cechy abstrakcyjnej cecha++ – redefinicja cechy
obwód++
Składnia dziedziczenia w języku C++
class Osoba {
protected:
char nazwisko [Max];
char płeć;
unsigned wiek;
public:
Osoba (char*, char, unsigned);
Void ZmieńNazwisko(char*);
void DaneOsobowe( );
};
class Pracownik : public Osoba {
protected:
char Etat [Max]; //nowa cecha
unsigned płaca; //nowa cecha
public:
Pracownik (char*, char, unsigned, char*);
void Awansuj(char*); //nowa cecha
void DajPodwyżkę(unsigned); //nowa cecha
void DaneOsobowe( ); //redefinicja cechy
};
...
Osoba Morzy("Morzy",'M',45);
Morzy.DaneOsobowe();
Pracownik Buła("Buła",'M',38,"portier");
Buła.Awansuj("prezes");
Buła.ZmieńNazwisko("Tarzan");
Buła.DaneOsobowe();
W języku C++ nie są dziedziczone:
konstruktory i destruktory klasy
przeciążony operator=()
„przyjaciele” klasy
klasa bazowa
Kowariantna redeklaracja cech klasy (Eiffel)
Kowariantna redeklaracja cech klasy: specjalizacja typu atrybutu klasy - zgodna z relacją pod-
typu; specjalizacja typów parametrów wejściowych lub warto-
ści zwrotnej metod class OSOBA
małżonek OSOBA
małżeństwo (m: OSOBA) is
do
małżonek := m
end
… -- inne cechy
end -- class OSOBA
class KOBIETA inherit OSOBA
redefine małżonek, małżeństwo
-- kowariantna redeklaracja atrybutu
małżonek MĘŻCZYZNA -- kowariantna redeklaracja metody
małżeństwo (m: MĘŻCZYZNA) is
do
małżonek := m
end
… -- inne cechy
end -- class KOBIETA
Składanie metod
Użyteczny jest mechanizm, w którym definicja metody redefiniowanej może korzystać z kodu metody przesła-nianej.
class PRACOWNIK { // C++
public:
float Wylicz_płacę( );
...
class KIEROWNIK: public PRACOWNIK {
public:
float Wylicz_płacę( );
...
float KIEROWNIK::Wylicz_płacę( ) {
return 2,5*(PRACOWNIK::Wylicz_płacę());
}
class PRACOWNIK { // JAVA
public float Wylicz_płacę( );
...
class KIEROWNIK extends PRACOWNIK {
public float Wylicz_płacę( ){
return 2,5*(super.Wylicz_płacę());
}
Dziedziczenie, a hermetyczność
Dostępność odziedziczonych elementów publicznych w obiektach podklasy.
1. Zachowanie relacji podtypu
Wszystkie publiczne elementy nadklasy muszą pozo-stać publiczne w jej podklasach.
Klasa B dziedziczy po klasie A
Funkcjonalność(A) Funkcjonalność(B)
2. Niezależność dziedziczenia i hermetyczności (postu-lowana)
Istnieją przypadki, w których interfejs publicznych me-tod podklasy nie obejmuje wszystkich publicznych metod nadklasy.
class Wielokąt {
public:
void DodajWierzchołek(Punkt);
float Pole( );
};
class Prostokąt: public Wielokąt {
...
};
...
Wielokąt *wp = new Prostokąt(p1, p2);
wp -> DodajWierzchołek(p3); //???
Niezależność dziedziczenia od hermetyczności
Innym przykładem zastosowanie niezależności dziedzi-czenia od hermetyczności jest wykorzystanie podklas jako zawężających widoków nadklasy.
Konto
Konto_ klient
public: otwórz zmień PIN wpłata wypłata blokada saldo zamknij
Konto_ księgowość
Konto_ zarząd
public: otwórz blokada saldo zamknij
public: zmień PIN wpłata wypłata saldo
public:
saldo
Całkowita niezależność interfejsów podklas z nadklasą oznacza pomylenie dziedziczenia klas z kompozycją klas.
Niezależność dziedziczenia od hermetyczności – składnia C++
Zależność relacji podtypu od hermetyczności
class Wielokąt {
public:
void DodajWierzchołek(Punkt);
float Pole( );
};
class Prostokąt: public Wielokąt {
private:
Wielokąt::DodajWierzchołek;
};
...
Wielokąt *wp = new Prostokąt(p1, p2); //błąd
class Konto {
public:
void Otwórz();
void Wpłata(float );
void Wypłata(float );
float Saldo();
void Blokada();
};
class KontoKlient: private Konto {
public:
Konto::Wpłata;
Konto:Wypłata;
Konto::Saldo;
};
Dziedziczenie „przyjaciół” klasy
udostępnienie
cechy a
A
B1
B
propagacja
cechy a
A1
dziedziczenie
przyjaciół
? ?
class B; class A {
private:
int a;
friend class B;
};
class A1: public A { };
class B {
public:
void b1(A);
void b2(A1);
};
class B1: public B {
public:
void d(A);
};
void B::b1(A x){
x.a=0; } //OK, klasa B jest przyjacielem klasy A void B::b2(A1 x){
x.a=0; } //błąd, klasa B nie jest przyjacielem klasy A1 void B1::d (A x){
x.a=1 } //błąd, klasa B1 nie dziedziczy dostępu do A::a
Relacja przyjaźni nie jest dziedziczona po
żadnej ze stron
Dziedziczenie klas w języku Java
W języku Java dziedziczone są wszystkie zmienne i me-tody nadklasy za wyjątkiem konstruktorów. Nie ma try-bów dziedziczenia prywatnego i chronionego. Dzięki te-mu relacja pod-typu jest zawsze zachowana w łańcuchu dziedziczenia.
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y; }
...
}
class ColoredPoint extends Point {
static final int WHITE=0, BLACK=1;
int color;
ColoredPoint(int x, int y) {
this(x, y, WHITE);
}
ColoredPoint(int x, int y, int color) {
//wywołanie konstruktora nadklasy – składanie metod
super(x, y); // Point(x,y) this.color = color;
}
...
}
Wywołanie konstruktora klasy bazowej musi mieć miej-sce w pierwszej linii kodu konstruktora klasy pochodnej.
Klasy abstrakcyjne
Klasy abstrakcyjne są to klasy, które nie mają kompletnej implementacji. W związku z tym, klasy te nie mogą mieć wystąpień. Mogą one służyć je-dynie jak pośredni etap dla dziedziczących po nich klas zawierających implementację cech abstrak-cyjnych.
Zdefiniowanie danej klasy jako abstrakcyjnej uniemożliwia tworzenie wystąpień tej klasy.
Własna implementacja klasy abstrakcyjnej wiąże się z występowaniem błędów w czasie wykonywa-nia programów:
class figura {
public:
virtual void obróć (int)
{ error (" klasa abstrakcyjna" );
virtual void rysuj ( )
{ error (" klasa abstrakcyjna" );
};
figura f; // nieprzydatny obiekt
f.obróć(45); // błąd
Klasy abstrakcyjne w C++
Składnia języka C++ umożliwia definiowanie klas abstrakcyjnych. Kompilator języka uniemożliwia tworzenie zmiennych (typów funkcji i metod oraz parametrów wejściowych funkcji i metod), których typem jest klasa abstrakcyjna.
class figura {
public:
virtual void obróć (int) = 0;
virtual void rysuj ( ) = 0;
// klasa nie może zawierać definicji metod obróć i rysuj };
figura f; //błąd składniowy
//wystąpienie klasy abstrakcyjnej
figura f1( ); // błąd składniowy - jw.
void f2(figura); // błąd składniowy - jw. figura *fp; // OK
figura &fr; // OK
Klasy pochodne klasy abstrakcyjnej powinny zawierać implementację abstrakcyjnych cech klasy.
class prostokąt : public figura {
public:
virtual void obróć (int);
// przesłania figura::obróc
virtual void rysuj ( );
// przesłania figura::rysuj
}
Interfejsy
Język Java oprócz klas abstrakcyjnych, obejmuje dodatkowo pojęcie interfejsu, który może być trak-towany jako szczególna klasa abstrakcyjna, która w ogóle nie posiada implementacji.
interface Colorable {
void setColor(int color);
}
Definicja interfejsu nie może zawierać:
elementów prywatnych
definicji zmiennych obiektów
metod typu final
Interfejsy powinny implementowane przez klasy. Pojedyncza klasa może implementować wiele in-terfejsów. Interfejs może reprezentować po-jedynczy aspekt klasy. class Colored implements Colorable {
int setColor(int color) {...};
}
Interfejsy mogą być powiązane siecią dziedzicze-nia o topologii zbioru acyklicznych grafów.
Topologia sieci dziedziczenia
Dziedziczenie jednokrotne (SmallTalk 80, C#, klasy w języku Java) - każda klasa ma co najwy-żej jedną nadklasę. Sieć klas ma kształt hierar-chii.
Dziedziczenie wielokrotne (C++, Eiffel, interfejsy w języku Java) - klasy mogą dziedziczyć po wie-lu nadklasach. Sieć klas ma kształt grafu acy-klicznego skierowanego.
Sieć klas może być rozłączna (C++) lub być nie-podzielna i mieć wyróżniony wierzchołek, który jest korzeniem sieci (SmallTalk 80, Java, C#).
Globalna struktura dziedziczenia w języku Eiffel
Rozszerzenie sieci klas o dwie uniwersalne klasy syste-mowe: ANY i NONE.
ANY
NONE
Klasy zdefiniowane przez użytkowników
Każda klasa dziedziczy po klasie ANY – możliwość zdefiniowania wspólnych cech wszystkich klas
Wszystkie klasy są generalizacją klasy NONE – klasa wartości nieokreślonych lub pustych (void, nill, null).
Dziedziczenie interfejsów, a dziedziczenie klas w języku Java
Topologie związków dziedziczenia między klasami i interfejsami w języku Java są odmienne.
Klasy mogą dziedziczyć bezpośrednio po tylko jednej nadklasie. Topologia sieci związków dziedziczenia klas ma kształt drzewa o jednym wyróżnionym korzeniu reprezentującym klasę Object.
Interfejsy mogą dziedziczyć bezpośrednio po wielu innych interfejsach. Topologia sieci związków dziedziczenia interfejsów ma kształt zbioru grafów acyklicznych skierowanych.
Związki implementacji między interfejsami i klasami maja topologię grafów acyklicznych skierowanych. Pojedyncza klasa może imple-mentować wiele interfejsów.
Sieci dziedziczenia klas i interfejsów oraz imple-mentacji tworzą jedną sieć podtypów.
Dziedziczenie wielokrotne (C++)
W języku C++ klasy mogą bezpośrednio dziedzi-czyć po wielu nadklasach. Brak mechanizmu wie-lokrotnego dziedziczenia wymaga wielokrotnej im-plementacji i utrzymania tych samych cech róż-nych klas.
Student
StudentPracujący
Pracownik
class Pracownik {
...
float płaca;
char *etat;
... };
class Student {
...
char *uczelnia;
int rokStudiów;
float średniaOcen;
... };
class StudentPracujący : public Pracownik,
public Student { ... };
Dziedziczenie wielokrotne (C++)
Klasa dziedzicząca może dziedziczyć po różnych klasach cechy o takich samych nagłówkach. Meto-dy lub zmienne o takich samych deklaracjach ale pochodzące z różnych klas są rozróżniane po miejscu ich zdefiniowania.
Student
StudentPracujący
Pracownik
class Pracownik {
public:
void dane_kontaktowe(); };
...
class Student {
void dane_kontaktowe(); }; ...
class StudentPracujący : public Pracownik,
public Student { }; ...
StudentPracujący sp;
sp.Student::dane_kontaktowe();
sp.Pracownik::dane_kontaktowe();
Wielokrotne dziedziczenie z tego samego źródła
class Osoba {
protected:
char *nazwisko;
...
public:
void Nazwisko(char*);
void wiek(int);
...
};
class Pracownik : public Osoba{ ... };
class Student : public Osoba { ... };
class StudentPracujący : public Pracownik,
public Student { ... };
StudentPracujący sp(...);
sp.Nazwisko("Tarzan"); // niejednoznaczne wywołanie
sp.Student::Nazwisko("Tarzan"); // OK
sp.Pracownik::Nazwisko("Kowalski"); // OK
-- student sp ma dwa niezależne nazwiska
Student
StudentPracujący
Pracownik
Osoba
Pracownik::Nazwisko Student::Nazwisko
Specyfika C++ dziedziczenie wirtualne
a) dziedziczenie zwykłe b) dziedziczenie wirtualne
skonsolidowany obszar pamięci
wskaźniki na poszczególne składowe obiektu
class Osoba {...};
class Pracownik : public virtual Osoba {...};
class Student : public virtual Osoba {...};
class StudPrac:public Student,public Pracownik {...};
StudentPracujący sp(...);
sp.Nazwisko("Tarzan"); // jednoznaczne wywołanie
// student sp ma jedno nazwisko
Pracownik:wiek
Pracownik:nazwisko
Pracownik:imię
Student:wiek
Student:nazwisko
Student:imię
Student:rok
Student:uczelnia
Pracownik:firma
Pracownik:wiek
nazwisko
imię
wiek
vbase
etat
płaca
uczelnia
rokStudiów
vbase
vbazse
Polimorfizm
Polimorfizm umożliwia podstawienie pod daną zmienną różnych typów obiektów. Typ podstawianego obiektu
musi występować w relacji jest z typem zmiennej.
Podstawiany obiekt nie jest poddawany konwersji!!! Za-chowuje on pełną funkcjonalność zgodą z jego własnym typem.
Taki rodzaj podstawienia jest nazywany podstawieniem polimorficznym; a zmienne są nazywane zmiennymi polimorficznymi. Zmiennymi polimorficznymi mogą być parametry wejściowe metod.
class Wielokąt {...);
class Prostokąt extends Wielokąt {...);
class Trójkąt extends Wielokąt {...);
Wielokąt w; // zmienna polimorficzna
Prostokąt pr = new Prostokąt(p1, p2);
Trójkąt tr = new Trójkąt (p3, p4, p5);
// podstawienia polimorficzne
w = pr; // podstaw prostokąt pod wielokąt
w = tr; // podstaw trójkąt pod wielokąt
Typowym zastosowaniem polimorfizmu są polimorficz-ne struktury danych
// polimorficzna struktura danych
Wielokąt [] tw;
// podstawienia polimorficzne
tw[0] = pr;
tw[1] = tr;
Podstawienie polimorficzne w C++
W języku programowania C++ poprawne są następujące typy podstawień polimorficznych:
Podstawienie pod wskaźnik klasy X adresu obiek-tu, który jest wystąpieniem publicznej podklasy kla-sy X.
Wielokąt *wp;
// klasa Trójkąt jest podklasą klasy Wielokąt
Trójkąt t(p1, p2, p3);
wp = &t;
Podstawienie pod zmienną referencyjną klasy X referencji na obiekt, który jest wystąpieniem pu-blicznej podklasy klasy X.
Trójkąt t(p1, p2, p3);
Wielokąt &wp = t;
Niemożliwe jest bezpośrednie podstawienie pod zmienną klasy X obiektu, który jest wystąpieniem podklasy klasy X.
Wielokąt w(p4, p2, p5, p6);
Trójkąt t(p1, p2, p3);
w = t; // utworzenie kopii wyniku rzutowania obiektu
Wiązanie dynamiczne
Wiązanie – operacja przypisywania nazwom progra-mu źródłowego - wartości
WyliczObwód(prostokąt1) 6aaf:99bd
prostokąt1 9bb4:1240
prostokąt.WyliczObwód() 6900:1234
Wiązanie statyczne (wczesne) – wiązanie w czasie kompilacji
Trójkąt t = new Trójkąt(p1, p2, p3);
/* statyczne przypisanie komunikatowi obwód kodu me-
tody obwód z klasy Trójkąt wynikającej z typu zmiennej */
float = t.obwód();
Wiązanie dynamiczne (późne – ang. late binding) –wiązanie nazw komunikatów dla zmiennych polimor-ficznych w czasie działania programu. Polega na dynamicznym wyborze metody właściwej dla obiektu przypisanego zmiennej polimorficznej.
Figura f;
// podstawienie polimorficzne
f = New Trójkąt(p1, p2, p3);
/* dynamiczne przypisanie komunikatowi obwód kodu
metody obwód z klasy Trójkąt mimo, że z typu zmien-
nej f wynikałaby metoda klasy figura */
float = f.obwód();
Wiązanie statyczne
Wiązanie statyczne nie gwarantuje proporcjonal-ności modyfikacji
class figura {...};
class Kwadrat {...};
class Trójkąt {...};
class Koło {...};
figura *lista_figur;
...
while (lista_figur.next != pusta) {
//przesuń kolejną figurę if lista_figur->typ = kwadrat
lista_figur-> przesuńKwadrat(x,y);}
if lista_figur->typ = koło
lista_figur-> przesuńKoło(x,y);}
...
while (lista_figur.next != pusta) {
//obróć kolejną figurę if lista_figur ->typ = kwadrat
lista_figur -> obróćKwadrat(x,y);}
Modyfikacja modułu A przez dodanie nowej klasy figur:
class Romb {...};
- propaguje się do wszystkich modułów przetwarzają-cych figury.
moduł A
moduł B
moduł C
class Romb {…};
Wiązanie dynamiczne
Wiązanie dynamiczne ułatwia utrzymanie progra-mów ograniczając zasięg modyfikacji
class figura {...};
class Kwadrat {...};
class Trójkąt {...};
class Koło {...};
figura *lista_figur;
...
while (lista_figur.next != pusta) {
//przesuń kolejną figurę lista_figur -> przesuń(x,y);}
...
while (lista_figur.next != pusta) {
//obróć kolejną figurę lista_figur -> obróć(x,y);}
Modyfikacja modułu A przez dodanie nowej klasy figur:
class Romb {...};
nie wymaga modyfikacji kodu modułów B i C. Komunika-ty przesuń i obrót zostań dynamicznie przypisane do ko-du metod klasy Romb.
moduł A
moduł B
moduł C
class Romb {…};
Wiązanie statyczne zmiennych polimorficznych (C++)
Domyślnym sposobem wiązania zmiennych polimorficz-nych w C++ jest wiązanie statyczne. Takie podejście jest niebezpieczne !!! Może ono powodować niewła-ściwe działanie programów. Wiązanie statyczne można stosować w celu zwiększenia efektywności działania programów jedynie w sytuacjach, gdy daje taki sam wynik jak wiązanie dynamiczne (rezygnacja z polimor-fizmu).
class Pracownik {
protected:
char Nazwisko [MaxN];
float stawka;
float płaca;
public:
void wylicz_płacę( );
};
class Kierownik : public Pracownik {
protected:
float dodatek_funkcyjny;
public:
void wylicz_płacę( );
};
// związanie statyczne Pracownik *pp= new Kierownik();
/* błędne wyliczenie płacy dla kierownika: Pracownik::wylicz_płacę() */
pp-> wylicz_płacę ( );
Implementacja wczesnego wiązania w C++
class Osoba {
char Imię [MaxI];
char Nazwisko [MaxN];
unsigned wiek;
public:
void print( );
};
class Student : public Osoba {
char Uczelnia [MaxU];
unsigned rokStudiów;
public:
void print( );
};
Osoba *op;
Student *sp = new Student();
sp -> print( ); // Student::print()
op = sp; //op i sp wskazują na ten sam obiekt
op -> print( ); // Osoba::print()
/* zmienna op umożliwia dostęp jedynie do osobowych
cech studenta */
Uaktywnienie metody print() zdefiniowanej w klasie Student
Uaktywnienie w obiekcie klasy Student przesłoniętej metody print() zdefiniowanej w klasie Osoba !!!
Wiązanie dynamiczne w C++
Dynamiczne wiązanie w C++ nie jest wiązaniem do-myślnym. Jest realizowane za pomocą mechanizmu funkcji wirtualnych.
class Pracownik {
protected:
char Nazwisko [MaxN];
float stawka;
float płaca;
public:
virtual void wylicz_płacę( );
};
class Kierownik : public Pracownik {
protected:
float dodatek_funkcyjny;
public:
void wylicz_płacę( );
};
// wiązanie dynamiczne
Pracownik *pp= new Kierownik();
// poprawne wyliczenie płacy dla kierownika
pp-> wylicz_płacę ( );
// Kierownik::wylicz_płacę()
Późne wiązanie w łańcuchu dziedziczenie
class PRACOWNIK {
public:
virtual float Wylicz_płacę( );
protected:
virtual float Wyznacz_podstawę( );
virtual float Wylicz_premię( );
...
}
float PRACOWNIK:: Wylicz_płacę( ) {
float s = this-> Wyznacz_podstawę( );
s += this-> Wylicz_premię( );
...
}
class KIEROWNIK: public PRACOWNIK {
protected:
float Wyznacz_podstawę ( ); //redefinicja
float Wylicz_premię ( ); //redefinicja
}
...
Kierownik Tarzan("Tarzan", 3500, 10);
float pensja = Tarzan.Wylicz_płacę( );
Tarzan Wylicz_płacę()
Wylicz_premię()
Pracownik
Kierownik
Wyznacz_podstawę()
"this" jako zmienna polimorficzna
Implementacja dynamicznego wiązania w C++
class Osoba {
char imię [MaxI];
char nazwisko [MaxN];
unsigned wiek;
public:
virtual void print( );
};
class Student : public Osoba {
char uczelnia [MaxU];
int rok;
public:
void print( );
};
Osoba *op;
Student *sp = new Student("Tarzan","PP");
op = sp;
op->print(); //Student::print()
Powyższe wywołanie będzie realizowane jako:
((op->vftbl[0]))(op)
tabela z adresami
kodu wirtualnych metod
vftbl
imię
nazwisko
wiek
uczelnia
rok
&Student::print() op
funkcjonalność
klasy Osoba
Implementacja wielokrotnego dziedziczenia w C++
class Student {
int indeks;
public:
virtual int indeks( );
...
};
class Pracownik {
char* etat;
public:
virtual char* etat( );
...
};
class StudentPrac : public Student, public Pracownik {
};
StudentPrac *spp;
*spp = new StudentPrac("Tarzan","prezes");
adres metody
&Student::indeks() spp
funkcjonalność
klasy Student
0
przesunięcie ciała obiektu
Student vftbl
indeks
uczelnia
rokStudiów
etat
płaca
Pracownik vftbl
funkcjonalność
klasy Pracownik
spp + delta PS
&Pracownik::etat() +delta PS
&Pracownik::płaca() +delta PS
W klasie StudentPrac kod metod odziedziczonych z klasy Pracownik ma nieak-tualne informacje o lokaliza-cji zmiennych etat i płaca.
Podstawienia polimorficzne wystąpień klas dziedziczących
w sposób wirtualny
class Osoba {...};
class Pracownik : public virtual Osoba {...};
class Student : public virtual Osoba {...};
Student *sp = new Student();
Osoba *op = sp;
Dwa wskaźniki na ten sam obiekt maja różne war-tości: op != sp.
nazwisko
imię
wiek
vbase
etat
płaca
uczelnia
rokStudiów
vbase
vbazse
sp
op
Dynamiczna identyfikacja typu
Podstawienia polimorficzne ograniczają funkcjonalność obiektu do interfejsu typu zmiennej polimorficznej. W in-terfejsie zmiennej polimorficznej nie występują komuni-katy metod specyficznych dla podklas jej typu. W pew-nych sytuacjach może być potrzebny dostęp do cech obiektów niedostępnych poprzez interfejs zmiennej po-limorficznej.
Void DanePersonalne(Osoba* pos) {
cout << pos->podajPesel();
// jeżeli to student dodaj informacje o studencie // jak dynamicznie ustalić typ ? cout << pos->podajŚrednią(); //błąd
// jeżeli to pracownik dodaj informacje o pracowniku // jak dynamicznie ustalić typ ? cout << pos->podajEtat(); //błąd
}
Zmienna pos jest typu Osoba, w jej interfejsie nie ma
metod specyficznych dla pracowników i studentów.
W powyższym programie niezbędne jest ustalenie w trakcie działania programu klasy obiektu podstawionego pod zmienną polimorficzną.
Języki obiektowe ze zintegrowaną siecią klas
Klasy Systemowe (Metaclass, Class, Object, itp.) posia-dają funkcjonalność umożliwiającą dynamiczną identyfi-kację własności obiektów i klas: nazwy i typy atrybutów, nazwy i nagłówki metod, nazwę klasy obiektu, nazwę nadklasy obiektu, klasę jako obiekt, itp.
Przykłady w języku Java
Systemowa funkcjonalność obiektów i klas zdefiniowana w klasie Class:
boolean isInstance(Object obj)
String getName()
Method getMethod(String name, Class[] parameterTypes)
public Field[ ] getFields()
boolean isInstance(Object ClassObject)
Class getSuperclass()
Systemowa funkcjonalność obiektów i klas zdefiniowana w klasie Object:
Class getClass() // wynikiem jest obiekt “Class”
String toString() // serializacja obiektu
Mechanizm refleksji w języku Java
Mechanizm refleksji umożliwia tworzenia, w wyjątkowych i wymagających takich rozwiązań wypadkach, bardziej ela-stycznego kodu programów.
// dany jest duży i zmienny zbiór klas: Student, Pracownik, itd // rozwiązanie bez mechanizmu refleksji
if (s.equals("Student")) {
Student st = new Student(x,y,z);
st.zróbCoś();}
else if (s.equals("Pracownik")) {
Pracownik pr = new Pracownik(x,y,z);
Pr.zróbCoś();}
... // tyle rozwidleń ile różnych klas
// bardziej elastyczne rozwiązanie z mechanizmem refleksji
Class kl = s.getClass(); // kl jest obiektem - klasą Object o = kl.newInstance();
// korzystamy z obiektu kl jako fabryki obiektów Method mt = kl.getMethod("zróbCoś", null);
// mt jest obiektem – metodą mt.invoke(o, null); //wywołanie metody ...
Dynamiczna identyfikacja typu w wypadku podstawień polimorficznych
Osoba os; //zmienna polimorficzna ...
if os.isInstance(Student) { // os jest studentem? Student s = (Student)os;
s.średnia(); } //operacja właściwa dla studentów
Dynamiczna identyfikacja typu w języku C++
Bibioteka: RTTI (Run Time Type Information)
#include <typeinfo>
void DanePersonalne(Osoba* pos) {
// część kodu odwołująca się do funkcjonalności osób
cout<<pos->Pesel();
// część kodu odwołująca się o funkcjonalności specyficznej
if (typeid(*pos)==typeid(Student))
// jeżeli to student podaj informacje o studencie
Student* pst=dynamic_cast<Student*>(pos);
cout << pst->ŚredniaOcen();
if (typeid(*pos)==typeid(Pracownik))
// jeżeli to pracownik dodaj informacje o pracowniku
Pracownik* ppr=dynamic_cast<Pracownik*>(pos);
cout << ppr->Etat();
}
Operator rzutowania dynamic_cast dokonuje konwer-
sji polimorficznego wskaźnika pos na wskaźnik na pod-
typ. Następuje zmiana typu wskaźnika wskazującego na ten sam obiekt.
Mimo, że zmienne pos i pst wskazują na ten sam obiekt,
to typ tych zmiennych jest różny.
Dziedziczenie, a operacje tworzenia i usuwania obiektów Tworzenie wystąpień klas pochodnych C++
W języku C++ konstruktory obiektów nie mogą być prze-słaniane. W związku z tym obiekty, które są wystąpie-niami klas pochodnych muszą być inicjowane przez se-kwencję wywołań konstruktorów klas wzdłuż łańcucha dziedziczenia począwszy od korzenia grafu dziedzicze-nia. Potrzebny jest do tego mechanizm przekazywania parametrów aktualnych do tych konstruktorów.
class Osoba {
private:
char *nazwiskoOsoby;
public:
Osoba(char *nazwisko);
...};
Osoba::Osoba(char *nazwisko) {
strcpy(nazwiskoOsoby, nazwisko);
}
class Student : public Osoba {
private:
char *NazwaUczelni;
public:
Student(char *uczelnia, char *nazwisko);
...};
Student::Student(char *uczelnia, char *nazwisko):
Osoba(nazwisko) {
strcpy(NazwaUczelni, uczelnia);
} wywołanie konstruktora klasy bazowej
Dynamiczne wiązanie destruktorów
Destruktory wystąpień klas pochodnych są wywoływane w odwrotnej kolejności niż konstruktory, to jest począw-szy od destruktora klasy obiektu, poprzez destruktory klas bazowych, do destruktora klasy, która jest korze-niem grafu klas.
Żeby wywołać destruktor klasy obiektu podstawionego pod zmienną polimorficzną musi być on (destruktor) wią-zany dynamicznie.
class Osoba {
public:
virtual ~Osoba(); // destruktor dynamiczny ...};
class Student : public Osoba {
private:
ListaPubów *Pub;
public:
~Student();
...};
Student::~Student(){
delete [] ListaPubów;
}
Osoba *op = new Student("Tarzan", "PP");
...
delete op; //wywołanie destruktora ~Student
Ograniczenia polimorfizmu
Zmieniony zakres dostępności odziedzi-czonych cech klasy
class Wielokąt {
public:
void DodajWierzchołek(Punkt);
float Pole( );
};
class Prostokąt: public Wielokąt {
private:
Wielokąt::DodajWierzchołek;
};
… Wielokąt *wp
Prostokąt p = new Prostokąt(p1, p2);
wp = p; // błąd kompilacji
// podstawienie polimorficzne jest zabronione
Dodatkowe reguły statycznego typowania danych
(R1) Zabronione są polimorficzne wywołania metod o zmienionym zakresie dostępności odziedzi-czonych cech klasy
(R2) Zabronione są polimorficzne wywołania metod redeklarowanych kowariantnie
Ograniczenia polimorfizmu
Specjalizacja argumentów
Redeklaracja kowariantna cech wzdłuż łańcucha dziedziczenia
class STUDENT feature
współlokator: STUDENT
przydziel (inny: STUDENT) is
do współlokator := inny end
…
end -- class STUDENT
class DZIEWCZYNA inherit STUDENT
redefine współlokator, przydziel end
współlokator: DZIEWCZYNA
przydziel (inny: DZIEWCZYNA) is
do współlokator := inny end
…
end -- class DZIEWCZYNA
s: STUDENT; d: DZIEWCZYNA; c: CHŁOPAK
…
!! c.make(…); !!d.make(…)
-- tworzenie obiektów DZIEWCZYNA i CHŁOPAK
s : = d; -- błąd kompilacji
-- podstawienie polimorficzne jest niedozwolone -- żeby uniknąć błędu typu parametru wejściowego
s.przydziel(c); -- chłopak w pokoju dziewczyn
Metodyka stosowania dziedziczenia
“Mieć, czy być”
Model obiektowy oferuje dwa różne rodzaje powiązań międzymodułowych:
związek zawierania - ma (ang. has),
związek dziedziczenia - jest (ang. is a).
jest
B
A’
Ama
W pewnych szczególnych sytuacjach rozróżnienie to nie jest oczywiste
class INFORMATYK_1 inherit INŻYNIER feature …
end -- class INFORMATYK
class INFORMATYK_2 feature inżynier_we_mnie: INŻYNIER …
end -- class INFORMATYK
Kryterium wyboru
jest
INŻYNIER
maINFORMATYK
POETA
POWOŁANIE
HYDRAULIK
jest
INŻYNIER
INFORMATYKARCHITEKT
ELEKTRYK
Reguła zmienności
Nie używaj dziedziczenia do opisu relacji jest, jeżeli
relacja ta jest zmienna w czasie.
Reguła polimorfizmu
Użyj dziedziczenia do opisu relacji jest, jeżeli wystą-
pienia klasy mają być używane w sposób polimorficzny.
Klasyfikacja zastosowań dziedziczenia
(Klasa B dziedziczy po klasie A)
1. Dziedziczenie opisujące związki występujące w modelu
1.1 Relacja podzbioru – zbiór wystąpień klasy B nale-ży do zbioru wystąpień klasy A i inne podzbiory kla-sy A są rozłączne z B: prostokąty -> wielokąty; ssa-ki -> kręgowce; pracownik -> osoba.
1.2 Perspektywa – wystąpienia klasy B ujawniają jedy-nie pewne aspekty wystąpień klasy A: pracow-nik_płace -> pracownik.
1.3 Ograniczenia - wystąpienia klasy B są wystąpie-niami klasy A, które spełniają pewne dodatkowe ograniczenia (dodatkowe niezmienniki nie występu-jące w klasie A): kwadraty -> prostokąty, okręgi -> elipsy.
1.4 Rozszerzenie – klasa B wprowadza nowe cechy, nie występujące w A, przy czym klasa A nie jest klasą abstrakcyjną: cząstka(masa, prędkość) -> punkt.
2. Dziedziczenie opisujące implementację
2.1 Konkretyzacja – klasa A opisuje ogólny typ da-nych, klasa B zawiera jego reprezentację: stos_jako_lista -> stos_abstrakcyjny.
2.2 Współdzielenie – abstrakcyjna klasa A zawiera pewne ogólne cechy wykorzystane w klasie B: licz-by -> wielkości_porównywalne (operacje >, >=, <, itd.).
2.3 Implementacja – klasa A oferuje klasie B zbiór cech niezbędnych do jej implementacji.
2.4 Usługi - klasa A przekazuje klasie B zbiór użytecz-nych usług: klasa_użytkownika -> wyjątki.
3. Wariacje klas
3.1 Wariacje funkcjonalne – klasa B jest modyfikacją klasy A polegającą na redefinicji wybranych funkcji. Ogólna semantyka klasy A pozostaje niezmieniona.
3.2 Wariacje strukturalne - klasa B jest modyfikacją klasy A polegającą na redefinicji struktury klasy. Ogólna semantyka klasy A pozostaje niezmieniona.
3.3 Dematerializacja – klasa B przesłania efektywne (zaimplementowane) cechy klasy A przez cechy abstrakcyjne.