dziedziczenie - cs.put.poznan.pl - dziedziczenie.pdf · tarka portier s relacja między typami...

Post on 11-Aug-2019

222 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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.

top related