Programowanie Obiektowe Cz2, Dziedziczenie proste


Dziedziczenie proste

Przykład:

class PRACOWNIK

{

protected:

char * Nazwisko, * Dział;

int Uposażenie;

. . . . . . . . . . . . . . . . . . . . .

};

class KIEROWNIK: public PRACOWNIK

{

int Uprawnienia[ 8 ];

. . . . . . . . . . . . . . . . . . . . .

};

Przykłady poprawnych definicji obiektów i wskazań do obiektów w warunkach dziedziczenia prostego:

KIEROWNIK kk;

PRACOWNIK * p = & kk;

// każdy kierownik jest jednocześnie pracownikiem,

PRACOWNIK pp;

0x08 graphic
0x08 graphic

KIEROWNIK * k = & pp;

// . . . ale nie każdy pracownik kierownikiem

  • Obiekty klasy pochodnej są traktowane jak obiekty klasy bazowej, jeśli sięgnie się do nich za pomocą wskaźnika. Odwrotnie - NIE !

Dziedziczenie i polimorfizm

W poniższym przykładzie zaprezentowane zostanie użycie: klas abstrakcyjnych, funkcji wirtualnych i czysto wirtualnych (abstrakcyjnych), oraz omówiony mechanizm polimorfizmu.

Problem:

Zdefiniować klasę, mogącą przechowywać wskaźniki do obiektów różnych typów i zapewniającą ich wizualizacje za pomocą jednej funkcji Out( ).

0x08 graphic

class ELEMENT

{

public:

void Out( );

};

0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic
rozmiar = 5

0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic

Rys. Klasa KOSZ ze wskazaniami do obiektów różnych klas

typedef BOOL;

class KOSZ

{

ELEMENT **tab; // tablica wskaźników do obiektów

unsigned rozmiar, ostatni;

public:

KOSZ( unsigned rozm = 10 );

~ KOSZ( void );

BOOL DoKosza( ELEMENT * );

// weźmie do kosza wskazanie obiektu każdej klasy,
// dziedziczącej z klasy ELEMENT

void Out( void );

// wysyła na wyjście wszystkie obiekty znajdujące się w
// koszu niezależnie od ich typu

};

KOSZ:: KOSZ( unsigned rozm ):

tab( new ELEMENT *[ rozm ], rozmiar( rozm),
ostatni( 0 ) { }

KOSZ:: ~KOSZ( void )

{

for(int i = 0; i < ostatni; ++i) delete tab[ i ];

delete tab;

};

BOOL KOSZ:: Do Kosza( ELEMENT * pE1)

{ if( ostatni < rozmiar )

{ tab[ ostatni ] = pE1; ++ ostatni; return 1; }

else return 0;

}

void KOSZ:: Out( void )

{

for( int i=0; i < ostatni; i++) tab[ i ] Out( );

}

  • Umieszczenie w Koszu wskaźnika obiektu klasy pochodnej od ELEMENT będzie równoważne umieszczeniu tam wskaźnika klasy ELEMENT

Teraz możemy definiować klasy zupełnie dowolnych obiektów dziedziczących z klasy ELEMENT

0x08 graphic

0x08 graphic

0x08 graphic

0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic
0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic
0x08 graphic

class STUDENT: public ELEMENT

{

char * nazwisko;

int wiek;

public:

STUDENT( char *_nazwisko, int _wiek);

~STUDENT( );

void Out( );

};

STUDENT:: STUDENT( char *_nazwisko, int _wiek):

nazwisko( strdup( _nazwisko ), wiek( _wiek) { }

STUDENT:: Out( void )

{ cout<< “\nNazwisko “<<nazwisko

<<”\nWiek “<<wiek; }

  • Funkcja składowa Out( ) klasy STUDENT ma tę samą nazwę, co funkcja składowa klasy bazowej ELEMENT, więc ją przesłania (dziedziczenie nie wystąpi).

  • Wywołanie tab[i]Out( ); odwołuje się do funkcji ELEMENT::Out( ). Jeśli chcemy, aby odnosiło się do konkretnej funkcji Out( ) w klasie pochodnej, musimy funkcję Out( ) w klasie bazowej uczynić funkcją wirtualną.

Przedefiniujmy więc klasę ELEMENT

0x08 graphic

class ELEMENT

{

public:

virtual ~ELEMENT( );

virtual void Out( ) = 0;

// deklaracja funkcji abstrakcyjnej (czysto wirtualnej)

};

Klasę KOSZ tez można wyprowadzić z klasy ELEMENT. Wtedy będzie można UMIESZCZAĆ W Koszu inne Kosze wraz z zawartością (sic ! ).

Oto poprawiona definicja klasy KOSZ:

class KOSZ: public ELEMENT

{

ELEMENT **tab;

unsigned rozmiar, ostatni;

public:

KOSZ( unsigned rozm = 10 );

virtual ~ KOSZ( void ); // wirtualny destruktor

BOOL DoKosza( ELEMENT * );

virtual void Out( void ); // wirtualna metoda

};

Również klasy STUDENT i LITERA, dziedziczące z klasy ELEMENT będą miały wirtualne destruktory i metody Out( ).

Funkcja testująca zdefiniowaną wyżej strukturę klas:

void main( )

{

KOSZ *k1= new KOSZ(3), *k2=new KOSZ(2);

// oba obiekty są dynamiczne

k1→DoKosza( new STUDENT(„Jan Nowak”, 20);

k1→DoKosza( new LITERA(`B');

k1→DoKosza( new STUDENT(„Anna Jopek”, 25);

k2→DoKosza( new LITERA(`Q');

k2→DoKosza(k1);

k2→Out( ); // polimorfizm

delete k2; // polimorfizm

return 0;

};

  • Wywołanie metody Out( ), dla obiektu wskazywanego przez k2 klasy KOSZ, powoduje poprzez tab[i] → Out( ), wywołanie odpowiedniej funkcji klasy pochodnej, odpowiedniej do klasy obiektu, chociaż tab[i] jest wskazaniem klasy bazowej ELEMENT *,

  • Podobnie delete k2 jest równoważne delete tab[i], co odpowiada usunięciu obiektu wskazywanego przez tab[i] klasy ELEMENT. Destruktor w klasie ELEMENT jest czysto wirtualny a jego definicje znajdują się w klasach pochodnych.

Zasady używania funkcji wirtualnych:

Jeszcze o funkcjach wirtualnych i klasach abstrakcyjnych

class X {

public:

virtual void f( )=0; // funkcja czysto wirtualna

virtual void g( )=0; // inna funkcja czysto wirtualna

};

0x08 graphic
0x08 graphic
X x; // błąd: deklaracja obiektu klasy abstrakcyjnej X

class A: public X {

public:

void f( ); // unieważnienie X::f( )

};

0x08 graphic
0x08 graphic
A a; // błąd: deklaracja obiektu klasy abstrakcyjnej A

class B: public A {

public:

void g( ); // unieważnienie X::g( )

};

B b; // w porządku

class K: public X {

public:

virtual void f( ); // wymagana definicja K::f( )

void g( ); // unieważnienie X::g( )

};

K k; // w porządku

  • Funkcja czysto wirtualna, która nie jest zdefiniowana w klasie pochodnej, pozostaje funkcją czysto wirtualną, czyniąc klasę pochodną również klasą abstrakcyjną.

Dziedziczenie wielobazowe

Postać ogólna nagłówka definicji klasy pochodnej:

class oznacznik : lista_klas_bazowych

Element listy klas bazowych ma postać:

< public | protected | private > ozn_klasy_bazowej

Dostępność klas bazowych w klasach pochodnych:

Rodzaj dziedziczenia

Składowe klasy bazowej ...

w klasach pochodnych są ...

public

private

niedostępne

protected

protected

public

public

protected

private

niedostępne

protected

protected

public

protected

private

private

niedostępne

protected

prywatnymi klasy pochodnej

public

prywatnymi klasy pochodnej

Jeśli żadne słowo kluczowe nie wystąpiło przyjmuje się:

Przykład:

class X: public A, B, protected C { };

Tutaj klasa X dziedziczy:

Przywracanie praw dostępu

Przykład:

class A class B: A

{ int a; {

protected: protected:

double x; A:: x; // teraz zabezpieczone

public: public:

int b; A:: b; // teraz publiczne

}; };

Prawa dostępu mogą być tylko przywracane. Nie można zmieniać praw dostępu.

Związki między klasami

Związki między klasami widziane przez pryzmat struktury programu (implementację):

  1. dziedziczenie,

  2. zawieranie

  3. należenie, albo posiadanie

  4. używanie,

  5. związki zaprogramowane.

Związki dziedziczenia

Problem: Symulacja ruchu ulicznego w celu szacowania

czasu dojazdu pojazdów uprzywilejowanych

do określonych punktów miasta.

Struktura klas z użyciem klasycznego dziedziczenia gen-spec:

POJAZD

0x08 graphic
0x08 graphic

0x08 graphic
OSOBOWY CIĘŻAROWY

0x08 graphic
0x08 graphic

UPRZYWILEJOWANY

0x08 graphic
0x08 graphic
0x08 graphic

POLICYJNY KARETKA
PRZECIWPOŻAROWY

0x08 graphic

POMPA_STRAŻACKA

Strzałki pokazują związki miedzy klasami, zwane na poziomie analityczno-projektowym uogólnieniami, a na poziomie implementacji - dziedziczeniem.

Ta sama struktura klas z użyciem pojęcia należenia:

UPRZYWILEJOWANY

POJAZD

0x08 graphic
0x08 graphic

OSOBOWY CIĘŻAROWY

0x08 graphic
0x08 graphic
0x08 graphic

POLICYJNY KARETKA
PRZECIWPOŻAROWY

0x08 graphic

POMPA_STRAŻACKA

Klasa UPRZYWILEJOWANY znajduje się poza strukturą klas, powiązanych związkiem dziedziczenia, natomiast obiekt tej klasy może należeć do obiektów różnych klas odpowiadających pojazdom.

class UPRZYWILEJOWANY { . . . };

class POJAZD

{ public: UPRZYWILEJOWANY * u_wsk; };

class OSOBOWY: public POJAZD { . . . };

class POLICYJNY: public OSOBOWY { . . . };

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Teraz dowolny pojazd będzie uprzywilejowany, jeśli u_wsk będzie różne od zera.

// konstruktor klasy OSOBOWY

OSOBOWY:: OSOBOWY( )

{ u_wsk = 0; }

// konstruktor klasy POLICYJNY

POLICYJNY:: POLICYJNY( )

{ u_wsk = new UPRZYWILEJOWANY; }

Funkcja konwersji każdego pojazdu do uprzywilejowanego i na odwrót:

void convert( POJAZD *p)

{ if(p)
{delete p→u_wsk; p→u_wsk = 0; };
// już nie uprzywilejowany

else p→u_wsk = new UPRZYWILEJOWANY;
// znów uprzywilejowany

}

Związek zawierania

class X

0x08 graphic
0x08 graphic
{. . . . .

public:

X(int);

. . . . . };

class C1

{ X a; // klasyczne zawieranie

public:

C1(int i): a(i) { }

0x08 graphic

// niejawne wywołanie konstruktora klasy X

};

Związki posiadania

0x08 graphic
0x08 graphic

class C2

0x08 graphic
{ X *p; obiekt C2

// wskaźnik do obiektu posiadanego

public:

C2(int i): p( new X(i)) { } obiekt typu X

// ten konstruktor kreuje i inicjuje obiekt posiadany

C2(X *q): p(q) { }

// ... a ten „dopina się” do obiektów istniejących,

// może się też dopinać do obiektów klas potomnych

~C2( ) {delete p;} // utrata obiektu posiadanego

X *udostępnij( ) { return p; }

X *zamien( X *q) { X *t = p; p = q; return t; }

};

Niech

class XX: public X { };

class XXX: public X { };

wtedy

void f( )

{

C2 *p1 = new C2( new X );

//obiekt dynamiczny klasy C2 „posiada” obiekt klasy X

C2 *p2 = new C2( new XX );

//obiekt dynamiczny klasy C2 „posiada” obiekt klasy XX

C2 *p3 = new C2( new XXX );

//obiekt dynamiczny klasy C2 „posiada” obiekt klasy XXX

}

Użycie referencji tworzy klasy operujące na obiektach klas X bezpośrednio, bez konieczności używania wskaźników.

0x08 graphic
class C3

0x08 graphic
{

X &r; obiekt C3

public:

C3( X &q): r(q) { }; obiekt o nazwie q klasy X

. . . . . . . . . . . . . . . .

};

  • Związki posiadania z użyciem wskaźników i referencji tworzą swoistą hierarchię obiektów a nie klas.

Związki używania

Cytaty: [ B. Stroustrup ]

1/„Wiedza o tym, jakich klas używa dana klasa i w jaki sposób,
jest często decydująca do wyrażenia i zrozumienia
projektu.”

2/„Klasa może używać jedynie nazw, które zostały już gdzieś
(wcześniej) zadeklarowane.”

Związek, zwany na poziomie implementacji związkiem użycia, jest na poziomie analizy obiektowej zwany związkiem zależności.

0x08 graphic

0x08 graphic
0x01 graphic

W tym przykładzie klasa RozkładZajęć używa klasy Przedmiot. Zmiany dokonane w specyfikacji klasy Przedmiot mogą mieć wpływ na definicję klasy RozkładZajęć, ale nie na odwrót.

Klasyfikacja sposobów, w jakich jedna klasa X może używać innej klasy Y:

  1. X używa nazwy klasy Y (jak w przykładzie powyżej),

  2. X używa Y, ponieważ:
    2.1. X czyta składową Y,
    2.2. X zapisuje składową Y,
    2.3. X wywołuje funkcje składową Y,

  3. X tworzy obiekt klasy Y, tj. X przydziela pamięć dla statycznego lub automatycznego obiektu klasy Y, lub
    tworzy dynamiczny obiekt Y za pomocą operatora new,

  4. X pobiera rozmiar Y

Związki zaprogramowane

Załóżmy, że w projekcie tworzonego systemu wyspecyfikowano, że każda operacja, która nie może być obsłużona przez klasę A, powinna być obsłużona przez klasę B, posiadaną przez klasę A.

class B

{ . . . . . .

void f( ); void g( ); void h( ); }

class A

{ B *p;

. . . . . .

void f( ); void ff( );

void g( ) { p→g( ); } // delegowanie g( )

void h( ) { p→h( ); } // delegowanie h( )

};

Związki zaprogramowane są głęboko ukryte w implementacji, przez to mało widoczne a ich skutki są trudne do przewidzenia.

Interfejsy i implementacje

0x08 graphic

0x08 graphic
0x08 graphic

0x08 graphic

0x08 graphic

Komponent jest fizyczną, wymienną, częścią systemu informatycznego. Komponent, obok swojej implementacji, wykorzystuje i realizuje pewien zbiór własnych interfejsów. Interfejs komponentu jest zestawem operacji, zamkniętych w klasie (lub klasach) interfejsowych, które to operacje wyznaczają usługi oferowane przez komponent. Taki zestaw usług określa tzw. szwy systemu.

Idealny interfejs:

// przykład interfejsu w złym stylu

class X {

Y a;

public:

void f( const char *, . . . );
// funkcja interfejsowa ze zmienną liczbą parametrów

void g( int[ ], int );

// argument funkcji interfejsowej w postaci wskazania

void ustaw_a( Y& );
// funkcja interfejsowa z parametrem w postaci referencji do
nieznanej klasy

Y& wez_a( );

// typ wyniku funkcji interfejsowej w postaci referencji do
nieznanej klasy

};

Zawarte w klasie interfejsowej powyższego przykładu metody realizują interfejs na bardzo niskim poziomie abstrakcji, ujawniając szczegóły implementacji. Nie są samoopisujące się.

Zasady praktyczne projektowania

Motto:

„Nie ma jednej „właściwej” metody projektowania. Projektowanie wymaga wyczucia, doświadczenia i inteligencji.”

  1. Użyj publicznego dziedziczenia do reprezentowania relacji bycia.

Jeśli class Y: public X { ... }; to klasa Y jest swego
rodzaju klasą X.

  1. Użyj wskaźników do reprezentowania relacji posiadania.

  2. Upewnij się, że zależności używania są zrozumiałe, minimalne i niecykliczne.

  3. Wyrażaj interfejsy w kategoriach typów z dziedziny zastosowań.

36

Ta klasa mogłaby być klasą abstrakcyjną dla takiej klasy

Zdefiniujmy wobec tego klasę podstawową KOSZ zawierającą wszystko co niezbędne

student

ostatni

litera

tab

ELEMENT

void Out( void );

LITERA

char ch;

void Out( void );

STUDENT

char * nazwisko;

int wiek;

void Out( void );

Klasa abstrakcyjna (!!!) - nie można tworzyć obiektów !!!

C1

X

p

r

Zależność

Zależność

implementacja

interfejsy

komponent



Wyszukiwarka

Podobne podstrony:
Programowanie obiektowe, wyklad5, Dziedziczenie
Programowanie obiektowe, wyklad6-czesc1, Dziedziczenie wielobazowe
15 Enkapsulacja, dziedzicznie i polimorfizm w programowaniu obiektowym
Dziedziczenie 3 PRZYKLADY, Programowanie obiektowe
Programowanie obiektowe(ćw) 1
Zadanie projekt przychodnia lekarska, Programowanie obiektowe
Programowanie obiektowe w PHP4 i PHP5 11 2005
Programowanie Obiektowe ZadTest Nieznany
Egzamin Programowanie Obiektowe Głowacki, Programowanie Obiektowe
Jezyk C Efektywne programowanie obiektowe cpefpo
Programowanie Obiektowe Ćwiczenia 5
Programowanie obiektowe(cw) 2 i Nieznany
programowanie obiektowe 05, c c++, c#
Intuicyjne podstawy programowania obiektowego0
Programowanie obiektowe, CPP program, 1
wyklad5.cpp, JAVA jest językiem programowania obiektowego
projekt01, wisisz, wydzial informatyki, studia zaoczne inzynierskie, programowanie obiektowe, projek
przeciazanie metod i operatorow, Programowanie obiektowe

więcej podobnych podstron