Projektowanie obiektowe Klasy abstrakcyjne Interfejsy
Transkrypt
Projektowanie obiektowe Klasy abstrakcyjne Interfejsy
Projektowanie obiektowe Roman Simiński [email protected] www.siminskionline.pl Klasy abstrakcyjne Interfejsy Klasa abstrakcyjna ― po co? W programowaniu obiektowym wykorzystywane są: Abstrakcja – programowanie na na wysokim poziomie ogólności, ukierunkowane na identyfikowanie głównych obiektów w systemie i ich najważniejszych zachowań oraz na definiowanie głównych zależności pomiędzy obiektami oraz ogólnych scenariuszy ich zachowań. Rozszerzalność – programowanie pozwalające na definiowanie szczególnych zachowań obiektów, realizowanych w ramach ogólnych scenariuszy, co zwykle osiąga się z wykorzystaniem dziedziczenia i polimorfizmu. Środkiem do osiągnięcia rozszerzalności jest: Antycypacja – przewidywanie oraz odwzorowanie w kodzie przyszłych, choć jeszcze nie do końca znanych zmian i modyfikacji wymagań dla systemu. Projektowanie obiektowe | 2 Wskaźniki do obiektów a hierarchia klas Konwersja wskaźników C++ Projektowanie obiektowe | 3 Upcasting, czyli dziecko staje się rodzicem class class Silnik Silnik {{ public: public: int int pojemnosc; pojemnosc; int int lbCylindrow; lbCylindrow; int int moc; moc; }; }; Upcasting Upcasting występuje występuje wtedy, wtedy, gdy gdy do do wskaźnika wskaźnika lub lub referencji referencji klasy klasy bazowej, bazowej, przypisujemy przypisujemy odwołanie odwołanie do do obiektu obiektu klasy klasy pochodnej. pochodnej. ZZ obiektem obiektem klasy klasy pochodnej pochodnej możemy możemy robić robić wszystko wszystko to, to, co co zz obiektem obiektem klasy klasy bazowej bazowej (relacja (relacja is-a). is-a). class class SilnikTurbo SilnikTurbo :: public public Silnik Silnik {{ public: public: float float cisnienieDoladowania; cisnienieDoladowania; }; }; .. .. .. Silnik Silnik ** s; s; ss == new new SilnikTurbo; SilnikTurbo; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; cout cout cout cout cout cout << << << << << << endl endl endl endl endl endl << << << << << << Upcasting Upcasting nie nie wymaga wymaga żadnych żadnych dodatkowych dodatkowych operacji, operacji, jest jest legalny legalny ii bezpieczny. bezpieczny. s->pojemnosc; s->pojemnosc; s->moc; s->moc; s->lbCylindrow; s->lbCylindrow; Projektowanie obiektowe | 4 Upcasting działa ze wskaźnikami i referencjami Silnik Silnik ** s; s; ss == new new SilnikTurbo; SilnikTurbo; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; Upcasting Upcasting via via wskaźnik wskaźnik ii referencja, referencja, obiekt obiekt anonimowy anonimowy tworzony tworzony dynamicznie dynamicznie Silnik Silnik && sr sr == *s; *s; sr.pojemnosc sr.pojemnosc == 2400; 2400; sr.moc sr.moc == 163; 163; sr.lbCylindrow sr.lbCylindrow == 5; 5; SilnikTurbo SilnikTurbo silT; silT; Silnik Silnik ** ss == &silT; &silT; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; Upcasting Upcasting via via wskaźnik wskaźnik ii referencja referencja do do zwykłego zwykłego obiektu obiektu Silnik Silnik && sr sr == silT; silT; sr.pojemnosc sr.pojemnosc == 3200; 3200; sr.moc sr.moc == 240; 240; sr.lbCylindrow sr.lbCylindrow == 8; 8; Projektowanie obiektowe | 5 Upcasting oznacza chwilowe ograniczenie dostępności pól „pochodnych” class class Silnik Silnik {{ public: public: int int pojemnosc; pojemnosc; int int lbCylindrow; lbCylindrow; int int moc; moc; }; }; Utworzony Utworzony obiekt obiekt jest jest reprezentantem reprezentantem klasy klasy pochodnej pochodnej SilnikTurbo, SilnikTurbo, jednak jednak lokalizujący lokalizujący go go wskaźnik wskaźnik związany związany jest jest zz klasą klasą bazową bazową Silnik. Silnik. Doszło Doszło do do upcastingu upcastingu — — dla dla kompilatora kompilatora wskazywany wskazywany obiekt obiekt jest jest teraz teraz obiektem obiektem klasy klasy Silnik. Silnik. class class SilnikTurbo SilnikTurbo :: public public Silnik Silnik {{ public: public: float float cisnienieDoladowania; cisnienieDoladowania; }; }; .. .. .. Silnik Silnik ** s; s; ss == new new SilnikTurbo; SilnikTurbo; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; s->cisnienieDoladowania s->cisnienieDoladowania == 0.9; 0.9; cout cout cout cout cout cout cout cout << << << << << << << << endl endl endl endl endl endl endl endl << << << << << << << << Ponieważ Ponieważ wskaźnik wskaźnik jest jest związany związany zz klasą klasą Silnik, Silnik, aa w w niej niej nie nie ma ma pola pola cisnienieDoladowania, cisnienieDoladowania, odwołania odwołania to to są są nieprawidłowe nieprawidłowe — — mimo mimo że że tak tak naprawdę naprawdę wskazywany wskazywany obiekt obiekt jest jest klasy klasy SilnikTurbo. SilnikTurbo. s->pojemnosc; s->pojemnosc; s->moc; s->moc; s->lbCylindrow; s->lbCylindrow; s->cisnienieDoladowania; s->cisnienieDoladowania; Projektowanie obiektowe | 6 Upcasting czasem oznacza ograniczenie dostępności pól Dostęp do obiektu poprzez wskaźnik jego klasy SilnikTurbo SilnikTurbo SilnikTurbo ** s; s; ss == new new SilnikTurbo; SilnikTurbo; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; s->cisnienieDoladowania s->cisnienieDoladowania == 0.9; 0.9; Silnik Pełny Pełny dostęp dostęp do do własnych własnych pól pól oraz oraz pojemnosc lbCylindrow moc pól pól odziedziczonych. odziedziczonych. ... cisnienieDoladowania ... Dostęp do obiektu poprzez wskaźnik jego klasy bazowej — upcasting SilnikTurbo Silnik Silnik ** s; s; ss == new new SilnikTurbo; SilnikTurbo; s->pojemnosc s->pojemnosc == 2400; 2400; s->moc s->moc == 163; 163; s->lbCylindrow s->lbCylindrow == 5; 5; s->cisnienieDoladowania s->cisnienieDoladowania == 0.9; 0.9; Silnik Dostępne Dostępne tylko tylko pola pola odziedziczone, odziedziczone, czyli czyli jak jak w w klasie klasie bazowej. bazowej. pojemnosc lbCylindrow moc ... cisnienieDoladowania ... Projektowanie obiektowe | 7 Dlaczego upcasting jest potrzebny? Bo jest wygodny! I daje możliwości! void void zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( Silnik Silnik {{ s->pojemnosc s->pojemnosc == 0; 0; s->moc s->moc == 0; 0; s->lbCylindrow s->lbCylindrow == 0; 0; }} void void pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( Silnik Silnik {{ cout cout << << endl endl << << s->pojemnosc; s->pojemnosc; cout cout << << endl endl << << s->moc; s->moc; cout cout << << endl endl << << s->lbCylindrow; s->lbCylindrow; }} .. .. .. Silnik Silnik sil; sil; SilnikTurbo SilnikTurbo silT; silT; SilnikHybryda SilnikHybryda silH; silH; zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( &sil &sil ); ); zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( &silT &silT ); ); zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( &silH &silH ); ); silT.cisnienieDoladowania silT.cisnienieDoladowania == 0; 0; silH.mocElektryczna silH.mocElektryczna == 0; 0; pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( &sil &sil ); ); &silT &silT ); ); &silH &silH ); ); ** ss )) ** ss )) class class Silnik Silnik {{ public: public: int int pojemnosc; pojemnosc; int int lbCylindrow; lbCylindrow; int int moc; moc; }; }; class class SilnikTurbo SilnikTurbo :: public public Silnik Silnik {{ public: public: float float cisnienieDoladowania; cisnienieDoladowania; }; }; class class SilnikHybryda SilnikHybryda :: public public Silnik Silnik {{ public: public: int int mocElektryczna; mocElektryczna; }; }; Projektowanie obiektowe | 8 Upcasting działa również z referencjami void void zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( Silnik Silnik {{ s.pojemnosc s.pojemnosc == 0; 0; s.moc s.moc == 0; 0; s.lbCylindrow s.lbCylindrow == 0; 0; }} void void pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( Silnik Silnik {{ cout cout << << endl endl << << s.pojemnosc; s.pojemnosc; cout cout << << endl endl << << s.moc; s.moc; cout cout << << endl endl << << s.lbCylindrow; s.lbCylindrow; }} .. .. .. Silnik Silnik sil; sil; SilnikTurbo SilnikTurbo silT; silT; SilnikHybryda SilnikHybryda silH; silH; zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( sil sil ); ); zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( silT silT ); ); zerujBazoweDaneSilnika( zerujBazoweDaneSilnika( silH silH ); ); silT.cisnienieDoladowania silT.cisnienieDoladowania == 0; 0; silH.mocElektryczna silH.mocElektryczna == 0; 0; pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( pokazBazoweDaneSilnika( sil sil ); ); silT silT ); ); silH silH ); ); && ss )) && ss )) class class Silnik Silnik {{ public: public: int int pojemnosc; pojemnosc; int int lbCylindrow; lbCylindrow; int int moc; moc; }; }; class class SilnikTurbo SilnikTurbo :: public public Silnik Silnik {{ public: public: float float cisnienieDoladowania; cisnienieDoladowania; }; }; class class SilnikHybryda SilnikHybryda :: public public Silnik Silnik {{ public: public: int int mocElektryczna; mocElektryczna; }; }; Projektowanie obiektowe | 9 Czy jest downcasting? Tak, ale on może być przyczyną klopotów... SilnikTurbo SilnikTurbo silT; silT; s.pojemnosc s.pojemnosc == 2400; 2400; s.moc s.moc == 163; 163; s.lbCylindrow s.lbCylindrow == 5; 5; s.cisnienieDoladowania s.cisnienieDoladowania == 0.9; 0.9; silT : SilnikTurbo pojemnosc lbCylindrow moc cisnienieDoladowania 2400 5 163 0.9 Upcasting: Silnik Silnik ** s; s; ss == &silT; &silT; s->cisnienieDoladowania s->cisnienieDoladowania == 0.5; 0.5; .. .. .. s pojemnosc lbCylindrow moc 2400 pojemnosc lbCylindrow moc 2400 5 163 Downcasting: .. .. .. SilnikTurbo SilnikTurbo ** st; st; st st == (( SilnikTurbo SilnikTurbo ** )s; )s; st->cisnienieDoladowania st->cisnienieDoladowania == 0.5; 0.5; st cisnienieDoladowania 5 163 0.9 Downcasting: Downcasting: konwersja konwersja wskazania wskazania na na obiekt obiekt klasy klasy bazowej bazowej do do wskaźnika wskaźnika na na klasę klasę pochodną. pochodną. Możliwe Możliwe tylko tylko poprzez poprzez konwersję konwersję typów typów wskaźników wskaźników jawnie jawnie zapisaną zapisaną przez przez programistę. programistę. Projektowanie obiektowe | 10 Wskaźniki pozwalają „patrzeć” na obiekt w różny sposób SilnikTurbo SilnikTurbo silT; silT; s.pojemnosc s.pojemnosc == 2400; 2400; s.moc s.moc == 163; 163; s.lbCylindrow s.lbCylindrow == 5; 5; s.cisnienieDoladowania s.cisnienieDoladowania == 0.9; 0.9; silT : SilnikTurbo pojemnosc lbCylindrow moc cisnienieDoladowania Upcasting: Silnik Silnik ** s; s; ss == &silT; &silT; s->cisnienieDoladowania s->cisnienieDoladowania == 0.5; 0.5; .. .. .. 5 163 0.9 Wskaźnik s „widzi” obiekt silT tak: s Downcasting: .. .. .. SilnikTurbo SilnikTurbo ** st; st; st st == (( SilnikTurbo SilnikTurbo ** )s; )s; st->cisnienieDoladowania st->cisnienieDoladowania == 0.5; 0.5; 2400 pojemnosc lbCylindrow moc 2400 5 163 Wskaźnik st „widzi” obiekt silT tak: st pojemnosc lbCylindrow moc cisnienieDoladowania 2400 5 163 0.9 W W tym tym przypadku przypadku downcasting downcasting jest jest bezpieczny bezpieczny — — obiekt obiekt wskazywany wskazywany przez przez ss jest jest rzeczywiście rzeczywiście obiektem obiektem klasy klasy SilnikTurbo SilnikTurbo ii downcasting downcasting „odsłania” „odsłania” przesłonięte przesłonięte pole pole cisnienieDoladowania. cisnienieDoladowania. Projektowanie obiektowe | 11 Ale teraz będą kłopoty... Silnik Silnik sil; sil; s.pojemnosc s.pojemnosc == 2400; 2400; s.moc s.moc == 163; 163; s.lbCylindrow s.lbCylindrow == 5; 5; sil : Silnik pojemnosc lbCylindrow moc Zakotwiczenie wskaźnika: Silnik Silnik ** s; s; ss == &sil; &sil; s->cisnienieDoladowania s->cisnienieDoladowania == 0.5; 0.5; .. .. .. 5 163 Wskaźnik s „widzi” obiekt silT tak: s pojemnosc lbCylindrow moc Downcasting: .. .. .. SilnikTurbo SilnikTurbo ** st; st; st st == (( SilnikTurbo SilnikTurbo ** )s; )s; st->cisnienieDoladowania st->cisnienieDoladowania == 0.5; 0.5; 2400 2400 5 163 Wskaźnik st „widzi” obiekt silT tak: st pojemnosc lbCylindrow moc Tego nie ma! cisnienieDoladowania 2400 5 163 0.9 W W tym tym przypadku przypadku downcasting downcasting jest jest błędny błędny — — obiekt obiekt wskazywany wskazywany przez przez ss nie nie jest jest obiektem obiektem klasy klasy SilnikTurbo SilnikTurbo ii downcasting downcasting produkuje produkuje nieistniejące nieistniejące pole pole cisnienieDoladowania. cisnienieDoladowania. Projektowanie obiektowe | 12 Downcasting wykonywany poprzez dynamic_cast Nie do końca bezpieczne metody konwersji wskaźników: Silnik Silnik ** s; s; SilnikTurbo SilnikTurbo ** st; st; .. .. .. st st == (( SilnikTurbo SilnikTurbo ** )s; )s; st st == static_cast< static_cast< SilnikTurbo SilnikTurbo ** >( >( ss ); ); st st == reinterpret_cast< reinterpret_cast< SilnikTurbo SilnikTurbo ** >( >( ss ); ); Rzutowanie z kontrolą jego poprawności: st st == dynamic_cast< dynamic_cast< SilnikTurbo SilnikTurbo ** >( >( ss ); ); if( if( st st != != 00 )) st->cisnienieDoladowania st->cisnienieDoladowania == 0.5; 0.5; Operator Operator dynamic_cast dynamic_cast pozwala pozwala na na konwersję konwersję wskaźników wskaźników zz kontrolą, kontrolą, czy czy taka taka konwersja konwersja może może być być przeprowadzona. przeprowadzona. Jeżeli Jeżeli możliwa możliwa jest jest konwersja konwersja do do postaci postaci wskaźnika wskaźnika na na klasę klasę pochodną, pochodną, operator operator taką taką konwersję konwersję realizuje. realizuje. Jeżeli Jeżeli konwersja konwersja nie nie może może zostać zostać zrealizowana, zrealizowana, rezultatem rezultatem operatora operatora jest jest wartość wartość zerowa. zerowa. Mechanizm Mechanizm pozwalający pozwalający na na dynamic_cast, dynamic_cast, znany znany jest jest jako jako Run-Time Run-Time Type Type Information Information — — RTTI. RTTI. Projektowanie obiektowe | 13 Polimorfizm w akcji Projektowanie obiektowe | 14 Polimorfizm w akcji Należy napisać program wspomagający pracę technologa w firmie produkującej okna. Zadaniem programu jest: obliczanie łącznego pola powierzchni wszystkich skrzydeł okna, przybliżonej, łącznej długości profili, wymaganych do produkcji każdego ze skrzydeł. Projektowanie obiektowe | 15 Analiza obiektowa Stosując zasadę abstrakcji wyodrębniamy najistotniejsze cechy obiektów dla rozpatrywanego zagadnienia — szyby to figury geometryczne a okno to ich złożenie. Łączna powierzchnia okna to, w przybliżeniu, suma pól figur opisujących szyby, Łączna długość profili to, w przybliżeniu, suma obwodów figur opisujących szyby. Kwadrat Trójkąt Prostokąt Prostokąt Obiekty rzeczywiste Abstrakcyjny model analityczny Projektowanie obiektowe | 16 Analiza obiektowa, cd... Realizacja programu sprowadza się do obliczeń pól powierzchni i obwodów obiektów, będących złożeniem elementarnych figur płaskich. Utworzyliśmy już klasy reprezentujące figury płaskie, Nie wiemy jak reprezentować ich złożenia. Kwadrat Trójkąt Prostokąt Prostokąt Kwadrat Prostokąt Prostokąt Trójkąt Projektowanie obiektowe | 17 Figury płaskie raz jeszcze — budujemy hierarchię klas Rozpoczynamy od utworzenia nieco dziwnej klasy — reprezentującej jakąś abstrakcyjną figurę geometryczną. Wyposażamy ją w funkcje obliczania pola i obwodu. class class Figure Figure {{ public public :: Figure() Figure() {} {} double double area() area() const const {{ return return double double circumference() circumference() const const const const char char ** getName() getName() const const }; }; 0; 0; }} {{ return return {{ return return 0; 0; }} "Figura"; "Figura"; }} Klasa Figure służyć będzie jak klasa bazowa dla specjalizowanych klas pochodnych, reprezentujących konkretne figury geometryczne. Jej istotą jest stwierdzenie, że każda figura geometryczna powinna umieć wyznaczać swoje pole i obwód, w charakterystyczny dla siebie sposób. Zatem każda z klas pochodnych, powinna redefiniować funkcje area i circumference, w odpowiedni dla tych klas sposób. Dodatkowo klasa Figure i jej pochodne posiadać będę funkcję getName(), jej rezultatem będzie nazwa klasy. Projektowanie obiektowe | 18 Klasa Square jako pochodna klasy Figure Klasa reprezentująca kwadrat będzie teraz klasą pochodną klasy Figure: class class Square: Square: public public Figure Figure {{ public public :: Square( Square( double double startSide startSide == 00 ); ); void setSide( void setSide( double double newSide newSide ); ); double double getSide(); getSide(); double double area() area() const; const; double double circumference() circumference() const const char char ** getName() getName() const; const; const; const; private: private: double double side; side; }; }; Redefinicja funkcji area i circumference i getName dla kwadratu: double double Square::area() Square::area() const const {{ return return double double Square::circumference() Square::circumference() const const const const char char ** Square::getName() Square::getName() const const side side ** side; side; }} {{ return return 44 ** side; side; }} {{ return return "Kwadrat"; "Kwadrat"; }} Projektowanie obiektowe | 19 Klasa Rectangle jako pochodna klasy Figure Klasa reprezentująca prostokąt będzie teraz klasą pochodną klasy Figure: class class Rectangle: Rectangle: public public Figure Figure {{ public public :: Rectangle( Rectangle( double double startWidth startWidth == 0, 0, double double startHeight startHeight == 00 ); ); void void setWidth( setWidth( double double newWidth newWidth ); ); void void setHeight( setHeight( double double newHeight newHeight ); ); double getWidth() const; double getWidth() const; double double getHeight() getHeight() const; const; double double area() area() const; const; double double circumference() circumference() const const char char ** getName() getName() const; const; const; const; private: private: double double width, width, height; height; }; }; Redefinicja funkcji area i circumference i getName dla prostokąta: double double Rectangle::area() Rectangle::area() const const {{ return return double double Rectangle::circumference() Rectangle::circumference() const const const char * Rectangle::getName() const const char * Rectangle::getName() const width width ** height; height; }} {{ return return 22 ** width width ++ 22 ** height; height; }} {{ return return "Prostokat"; "Prostokat"; }} Projektowanie obiektowe | 20 Klasa Triangle jako pochodna klasy Figure Klasa reprezentująca trójkąt będzie teraz klasą pochodną klasy Figure: class class Triangle: Triangle: public public Figure Figure {{ public public :: Triangle( Triangle( double double startBase startBase == 0, 0, double double startHeight startHeight == 00 ); ); void void setBase( setBase( double double newBase newBase ); ); void void setHeight( setHeight( double double newHeight newHeight ); ); double getBase() const; double getBase() const; double double getHeight() getHeight() const; const; double double area() area() const; const; double double circumference() circumference() const const char char ** getName() getName() private: private: double double base, base, height; height; }; }; const; const; const; const; Uwaga, Uwaga, zakładamy zakładamy dla dla uproszczenia, uproszczenia, że że trójkątne trójkątne szyby szyby będą będą trójkątami trójkątami prostokątnymi. prostokątnymi. Redefinicja funkcji area i circumference i getName dla trójkąta: double double Triangle::area() Triangle::area() const const {{ return return 0.5 0.5 ** base base ** height; height; }} double double Triangle::circumference() Triangle::circumference() const const {{ return return sqrt( sqrt( base base ** base base ++ height height ** height height )) ++ base base ++ height; height; }} const const char char ** Triangle::getName() Triangle::getName() const const {{ return return "Trojkat"; "Trojkat"; }} Projektowanie obiektowe | 21 Hierarchia klas figur płaskich Figure +double area() +double circumference() +const char * getName() Rectangle Square Triangle +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() ―double height ―double width ―double side ―double base ―double width Projektowanie obiektowe | 22 Krok w stronę polimorfizmu — upcasting z wykorzystaniem wskaźnika Załóżmy, że zdefiniowano wskaźnik do klasy bazowej Figure: Figure Figure ** fp; fp; Załóżmy, że zdefiniowano obiekty klas pochodnych: Square Square Rectangle Rectangle Triangle Triangle s( s( r( r( t( t( 10 10 ); ); 10, 10, 20 20 ); ); 10,10 10,10 ); ); Wiemy już, że można przypisać do wskaźnika fp wskazanie na obiekt klasy pochodnej: fp fp fp fp fp fp == == == &s; &s; &r; &r; &t; &t; // // // // // // OK OK OK OK OK OK Projektowanie obiektowe | 23 Polimorfizm w akcji...? Figure Figure ** fp; fp; Square Square Rectangle Rectangle Triangle Triangle s( s( r( r( t( t( 10 10 ); ); 10, 10, 20 20 ); ); 10,10 10,10 ); ); fp fp == cout cout cout cout cout cout &s; &s; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); fp fp == cout cout cout cout cout cout &r; &r; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); fp fp == cout cout cout cout cout cout &t; &t; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); Ale to nie działa! Obiekty zachowują się tak, jakby były obiektami klasy Figure! Zapomnieliśmy o funkcjach wirtualnych...! Projektowanie obiektowe | 24 Zapomnieliśmy o funkcjach wirtualnych! Jest: class class Figure Figure {{ public public :: Figure() Figure() {} {} double double area() area() const const {{ return return double double circumference() circumference() const const const const char char ** getName() getName() const const }; }; 0; 0; }} {{ return return {{ return return 0; 0; }} "Figura"; "Figura"; }} Powinno być: class class Figure Figure {{ public public :: Figure() Figure() {} {} virtual virtual double double area() area() const const {{ return return virtual virtual double double circumference() circumference() const const virtual virtual const const char char ** getName() getName() const const }; }; 0; 0; }} {{ return return 0; 0; }} {{ return return "Figura"; "Figura"; }} Nie ma polimorfizmu bez metod wirtualnych w klasach tworzących hierarchię dziedziczenia. Projektowanie obiektowe | 25 Wiązanie statyczne i funkcje niewirtualne — raz jeszcze O tym która funkcja jest wywoływana decyduje kompilator na etapie kompilacji. Wywołanie ma taką samą postać jak wywołanie klasycznych funkcji w języku C. O tym która funkcja zostanie wywołana decyduje typ występujący w deklaracji zmiennej wskaźnikowej. Square Figure Figure ** fp; fp; Square Square s( s( 10 10 ); ); fp fp == cout cout cout cout &s; &s; << << fp->area() fp->area() << << endl; endl; << << fp->circumference() fp->circumference() << << endl; endl; Mimo Mimo iż iż klasa klasa Square Square przedefiniowała przedefiniowała obie obie funkcje funkcje i posiada i posiada ich ich własne własne wersje, wersje, nie nie zostaną zostaną one one Figure +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() wywołane. wywołane. ―double side Projektowanie obiektowe | 26 Wiązanie dynamiczne i funkcje wirtualne — raz jeszcze O tym która funkcja jest wywoływana decyduje typ obiektu wskazywanego. Funkcja wiązana dynamicznie musi być zadeklarowana jako wirtualna. Wystarczy, że słowo kluczowe virtual wystąpi w deklaracji klasy bazowej. Square Figure Te Te funkcje funkcje zostały zostały „nadpisane” „nadpisane” w w klasie klasie pochodnej. pochodnej. Figure Figure ** fp; fp; Square Square s( s( 10 10 ); ); fp fp == cout cout cout cout &s; &s; << << fp->area() fp->area() << << endl; endl; << << fp->circumference() fp->circumference() << << endl; endl; Klasa Klasa Square Square przedefiniowała przedefiniowała obie obie funkcje funkcje wirtualne. wirtualne. To To one one właśnie właśnie zostaną zostaną wywołane wywołane aa nie nie funkcje funkcje zz klasy klasy +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() ―double side bazowej. bazowej. Projektowanie obiektowe | 27 Jeszcze raz z funkcjami wirtualnymi... Figure Figure ** fp; fp; Square Square Rectangle Rectangle Triangle Triangle s( s( r( r( t( t( 10 10 ); ); 10, 10, 20 20 ); ); 10,10 10,10 ); ); fp fp == cout cout cout cout cout cout &s; &s; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); fp fp == cout cout cout cout cout cout &r; &r; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); fp fp == cout cout cout cout cout cout &t; &t; << << endl endl << << endl endl << << endl endl << << << << << << "Figura: "Figura: "" Pole: Pole: "" Obwod: Obwod: "" "" "" << << << << << << fp->getName(); fp->getName(); fp->area(); fp->area(); fp->circumference(); fp->circumference(); Polimorfizm = dziedziczenie, redefinicja metod wirtualnych, wskaźniki (referencje) i upcasting. Projektowanie obiektowe | 28 Jak to wszystko wykorzystać w programie „okiennym”? Zadany jest układ okna oraz wymiary jego ościeżnic. Jedna ościeżnica jest kwadratowa (bok 50cm), pozostałe trzy prostokątne (70x50 cm, 50x100 cm i 70x100 cm). Należy wyznaczyć łączną powierzchnię szyb i długość profili. 70 100 50 50 Projektowanie obiektowe | 29 Jak reprezentować informacje o oknie? 50 70 const const int int itemNo itemNo == 4; 4; 50 Figure Figure ** win[ win[ itemNo itemNo ]; ]; 0 1 2 3 100 win Kwadrat Prostokąt 70 Prostokąt 50 70 100 100 50 50 50 Prostokąt Projektowanie obiektowe | 30 Jak to wygląda w pamięci operacyjnej? 0 1 2 3 win Square Rectangle +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() ―double side: ―double height:70 ―double width: 100 50 Rectangle +double area() +double circumference() +const char * getName() ―double height:50 ―double width: 70 Rectangle +double area() +double circumference() +const char * getName() ―double height:50 ―double width: 100 Projektowanie obiektowe | 31 Definiowanie elementów okna, obliczenia, sprzątanie // // Okre Okreśślenie lenie liczby liczby elementów elementów const const int int itemNo itemNo == 4; 4; // // Tablica Tablica wka wkaźźników ników na na elementy elementy okna okna Figure Figure ** win[ win[ itemNo itemNo ]; ]; // // Przydział Przydział pami pamięęci ci dla dla elementów elementów okna okna win[ win[ 00 ]] == new new Square( Square( 50 50 ); ); win[ win[ 11 ]] == new new Rectangle( Rectangle( 50, 50, 70 70 ); ); win[ win[ 22 ]] == new new Rectangle( Rectangle( 50, 50, 100 100 ); ); win[ win[ 33 ]] == new new Rectangle( Rectangle( 70, 70, 100 100 ); ); // // Oblicz Oblicz co co trzeba trzeba calcAndShowWinInfo( calcAndShowWinInfo( ii wyprowadz wyprowadz do do strumienia strumienia wyj wyjśściowego ciowego win, win, 44 ); ); // // Zwolnij Zwolnij pami pamięęćć przydzielon przydzielonąą elementom elementom okna okna for( for( int int ii == 0; 0; ii << itemNo; itemNo; delete delete win[ win[ i++] i++] )) ;; Projektowanie obiektowe | 32 Funkcja calcAndShowWinInfo void void calcAndShowWinInfo( calcAndShowWinInfo( Figure Figure ** window[], window[], int int numOfSash numOfSash )) {{ // // Tutaj Tutaj łłąączna czna powierzchnia powierzchnia szyb szyb ii długo długośśćć profili profili double double totalArea totalArea == 0, 0, totalCircum totalCircum == 0; 0; // // Zliczanie Zliczanie powierzchni powierzchni ii długo długośści ci for( for( int int ii == 0; 0; ii << numOfSash; numOfSash; i++ i++ )) {{ totalArea += totalArea += window[ window[ ii ]->area(); ]->area(); totalCircum totalCircum += += window[ window[ ii ]->circumference(); ]->circumference(); }} }} // // Wyprowadzanie Wyprowadzanie wyników wyników obliczeń obliczeń cout cout << << "Powierzchnia "Powierzchnia szyb szyb :: "" << << cout :: "" << cout << << "Dlugosc "Dlugosc profili profili << cout cout << << endl; endl; totalArea totalArea << << endl; endl; totalCircum totalCircum << << endl; endl; Projektowanie obiektowe | 33 Iteracja krok po kroku for( for( int int ii == 0; 0; ii << numOfSash; numOfSash; i++ i++ )) {{ totalArea += totalArea += window[ window[ ii ]->area(); ]->area(); totalCircum totalCircum += += window[ window[ ii ]->circumference(); ]->circumference(); }} 0 1 2 i 0 3 win Rectangle Square +double area() +double circumference() +double area() +double circumference() ―double side: 50 double double Square::area() Square::area() const const ―double height:70 {{ ―double width: 100 return return side side ** side; side; }} Rectangle Rectangle double double Square::circumference() Square::circumference() const const {{ +double area() +double area() return 44 ** side; +double circumference() return side; +double circumference() }} ―double height:50 ―double height:50 ―double width: 70 ―double width: 100 Projektowanie obiektowe | 34 Iteracja krok po kroku for( for( int int ii == 0; 0; ii << numOfSash; numOfSash; i++ i++ )) {{ totalArea += totalArea += window[ window[ ii ]->area(); ]->area(); totalCircum totalCircum += += window[ window[ ii ]->circumference(); ]->circumference(); }} 0 1 2 i 1 3 win Rectangle Square +double area() +double area() +double circumference() ―double side: 50 Rectangle double +doubleconst circumference() double Rectangle::area() Rectangle::area() const {{ ―double height:70 return return width width ** height; height; ―double width: 100 }} double double Rectangle::circumference() Rectangle::circumference() const Rectangle const {{ return return 22 ** width width ++ 22 ** height; height; +double area() }} +double area() +double circumference() +double circumference() ―double height:50 ―double width: 70 ―double height:50 ―double width: 100 Projektowanie obiektowe | 35 Iteracja krok po kroku for( for( int int ii == 0; 0; ii << numOfSash; numOfSash; i++ i++ )) {{ totalArea += totalArea += window[ window[ ii ]->area(); ]->area(); totalCircum totalCircum += += window[ window[ ii ]->circumference(); ]->circumference(); }} 0 1 2 i 2 3 win Rectangle Square +double area() double Rectangle::area() +double circumference() double Rectangle::area() const const +double area() +double circumference() return ―double side: width 50 return width ** height; height; ―double height:70 ―double width: 100 {{ }} double double Rectangle::circumference() Rectangle::circumference() Rectangle const { const { return return 22 ** width width ++ 22 ** height; height; +double area() }} +double circumference() ―double height:50 ―double width: 70 Rectangle +double area() +double circumference() ―double height:50 ―double width: 100 Projektowanie obiektowe | 36 Iteracja krok po kroku for( for( int int ii == 0; 0; ii << numOfSash; numOfSash; i++ i++ )) {{ totalArea += totalArea += window[ window[ ii ]->area(); ]->area(); totalCircum totalCircum += += window[ window[ ii ]->circumference(); ]->circumference(); }} 0 1 2 i 3 3 win Rectangle Square +double area() +double circumference() +double area() +double circumference() ―double side: ―double height:70 ―double width: 100 50 double double Rectangle::area() Rectangle::area() const const {{ return width Rectangle Rectangle return width ** height; height; }} +double area() +double area() double Rectangle::circumference() double Rectangle::circumference() +double circumference() +double circumference() const const {{ ―double height:50 height:50 return 22 ** width ** height; return width ++ 22―double height; ―double width: 70 ―double width: 100 }} Projektowanie obiektowe | 37 Klasy abstrakcyjne C++ Projektowanie obiektowe | 38 Figury płaskie raz jeszcze — klasa abstrakcyjna Klasa bazowa, określająca protokół rozmowy z obiektami klas pochodnych, powinna być klasą abstrakcyjną: class class Figure Figure {{ public public :: Figure() Figure() {} {} virtual virtual virtual virtual }; }; double double double double Funkcje Funkcje abstrakcyjne abstrakcyjne (ang. (ang. pure pure virtual virtual functions). functions). Muszą Muszą zostać zostać zdefiniowane zdefiniowane w w każdej każdej nieabstrakcyjnej nieabstrakcyjnej klasie klasie pochodnej. pochodnej. area() area() const const == 0; 0; circumference() circumference() const const == 0; 0; const const char char ** getName() getName() const const {{ return return "Figura"; "Figura"; }} Służy ona jak wzorcowa klasa bazowa dla specjalizowanych klas pochodnych, reprezentujących konkretne figury geometryczne. Każda pochodna klasa nieabstrakcyjna musi zdefiniować własną wersją czystej funkcji wirtualnej. Projektowanie obiektowe | 39 Figury płaskie raz jeszcze — klasa abstrakcyjna, cd. ... class class Figure Figure {{ public public :: Figure() Figure() {} {} virtual virtual double double area() area() const const == 0; 0; virtual virtual double double circumference() circumference() const const == 0; 0; const const char char ** getName() getName() const const {{ return return "Figura"; "Figura"; }} }; }; Nie można zdefiniować obiektów abstrakcyjnej klasy Figure. Figure Figure f; f; // // Nie Nie definiujemy definiujemy obiektów obiektów klas klas abstrakcyjnych abstrakcyjnych Wolno jednak posługiwać się wskaźnikami i referencjami do klasy Figure. Figure Figure ** f; f; // // OK OK .. .. .. void void showFigureInfo( showFigureInfo( Figure Figure && fp fp )) // // OK OK {{ .. .. .. }} Projektowanie obiektowe | 40 Klasy pochodne muszą dostarczyć definicji funkcji abstrakcyjnych Figure +double area() = 0 +double circumference() = 0 +const char * getName() = 0 Rectangle Square Triangle +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() +double area() +double circumference() +const char * getName() ―double height ―double width ―double side ―double base ―double width Projektowanie obiektowe | 41 Klasy pochodne muszą dostarczyć definicji funkcji abstrakcyjnych class class Figure Figure {{ public public :: .. .. .. virtual virtual double double virtual virtual double double .. .. .. }; }; area() area() const const == 0; 0; circumference() circumference() const const == 0; 0; class class Square: Square: public public Figure Figure {{ public public :: .. .. .. Deklaracje double double area() area() const; const; funkcji double double circumference() circumference() const; const; .. .. .. }; }; double double Square::area() Square::area() const const {{ return return side side ** side; side; }} double double Square::circumference() Square::circumference() const const {{ return return 44 ** side; side; }} Deklaracje funkcji Projektowanie obiektowe | 42 Klasy abstrakcyjne Java Projektowanie obiektowe | 43 Figury płaskie raz jeszcze — klasa abstrakcyjna abstract abstract class class Figure Figure {{ .. .. .. }} Figure Figure == new new Figure(); Figure(); // // Nie Nie tworzymy tworzymy obiektów obiektów klas klas abstrakcyjnych abstrakcyjnych Klasa abstrakcyjna: może posiadać implementację wybranych metod. może posiadać metody abstrakcyjne. może posiadać pola. Nie tworzymy obiektów klas abstrakcyjnych. Projektowanie obiektowe | 44 Figury płaskie raz jeszcze — klasa abstrakcyjna abstract abstract class class Figure Figure {{ public public Figure() Figure() {} {} Metody Metody abstrakcyjne. abstrakcyjne. Muszą Muszą zostać zostać zdefiniowane zdefiniowane w w każdej każdej nieabstrakcyjnej nieabstrakcyjnej klasie klasie pochodnej. pochodnej. public public abstract abstract double double area(); area(); public public abstract abstract double double circumference(); circumference(); public public String String getName() getName() {{ return return "Figura"; "Figura"; }} }} static static int int lbFigur; lbFigur; Zwykła Zwykła metoda, metoda, w w klasach klasach abstrakcyjnych abstrakcyjnych mogą mogą występować występować zarówno zarówno metody metody abstrakcyjne abstrakcyjne jak jak ii zwykłe. zwykłe. Metody abstrakcyjne deklarujemy z wykorzystaniem słowa kluczowego abstract. Zapisujemy tylko sygnaturę metody, nie definiujemy jej ciała (nawet pustego). Klasa Klasa abstrakcyjna abstrakcyjna pozwala pozwala na na częściowe częściowe zdefiniowanie zdefiniowanie czynności czynności realizowanych realizowanych przez przez klasę, klasę, pozwalając pozwalając na na późniejsze późniejsze uszczegółowienie uszczegółowienie czynności czynności reprezentowanych reprezentowanych przez przez metody metody abstrakcyjne. abstrakcyjne. Projektowanie obiektowe | 45 Implementacja metody abstrakcyjne z klasie pochodnej class class Square Square extends extends Figure Figure {{ public public Square() Square() {{ super(); super(); }} @Override @Override public public double double area() area() {{ return return side side ** side; side; }} }} @Override @Override public public double double circumference() circumference() {{ return return 44 ** side; side; }} .. .. .. Aby Aby można można było było tworzyć tworzyć obiekty obiekty klasy klasy pochodnej, pochodnej, każda każda metoda metoda abstrakcyjna abstrakcyjna musi musi być być w w klasie klasie pochodnej pochodnej zaimplementowana. zaimplementowana. @Override @Override –– adnotacja adnotacja sygnalizująca sygnalizująca intencję intencję programisty programisty przedefiniowania przedefiniowania metody metody zz klasy klasy bazowej, bazowej, pozwala pozwala na na sprawdzenia sprawdzenia w w czasie czasie kompilacji kompilacji czy czy programista programista nie nie popełnił popełnił błędu, błędu, Projektowanie obiektowe | 46 Rola klas abstrakcyjnych ― częściowa definicja działania abstract abstract class class Party Party {{ public public enum enum State State {{ NONE, NONE, BEFORE, BEFORE, IN_PROGRESS, IN_PROGRESS, AFTER AFTER }; }; public public Party() Party() {{ state state == State.NONE; State.NONE; }} Działanie Działanie tej tej metody metody zostanie zostanie uszczegółowione uszczegółowione public void beforeParty() { public void beforeParty() { w w klasie klasie pochodnej. pochodnej. state state == State.BEFORE; State.BEFORE; }} public public abstract abstract void void makeParty(); makeParty(); public public void void afterParty() afterParty() {{ state state == State.AFTER; State.AFTER; }} public public void void doParty() doParty() {{ beforeParty(); beforeParty(); makeParty(); makeParty(); afterParty(); afterParty(); }} }} Ogólny Ogólny przepis przepis na na realizację realizację zachowania zachowania obiektu obiektu ― ― wymaga wymaga doprecyzowania doprecyzowania makeParty() makeParty() public public State State state; state; Projektowanie obiektowe | 47 Dziedziczenie z klas abstrakcyjnych ― doprecyzowanie działania class class GardenParty GardenParty extends extends Party Party {{ public public GardenParty() GardenParty() {{ super(); super(); }} @Override @Override public public void void {{ state state == }} }} makeParty() makeParty() State.IN_PROGRESS; State.IN_PROGRESS; prepareGrill(); prepareGrill(); for( for( ;; ;; )) openBeer(); openBeer(); public public void void prepareGrill() prepareGrill() {} {} public public void void openBeer() openBeer() {} {} Doprecyzowanie Doprecyzowanie metody metody abstrakcyjnej abstrakcyjnej Projektowanie obiektowe | 48 Klasy abstrakcyjne C# Projektowanie obiektowe | 49 Definicja klasy abstrakcyjnej w C# jest zbliżona do znanej z języka Java Metody Metody abstrakcyjne. abstrakcyjne. Muszą Muszą zostać zostać zdefiniowane zdefiniowane w w każdej każdej nieabstrakcyjnej nieabstrakcyjnej klasie klasie pochodnej. pochodnej. abstract abstract class class Figure Figure {{ public public Figure() Figure() {} {} public public abstract abstract double double area(); area(); public public abstract abstract double double circumference(); circumference(); public public String String getName() getName() {{ return return "Figura"; "Figura"; }} }} static static int int lbFigur; lbFigur; Mogą Mogą występować występować pola pola statyczne statyczne ii niestatyczne niestatyczne Zwykła Zwykła metoda, metoda, w w klasach klasach abstrakcyjnych abstrakcyjnych mogą mogą występować występować zarówno zarówno metody metody abstrakcyjne abstrakcyjne jak jak ii zwykłe. zwykłe. Projektowanie obiektowe | 50 Definicja klasy pochodnej ― różnice w stosunku do języka Java class class Square Square :: Figure Figure {{ public public Square() Square() :: base() base() {{ }} public public override override {{ return return side side }} }} Przypomnienie Przypomnienie ― ― dziedziczenie dziedziczenie przypomina przypomina bardziej bardziej C++ C++ niż niż Javę Javę double double area() area() ** side; side; public public override override double double circumference() circumference() {{ return return 44 ** side; side; }} .. .. .. Każda Każda metoda metoda abstrakcyjna abstrakcyjna musi musi być być w w klasie klasie pochodnej pochodnej zaimplementowana zaimplementowana zz wykorzystaniem wykorzystaniem słowa słowa kluczowego kluczowego override. override. Projektowanie obiektowe | 51 Rola klas abstrakcyjnych ― częściowa definicja działania abstract abstract class class Party Party {{ public public enum enum State State {{ NONE, NONE, BEFORE, BEFORE, IN_PROGRESS, IN_PROGRESS, AFTER AFTER }; }; public public Party() Party() {{ state state == State.NONE; State.NONE; }} Działanie Działanie tej tej metody metody zostanie zostanie uszczegółowione uszczegółowione public void beforeParty() { public void beforeParty() { w w klasie klasie pochodnej. pochodnej. state state == State.BEFORE; State.BEFORE; }} public public abstract abstract void void makeParty(); makeParty(); public public void void afterParty() afterParty() {{ state state == State.AFTER; State.AFTER; }} public public void void doParty() doParty() {{ beforeParty(); beforeParty(); makeParty(); makeParty(); afterParty(); afterParty(); }} }} Ogólny Ogólny przepis przepis na na realizację realizację zachowania zachowania obiektu obiektu ― ― wymaga wymaga doprecyzowania doprecyzowania makeParty() makeParty() public public State State state; state; Projektowanie obiektowe | 52 Interfejsy Po co? Projektowanie obiektowe | 53 Rola interfejsów w obiektowości Klasa reprezentuje szablon, według którego tworzony będzie obiekt. Klasa definiuje atrybuty i/lub metody w które taki obiekt będzie wyposażony. Nawet klasa abstrakcyjna docelowo służy do definiowania obiektów. Klasy Klasy są są narzędziem narzędziem modelowania modelowania ii definiowania definiowania obiektów obiektów w w systemie. systemie. Czasem Czasem potrzebujemy potrzebujemy modelować modelować potencjalne potencjalne zachowania zachowania obiektów, obiektów, często często w w oderwaniu oderwaniu od od nich nich samych. samych. Do Do modelowania modelowania zachowań zachowań wykorzystujemy wykorzystujemy interfejsy. interfejsy. Interfejsy nie są po to, by definiować obiekty. Interfejsy są po to, by definiować zestaw zachowań. Obiekt pewnej klasy może implementować interfejs — realizować zachowania określone przez dany interfejs. Interfejsy zazwyczaj nie zawierają pól. Projektowanie obiektowe | 54 Rola interfejsów w obiektowości Gdy pewna klasa wykorzystuje interfejs, to oznacza, że gwarantuje obsługę metody zadeklarowanej w tym interfejsie. Metody interfejsu deklaruje się bez żadnej treści, konkretna definicja metody w interfejsie nie jest dozwolona. Interfejs przypomina nieco klasę abstrakcyjną, posiadającą wszystkie metody abstrakcyjne. Klasy abstrakcyjne bywają wykorzystywane –np. w C++ – do realizacji interfejsów nie występujących jawnie w języku. Ważna różnica – klasa pochodna abstrakcyjnej klasy bazowej może zmieniać widoczność metod odziedziczonych, jeśli jakaś klasa implementuje interfejs, to wówczas musi udostępniać wszystkie metody zdefiniowane w tym interfejsie. Projektowanie obiektowe | 55 Interfejsy w C++ W języku C++ nie występuje osobna notacja dla interfejsów. Wykorzystanie koncepcji interfejsów wymaga zastosowania klas zawierających funkcje abstrakcyjne. Występujące w C++ dziedziczenie wielobazowe pozwala na swobodne budowanie klas posiadających wiele bezpośrednich klas bazowych. Interfejsy występują zazwyczaj w językach nie oferujących dziedziczenia wielobazowego. Jawnie wodrębnione interfejsy występują w językach Java, C#, PHP. Niektóre Niektóre kompilatory kompilatory (np. (np. VC++) VC++) wprowadzają wprowadzają własne własne rozszerzenia rozszerzenia do do C++ C++ oferujące oferujące mechanizm mechanizm podobny podobny do do interfejsów interfejsów zz języka języka Java Java ii C#. C#. Projektowanie obiektowe | 56 Interfejsy Java Projektowanie obiektowe | 57 Przykłady interfejsu interface interface BasicCalculations BasicCalculations {{ double double area(); area(); double double circumference(); circumference(); }} Domyślnie Domyślnie publicznie publicznie ii abstrakcyjne abstrakcyjne Wszystkie metody interfejsu są domyślnie publiczne i abstrakcyjne. Metody interfejsów nie mogą być statyczne (static) ani zakończone (final). interface interface GearBoxActions GearBoxActions {{ int int gearUp(); gearUp(); int int gearDown(); gearDown(); }} int int numberOfGears numberOfGears == 6; 6; Domyślnie Domyślnie publiczne, publiczne, statyczne statyczne ii zakończone. zakończone. Interfejs może zawierać pola, są one wtedy domyślnie publiczne, statyczne i finalne. Wszystkie klasy, które kiedyś zaimplementują interfejs, będą miały zawsze bezpośredni dostęp do tych samych, stałych wartości pól. Projektowanie obiektowe | 58 Przykłady wykorzystania interfejsu class class Driver Driver implements implements GearBoxActions GearBoxActions {{ public public Driver() Driver() {} {} @Override @Override public public int int gearUp() gearUp() {{ if( if( currentGear currentGear << numberOfGears numberOfGears ++currentGear; ++currentGear; return return currentGear; currentGear; }} @Override @Override public public int int gearDown() gearDown() {{ if( if( currentGear currentGear >> 00 )) --currentGear; --currentGear; return return currentGear; currentGear; }} }} Klasa Klasa implementuje implementuje dany dany interfejs interfejs )) Implementacje Implementacje metod metod interfejsu interfejsu muszą muszą być być publiczne publiczne public public int int currentGear currentGear == 0; 0; Projektowanie obiektowe | 59 Implementacja wielu interfejsów Interfejs definiujący akcje kontrolujące skrzynię biegów. interface interface GearBoxActions GearBoxActions {{ int int gearUp(); gearUp(); int int gearDown(); gearDown(); }} Interfejs definiujący akcje kontrolujące prędkość (hamulec, przyśpieszanie). interface interface SpeedControl SpeedControl {{ void void pressBreak(); pressBreak(); void void releaseBreak(); releaseBreak(); }} void void accelerate(); accelerate(); void void slowDown(); slowDown(); Projektowanie obiektowe | 60 Implementacja wielu interfejsów class class Driver Driver implements implements GearBoxActions, GearBoxActions, SpeedControl SpeedControl {{ public public Driver() Driver() {} {} @Override @Override public public int int gearUp() gearUp() {{ …… }} Implementacja Implementacja dwóch dwóch interfejsów interfejsów @Override @Override public public int int gearDown() gearDown() {{ …… }} @Override @Override public public void void pressBreak() pressBreak() {{ …… }} @Override @Override public public void void releaseBreak() releaseBreak() {{ …… }} @Override @Override public public void void accelerate() accelerate() {{ …… }} }} @Override @Override public public void void slowDown() slowDown() {{ …… }} Projektowanie obiektowe | 61 Implementacja interfejsów, zasady ogólne Nieabstrakcyjna klasa implementująca interfejs: Musi posiadać implementację wszystkich metod interfejsu, Implementowane metody muszą zachować sygnaturę metod z interfejsu, Implementowane metody muszą być zdefiniowane jako publiczne. Klasa może implementować więcej niż jeden interfejs. Nie można definiować konstruktora i destruktora wewnątrz interfejsu. Interfejsy zwyczajowo definiuje rozpoczynając ich nazwę od litery „I”. interface interface IGearBoxActions IGearBoxActions {{ .. .. .. }} interface interface ISpeedControl ISpeedControl {{ .. .. .. }} Projektowanie obiektowe | 62 Rozszerzanie interfejsów interface interface SpeedControl SpeedControl {{ void void pressBreak(); pressBreak(); void void releaseBreak(); releaseBreak(); }} void void accelerate(); accelerate(); void void slowDown(); slowDown(); interface interface BoostedSpeedControl BoostedSpeedControl extends extends SpeedControl SpeedControl {{ void void nitroBoosterOn(); nitroBoosterOn(); void void nitroBoosterOff(); nitroBoosterOff(); }} Interfejsy mogą rozszerzać inne interfejsy. Interfejs nie może implementować innego interfejsu. Projektowanie obiektowe | 63 Rozszerzać można wiele interfejsów interface interface GearBoxActions GearBoxActions {{ int int gearUp(); gearUp(); int int gearDown(); gearDown(); }} interface interface SpeedControl SpeedControl {{ void void pressBreak(); pressBreak(); void void releaseBreak(); releaseBreak(); }} void void void void accelerate(); accelerate(); slowDown(); slowDown(); interface interface BasicDriverActions BasicDriverActions extends extends GearBoxActions, GearBoxActions, SpeedControl SpeedControl {{ void void start(); start(); void void stop(); stop(); }} Należy Należy pamiętać, pamiętać, że że ostatecznie ostatecznie jakaś jakaś klasa klasa musi musi zaimplementować zaimplementować metody metody zadeklarowane zadeklarowane w w poszczególnych poszczególnych interfejsach. interfejsach. Projektowanie obiektowe | 64 Implementacja złożonych interfejsów class class BasicDriver BasicDriver implements implements BasicDriverActions BasicDriverActions {{ @Override @Override public public int int gearUp() gearUp() {{ …… }} @Override @Override public public int int gearDown() gearDown() {{ …… }} @Override @Override public public void void pressBreak() pressBreak() {{ …… }} @Override @Override public public void void releaseBreak() releaseBreak() {{ …… }} @Override @Override public public void void accelerate() accelerate() {{ …… }} @Override @Override public public void void slowDown() slowDown() {{ …… }} @Override @Override public public void void start() start() {{ …… }} }} @Override @Override public public void void stop() stop() {{ …… }} Elastycznym Elastycznym ii wygodnym wygodnym rozwiązaniem rozwiązaniem jest jest połączenie połączenie koncepcji koncepcji interfejsów interfejsów ii klas klas abstrakcyjnych. abstrakcyjnych. Projektowanie obiektowe | 65 Klasy abstrakcyjne + interfejsy abstract abstract class class BasicDriver BasicDriver implements implements SpeedControl SpeedControl {{ public public String String nickName nickName == ""; ""; public public boolean boolean avaliable avaliable == true; true; }} class class Driver Driver extends extends BasicDriver BasicDriver implements implements GearBoxActions GearBoxActions @Override @Override public public int int gearUp() gearUp() {{ …… }} {{ @Override @Override public public int int gearDown() gearDown() {{ …… }} @Override @Override public public void void pressBreak() pressBreak() {{ …… }} @Override @Override public public void void releaseBreak() releaseBreak() {{ …… }} @Override @Override public public void void accelerate() accelerate() {{ …… }} }} @Override @Override public public void void slowDown() slowDown() {{ …… }} Projektowanie obiektowe | 66 Nie zawsze klasy abstrakcyjne są użyteczne class class BasicDriver BasicDriver implements implements @Override @Override public public void void pressBreak() pressBreak() SpeedControl SpeedControl {{ {{ …… }} @Override @Override public public void void releaseBreak() releaseBreak() {{ …… }} @Override @Override public public void void accelerate() accelerate() {{ …… }} @Override @Override public public void void slowDown() slowDown() {{ …… }} }} public public public public String String nickName nickName == boolean boolean avaliable avaliable ""; ""; == true; true; class class Driver Driver extends extends BasicDriver BasicDriver implements implements GearBoxActions GearBoxActions @Override @Override public public int int gearUp() gearUp() {{ …… }} }} {{ @Override @Override public public int int gearDown() gearDown() {{ …… }} Projektowanie obiektowe | 67 Jeszcze raz przykład z figurami abstract abstract class class Figure Figure implements implements BasicCalculations BasicCalculations {{ public public Figure() Figure() {} {} }} public public String String getName() getName() {{ return return "Figura"; "Figura"; }} Klasa Klasa abstrakcyjne abstrakcyjne wykorzystuje wykorzystuje interfejs, interfejs, ale ale może może go go w w rzeczywistości rzeczywistości nie nie implementować implementować — — musi musi to to zrobić zrobić pierwsza pierwsza nieabstrakcyjna nieabstrakcyjna klasa klasa pochodna pochodna Abstrakcyjna klasa słowem kluczowym implements sygnalizuje, że wykorzystuje określony interfejs, choć w istocie może go nie implementować w pełni. Obowiązek implementacji metod interfejsu spada na klasy pochodne, każda nieabstrakcyjna klasa pochodna musi posiadać pełną implementację interfejsu. Projektowanie obiektowe | 68 Jeszcze raz przykład z figurami class class Square Square extends extends Figure Figure {{ public public Square() Square() {{ super(); super(); }} Dziedziczenie Dziedziczenie po po klasie klasie abstrakcyjnej, abstrakcyjnej, która która nie nie zdefiniowała zdefiniowała interfejsu, interfejsu, wymagana wymagana implementacja implementacja wszystkich wszystkich metod metod interfejsu interfejsu @Override @Override public public double double area() area() {{ return return side side ** side; side; }} @Override @Override public public double double circumference() circumference() {{ return return 44 ** side; side; }} }} public public int int side side == 0; 0; Projektowanie obiektowe | 69 Jeszcze raz przykład z figurami class class Circle Circle extends extends Figure Figure {{ public public Circle() Circle() {{ super(); super(); }} @Override @Override public public double double area() area() {{ return return Math.PI Math.PI ** radius radius ** radius; radius; }} @Override @Override public public double double circumference() circumference() {{ return return 22 ** Math.PI Math.PI ** radius; radius; }} }} public public int int radius radius == 0; 0; Projektowanie obiektowe | 70 Jeszcze raz przykład z figurami — interfejs jako obiekt class class FigDemoInterface FigDemoInterface {{ public public static static void void {{ .. .. .. Square Square ss == new new Circle Circle cc == new new }} }} main(String[] main(String[] args) args) Square(); Square(); Circle(); Circle(); BasicCalculations BasicCalculations calc calc == s; s; System.out.println( System.out.println( calc.area() calc.area() ); ); calc calc == c; c; System.out.println( System.out.println( calc.area() calc.area() ); ); .. .. .. Uaktywnienie Uaktywnienie zachowania zachowania obiektu obiektu za za pośrednictwem pośrednictwem obiektu obiektu interfejsowego interfejsowego –– Projektowanie obiektowe | 71 Jeszcze raz przykład z figurami — funkcja bazująca na zachowaniu class class FigDemoInterface FigDemoInterface {{ public public static static void void calcAndPrintAreas( calcAndPrintAreas( BasicCalculations BasicCalculations bc bc )) {{ System.out.println( System.out.println( bc.area() bc.area() ); ); }} public public static static void void {{ .. .. .. Square Square ss == new new Circle Circle cc == new new }} }} main(String[] main(String[] args) args) Square(); Square(); Circle(); Circle(); calcAndPrintAreas( calcAndPrintAreas( calcAndPrintAreas( calcAndPrintAreas( .. .. .. ss cc ); ); ); ); Interfejsy Interfejsy pozwalają pozwalają na na tworzenie tworzenie kodu kodu bazującego bazującego na na zachowaniu zachowaniu obiektów obiektów aa nie nie na na ich ich typie typie — — przynależności przynależności do do zadanej zadanej hierarchii hierarchii klas. klas. Projektowanie obiektowe | 72 Interfejsy C# Właściwe podobnie jak w języku Java... Projektowanie obiektowe | 73 Interfejsy w C# — podstawowe informacje Nie można definiować pól w interfejsie, nawet pól statycznych. Nie można definiować konstruktora i destruktora wewnątrz interfejsu. Wszystkie metody interfejsu są publiczne. Interfejs może zawierać właściwości. Nie można zagnieździć klasy, struktury, typów wyliczeniowych i innych interfejsów wewnątrz interfejsu. Interfejs nie może dziedziczyć po klasie. Interfejs może dziedziczyć zachowanie po innym interfejsie. Projektowanie obiektowe | 74 Interfejsy w C# — podstawowe informacje class class Driver Driver :: GearBoxActions GearBoxActions {{ public public Driver() Driver() {} {} public public int int gearUp() gearUp() {{ if( if( currentGear currentGear << numberOfGears numberOfGears ++currentGear; ++currentGear; return return currentGear; currentGear; }} Klasa Klasa implementuje implementuje dany dany interfejs interfejs )) public public int int gearDown() gearDown() {{ if( if( currentGear currentGear >> 00 )) --currentGear; --currentGear; return return currentGear; currentGear; }} }} public public int int currentGear currentGear == 0; 0; Projektowanie obiektowe | 75