Principy OOP - rozhraní, dědičnost
1
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Co nás dnes čeká… Motivace pro a realizace rozhraní Dědičnost a kompozice Operátor reference &
2
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Rozhraní
3 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Motivace pro rozhraní Může Microsoft/Torvalds vyvinout operační systém bez znalosti konkrétního hardware na kterém bude provozován? ● ● ● ● ●
Tiskárny Harddisk CPU Grafická karta …
4
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Motivace pro rozhraní
5
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Existující kód int main() { IPrinter* printer = GetSelectedPrinter(); printer->PrintDocument(); printer->GetPendingDocuments(); return 0; }
Nový kód IPrinter* GetSelectedPrinter() { // user selects printer via GUI // e.g., InkPrinter -> selectedPrinter // e.g., LaserPrinter -> selectedPrinter return selectedPrinter; }
7
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Rozhraní a implementace 1. Deklarace rozhraní (abstraktní třída, obecná tiskárna) ● Virtuální metody (virtual) ● Virtuální destruktor
2. Implementace konkrétní třídy (konkrétní tiskárna) ● Dědičnost ● Implementace metod rozhraní (obecné tiskárny)
3. Použití konkrétní instance (konkrétní tiskárna) ● Vytvoření instance konkrétní třídy, dynamická alokace ● Přetypování potomka na předka (rozhraní) ● Použití konkrétní tiskárny skrze rozhraní (obecná tiskárna)
4. Zrušení instance (konkrétní tiskárna) ● Hodí se nám virtuální destruktor PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
8 PB161
1. Deklarace rozhraní
Jméno rozhraní (velké ‘I’ před jménem je pouze konvence)
Klíčové slovo virtual umožní potomkům poskytnout vlastní implementaci metod rozhraní.
class IPrinter { public: virtual string GetPendingDocuments() const = 0; virtual void PrintDocument(const string& document) = 0; virtual ~IPrinter() {} };
Rozhraní by mělo vždy poskytovat virtuální destruktor tak, aby potomci mohli implementovat vlastní ‘úklid’, pokud potřebují. Prázdná implementace {} je poskytnuta proto, aby potomek nebyl nucen destruktor implementovat, pokud to nepotřebuje. PB161 | Principy OOP - Dědičnost, rozhraní
= 0 označuje deklaraci funkce, u které nebude poskytnuta implementace. Implementaci poskytují potomci.
29.9.2014
PB161
2. Implementace konkrétní třídy class CHPDeskJet5550 : public IPrinter { string m_printedDocument; public: CHPDeskJet5550() {}
Konkrétní třída CHPDeskJet5550 dědí z rozhraní IPrinter (tzv. implementuje rozhraní). V případě metod rozhraní = 0 musí poskytnout implementaci, jinak nelze tvořit instance.
virtual string GetPendingDocuments() const { Konkrétní implementace metod return m_printedDocument; z rozhraní (předka) } virtual void PrintDocument(const string& document) { m_printedDocument = document; } Třída může poskytnout vlastní destruktor s vlastním ‘úklidem’
~CHPDeskJet5550() { cout << "~CHPDeskJet5550() called"; } };
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
3. Použití konkrétní instance
Vytvoření proměnné printer1 typu CHPDeskJet5550 na zásobníku a její použití
// HPDeskJet5550 printer, allocation on stack
CHPDeskJet5550 printer1; printer1.PrintDocument("secret");
Dynamická alokace proměnné printer2 na haldě
// Create new HPDeskJet5550 printer, dynamic allocation on heap
CHPDeskJet5550* printer2 = new CHPDeskJet5550(); printer2->PrintDocument("secret"); // Create new HPDeskJet5550 printer, retype to base class (interface)
IPrinter* printer3 = new CHPDeskJet5550(); printer3->PrintDocument("secret"); cout << printer3->GetPendingDocuments() << endl;
Přetypování dynamicky alokovaného objektu typu CHPDeskJet5550 na typ předka - rozhraní
// printer1 is automatically removed when function ends // dealloaction of instance CHPDeskJet5550 // destructor CHPDeskJet5550::~CHPDeskJet5550 is called
delete printer2;
Použití objektu konkrétní tiskárny jen prostřednictvím rozhraní
// destructor CHPDeskJet5550::~CHPDeskJet5550 is called, // because IPrinter::~IPrinter is virtual destructor
delete printer3;
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
4. Zrušení instance // HPDeskJet5550 printer, allocation on stack
CHPDeskJet5550 printer1; printer1.PrintDocument("secret"); // Create new HPDeskJet5550 printer, dynamic allocation on heap
CHPDeskJet5550* printer2 = new CHPDeskJet5550(); printer2->PrintDocument("secret"); // Create new HPDeskJet5550 printer, retype to base class (interface)
IPrinter* printer3 = new CHPDeskJet5550(); printer3->PrintDocument("secret"); cout << printer->GetPendingDocuments() << endl;
Objekt printer1 na zásobníku se zruší automaticky při konci funkce
// printer1 is automatically removed when function ends
Dealokace dynamicky alokovaných objektů z haldy
// dealloaction of instance CHPDeskJet5550 // destructor CHPDeskJet5550::~CHPDeskJet5550 is called
delete printer2; // destructor CHPDeskJet5550::~CHPDeskJet5550 is called, // because IPrinter::~IPrinter is virtual destructor
delete printer3;
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
Objekt printer3 je typu IPrinter, díky virtuálnímu destruktoru se zavolá i destruktor třídy CHPDeskJet5550
PB161
Rozhraní a implementace (rekap.) 1. Deklarace rozhraní (abstraktní třída, obecná tiskárna) ● Virtuální, čistě abstraktní metody
2. Implementace konkrétní třídy (konkrétní tiskárna) ● Dědičnost, Polymorfismus, Zapouzdření ● Implementace metod rozhraní (obecné tiskárny)
3. Použití konkrétní instance (konkrétní tiskárna) ● Přetypování potomka na předka (rozhraní) ● Použití konkrétní tiskárny prostřednictvím rozhraní ● Zapouzdření
4. Zrušení instance (konkrétní tiskárna) ● delete, virtuální destruktor 13
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost
14
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost v OOP Dědičnost je nástroj pro podporu abstrakce ● potomci dodržují rozhraní zavedené předkem ● mohou ale měnit chování (implementaci) ● můžeme mít generický kód, který bude pracovat s budoucími implementacemi
Dědičnost je nástroj pro omezení duplicity v kódu ● Duplicita v kódu je nepříjemná ● ● ● ●
snižuje přehlednost zvyšuje náročnost úprav (nutno na více místech) zvyšuje riziko chyby (někde zapomeneme upravit) dědit z třídy jen pro využití části funkčnosti ale není dobré (viz. dále)
Zlepšuje možnost znovuvyužití existujícího kódu 15
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
“Dědičnost” v C V C se abstrakce a znovuvyužití kódu dosahuje: ● dobrým návrhem funkčního rozhraní ● umístěním do samostatných hlavičkových souborů ● přechod na jinou implementaci ideálně jen změnou hlavičkového souboru
V C se duplicita kódu odstraňuje: ● vytvořením nové funkce ● a vložením funkčního volání na původní místa
16
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost v C++ V C++ existuje systematičtější podpora ● lze odstranit duplicitu i pro proměnné ● navíc podporuje silnou typovou kontrolu
Mechanismus umožňující vytvořit další třídu (potomek) s využitím předlohové třídy (předek) ● potomek zdědí možnosti předka (atributy a metody) ● může je rozšiřovat a předefinovat (překrývat)
17
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost – postup při abstrakci Máme dvě (nebo víc) entit se společným chováním Snažíme se vytvořit společnou logiku (metody), které budou potřebné chování pro všechny entity popisovat ● aniž bychom museli vědět, se kterou právě pracujeme ● tvoříme rozhraní Vytvoříme novou třídu (předka, rozhraní) ● obsahující popis společného rozhraní (veřejné metody) Pro jednotlivé entity vytvoříme nové samostatné třídy, které budou implementovat definované rozhraní Nové třídy budou potomci třídy definující rozhraní
18
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost - syntaxe Hlavičkový soubor předka
#include "cmousebase.h" Potomek
class CFieldMouse : public CMouseBase { public: Předek CFieldMouse(); protected: bool increaseSizeByFood(const unsigned int foodAmount); };
Modifikátor definující způsob dědění metod a atributů 19
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Přístupová práva - protected K položce s právem protected má přístup pouze potomek ● atribut může být čten a měněn potomkem ● metoda nemůže být volána „zvenčí“
Jako protected typicky označujeme metody ● které nemají být dostupné všem, ale potomkům ano ● často jde o virtuální přetěžované metody (později) ● méně často atributy – raději protected „setter“ metodu
20
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Typová hierarchie Dědičnost vytváří hierarchii objektů ● od nejobecnějšího k nejspecifičtějším
Na místo předka může být umístěn potomek ● proměnná s typem předka může obsahovat potomka ● potomek může být argumentem funkce s typem předka ● zároveň zachována typová bezpečnost
Při dědění lze omezit viditelnost položek předka ● specifikace práv při dědění 21
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Specifikátory přístupových práv dědění public (class B : public A {}; ) ● zděděné položky dědí přístupová práva od předka ● práva zůstanou jako předtím
private (class B : private A {}; ) ● zděděné položky budou private, odvozená třída však bude mít přístup ke položkám, pokud byly v předkovi public nebo protected ● nebude přístup k položkám private u předka ● v potomcích potomka už nebude přístup ● používáme, pokud nechceme být předkem 22
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Specifikátory dědění přístupových práv (2) protected (class B : protected A {}; ) ● položky private a protected zůstanou stejné, z public se stane protected
pokud neuvedeme (class B : A {}; ) ● class jako private, u struct a union jako public
virtual (class B : virtual A {}; ) ● lze kombinovat s jedním z předchozích, přikazuje pozdní vazbu – (viz později)
23
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Práva při dědění - ukázka inheritanceRightsDemo.cpp přístup k private metodě přístup při dědění public/private/protected změna práv pro přístup při opakovaném dědění způsob znepřístupnění původně public metody
24
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
kód z inheritanceRightsDemo.cpp // // Inheritance rights demo // class A { private: void privateMethodA() {} protected: void protectedMethodA() {} public: void publicMethodA() {} }; /** Public inheritance - all rights for inherited methods/atributes stay same */ class B : public A { public: void test() { //privateMethodA(); // error: 'void A::privateMethodA()' is private protectedMethodA(); // OK publicMethodA(); // OK } }; /** Private inheritance - all rights for inherited methods/atributes changes to private */ class C : private A { public: void test() { //privateMethodA(); // error: 'void A::privateMethodA()' is private protectedMethodA(); // OK, but protectedMethodA is now private in C publicMethodA(); // OK, but publicMethodA is now private in C } };
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
25
PB161
class C : private A { public: void test() { //privateMethodA(); protectedMethodA(); publicMethodA(); } };
kód z inheritanceRightsDemo.cpp (2) /** Public inheritance from C (C was inherited privately from A) */ class D : public C { public: void test() { //privateMethodA(); // error: 'void A::privateMethodA()' is private //protectedMethodA(); // error: 'void A::protectedMethodA()' is protected //publicMethodA(); // error: 'void A::publicMethodA()' is inaccessible } };
/** Protected inheritance - all rights for inherited methods/atributes changes to private */ class E : protected A { public: void test() { //privateMethodA(); // error: 'void A::privateMethodA()' is private protectedMethodA(); // OK, protectedMethodA stays protected in E publicMethodA(); // OK, but publicMethodA is now protected in E } }; /** Public inheritance from E (E was inherited as protected from A) */ class F : public E { public: void test() { //privateMethodA(); // error: 'void A::privateMethodA()' is private protectedMethodA(); // OK publicMethodA(); // OK } };
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
26
PB161
Jak „dědit“ z více existujících tříd? Nová třída má mít vlastnosti více entit Novou třídu lze přetypovat na více různých předků ● v Jave se řeší pomocí interfaces
V C++ lze řešit ● pomocí násobné dědičnosti (třída má více předků) ● pomocí kompozice objektu (třída má více podčástí)
27
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Syntaxe násobné dědičnosti class CRAMMemory { int m_ramSize; Předek 1 public: CRAMMemory(unsigned int size) { m_ramSize = size; } int getRAMSize() const { return m_ramSize; } };
Předek 2 class CCPU { int m_clockFrequency; public: CCPU(unsigned int freq) { m_clockFrequency = freq; } int getCPUFreq() const { return m_clockFrequency; } };
Dědíme z obou předků
class CNotebookInherit : public CRAMMemory, public CCPU { public: CNotebookInherit(unsigned int ramSize, unsigned int cpuFreq) : CRAMMemory(ramSize), CCPU(cpuFreq) { this-> CCPU::getCPUFreq(); } int getCPUFreq() const { return m_clockFrequency; } };
28
Syntakticky správně, je ale vhodné? PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost vs. kompozice Dědičnost je „Is-A“ vztah (chová se je jako A) ● potomek má všechny vnější vlastnosti předka A ● potomka můžeme přetypovat na předka ● (je správné se na notebook dívat jako na případ CPU?)
Kompozice je „Has-A“ vztah ● třída může mít jako atribut další třídu A ● hodnotou, referencí, ukazatelem
● třída obsahuje vlastnosti A a další ● třída může mít víc tříd jako své atributy ● (je vhodnější se na notebook dívat jako na složeninu CPU a RAM?) PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
29
Kompozice namísto násobné dědičnosti class CNotebookInherit : public CRAMMemory, public CCPU { };
Násobná dědičnost
Kompozice class CNotebookComposition { CRAMMemory m_ram; CCPU m_cpu; public: /** Initialize atributes in constructor. As params are passed into constructors of attributes, inicialization list section needs to be used */ CNotebookComposition(unsigned int ramSize, unsigned int cpuFreq) : m_ram(ramSize), m_cpu(cpuFreq) {} int getCPUFreq() const { return m_cpu.getCPUFreq(); } int getRAMSize() const { return m_ram.getRAMSize(); } }; 30
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost vs. kompozice - ukázka inheritanceCompositionDemo.cpp násobná dědičnost kompozice využití inicializační sekce konstruktoru přetypování na předka
31
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost – vhodnost použití Dle použití lze volit mezi dědičností a kompozicí Obecně preference kompozice před dědičností ● násobná dědičnost může být nepřirozená ● ale kompozice může být kódově rozsáhlejší
Možná i kombinace ● objekt obsahuje kompozicí třídy jako atributy ● jednotlivé atributy mohou mít hierarchii dědičnosti
32
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Existující kód int main() { IPrinter* printer = GetSelectedPrinter(); printer->PrintDocument(); printer->GetPendingDocuments(); return 0; }
Nový kód IPrinter* GetSelectedPrinter() { // user selects printer via GUI // e.g., InkPrinter -> selectedPrinter // e.g., LaserPrinter -> selectedPrinter return selectedPrinter; }
33
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost – správné použití (1) Z nového kódu můžeme vždy volat kód existující ● aniž bychom přepisovali existující kód ● (víme jméno a parametry existující funkce, nový kód přizpůsobíme)
Dědičnost nám umožňuje volat z existujícího kódu kód, který teprve bude napsán ● existující kód pracuje s předkem (např. IPrinter) ● (IPrinter deklarován v době psaní existujícího kódu) ● nový kód vytváří potomky (např. CInkPrinter) ● existující kód pracuje s IPrinter ● CInkPrinter lze přetypovat na IPrinter ● existující kód může používat CInkPrinter (jako IPrinter)
Potomek by neměl měnit logiku chování (rozhraní) předka! ● CInkPrinter pořád přijímá dokument na tisk ● „pouze“ tiskne specializovaným způsobem PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
34
PB161
Multifunkční zařízení (tiskárna + scanner)
Řešení pomocí dědičnosti PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
Řešení pomocí kompozice PB161
35
Multifunkční zařízení (2) Je lepší použít dědičnost nebo kompozici? Pokud se jednotlivý předkové funkčně nepřekrývají, tak lze vícenásobná dědičnost ● vhodné je dědit z čistě abstraktních tříd (viz. dále) ● pak je stejné jako rozhraní v Javě (interfaces) ● IPrinter a IScanner pokrývají odlišnou funkčnost
Dědičnost používáme, pokud předpokládáme přetypování potomka na předka ● u CInkPrinter bude nastávat ● bude nastávat i u CScanPrintDev?
Dědičnost používáme, pokud předpokládáme později vznik potomků z naší třídy PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
36
Dědičnost – správné použití (2) Dědičnost je velice silný vztah ● => jeho nevhodné použití může přinést problémy
Potomci při dědičnosti mají specializovat, ne rozšiřovat funkčnost základního objektu ● CStudentTeacher není jen speciální případ studenta ● CStudentTeacher je student a učitel zároveň ● => víc než jen student => dědičnost není vhodná ● vhodnější je zde kompozice (CStudentTeacher obsahuje atributy CStudent a CTeacher)
Platí že potomek je substituovatelný za předka? ● pokud ano, použijte dědičnost
Preferujte kompozici před dědičností PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
37
PB161
Dědičnost – postup při duplicitě Máme dvě (nebo víc) tříd se společným chováním Identifikujeme společnou logiku (metody) Identifikujeme společná data (atributy) Vytvoříme novou třídu (předka) ● obsahující společné atributy a logiku
Odstraníme přesunuté atributy&metody z původních tříd Původní třídy nastavíme jako potomky nového předka 38
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost - příklad V laboratoři máme domácí a polní myš. Rozdíl mezi druhy je jen v počáteční velikosti a rychlosti přibírání po požití potravy. Nové třídy CHouseMouse a CFieldMouse Společné vlastnosti přesunuty do CMouseBase CHouseMouse a CFieldMouse potomci CMouseBase 39
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Přetypování private/protected potomka “Lze získat přístup k public metodám předka z instance potomka, který dědil pomocí private/protected pomocí jeho přetypování na předka?” nelze, viz. brokenRightsDemo.cpp http://stackoverflow.com/questions/9661936/inher itance-a-is-an-inaccessible-base-of-b
40
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Přetypování private/protected potomka (2) class A { public: void foo() { cout << "foo called" << endl; } }; class B : protected A { }; int main() { A a; a.foo(); B b; //b.foo();
// error: 'void A::foo()' is inaccessible
// Let's try to retype B to A to get access to originally public method A& refB = b; // error: 'A' is an inaccessible base of 'B' refB.foo(); 41
}
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost a virtuální dědičnost Problém typu diamand vzniká typicky při nevhodné OO hierarchii Virtuální dědičnost umožňuje obejít, ale MNOHEM vhodnější je přímo odstranit problém změnou hierarchie CPerson
● (pokud je možné) CStudent
CTeacher
42 CTeachStudent PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Abstraktní třída
43
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Generalizace
PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
Cílem je navržení takového rozhraní, které pod sebe schová chování více typů objektů Hledají se společné vlastnosti různých objektů Např. iterátor na procházení pole
● není podstatné, že jde o int[] nebo float[] pole
Např. zobrazení objektů na cílovou plochu
● není podstatné, jaká přesně bude (obrazovka, tiskárna)
Např. tiskárny ● není podstatná technologie tisku
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Motivace pro rozhraní Chceme podchytit, co všechno musí splňovat třída, aby se mohla vydávat za příslušníka dané skupiny V C++ implementujeme pomocí společného předka ● class IDrawable;
PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
● např. všechny grafické objekty je možné vykreslit
● požadavky na chování příslušníků zachytíme v jeho veřejných metodách ● např. virtual void paint();
Potomci si provádí vlastní implementaci těchto metod ● void CButton::paint() const {}
(Společný předek nemusí mít smysl jako instance)
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Čistě virtuální metoda (pure virtual) Metoda, která má ve třídě pouze svou deklaraci Syntaxe ● virtual návratový_typ metoda(parametry) = 0;
Potomci standardním způsobem implementují ● překrývají čistě virtuální metodu předka
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
● implementace je ponechána na potomky
PB161
Abstraktní třída
class CPersonInterface { public: virtual const char* getEmail() = 0; virtual void print() = 0; };
Třída s alespoň jednou čistě virtuální metodou Nelze z ní přímo vytvářet instance (objekty) ● chyba při překladu
Lze ale využít jako třídu pro dědění ● potomci překrývají virtuální metody abstraktního předka
Analogie rozhraní v Javě ● může ale obsahovat implementaci některých funkcí
Čistě abstraktní třída - všechny metody jsou čistě virtuální ● opravdové rozhraní ve stylu Javy PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Abstraktní třída - obrázek Čistě abstraktní třída PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
Abstraktní třída (může být část implementace)
Třída s implementací (děláme objekty) PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Abstraktní třída - ukázka int main() { IPerson* person = new CStudent("
[email protected]"); person->print(); delete person; return 0; }
class CStudent : public IPerson { char m_email[MAX_EMAIL_LENGTH+1]; public: CStudent(const char* email) { strncpy(m_email, email, MAX_EMAIL_LENGTH); m_email[MAX_EMAIL_LENGTH] = 0; } virtual const char* getEmail() { return m_email; } virtual void print() { cout << "Student: " << m_email << endl; } }; PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
// Abstract class, at least // one method is pure virtual class IPerson { public: virtual const char* getEmail() = 0; virtual void print() = 0; };
PB161
Abstraktní třída - ukázka
PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
abstractClassDemo.cpp Čistě virtuální metoda Abstraktní třída Čistě abstraktní třída Potomek implementující abstraktní třídu Potomek implementující jen část čistě virtuálních metod
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Psaní dobrého kódu (2) Označte virtuální ty metody, které budou v potomkovi definovat jeho specializaci PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014
● SerializeIntoFile()
● kód pro manipulaci zápis do souboru bude v předkovi ● pro potomky virtuální funkce SerializeIntoBuffer()
Nedělejte všechny metody virtuální ● pokud není třída zároveň čistě abstraktní (rozhraní) ● jinak svědčí spíš o špatném návrhu hierarchie
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Operátor reference &
52
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Reference na proměnnou Alternativní jméno pro objekt/proměnou (~alias) ● operátor reference je & int a = 1; int b = 5; int& refToA1 = a; // First reference to a int& refToA2 = a; // Another reference to a //int& uninitializedRef; // error: uninitialized reference a += 9; operátor refToA1 += 9; reference refToA2 += 11;
Určitá analogie s ukazatelem na proměnnou ● není ukazatel na objekt, ale má podobné použití ● není proměnná s adresou na původní PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
53
PB161
Předávání argumentů referencí Alternativa k předávání argumentů funkce ukazatelem ● volání hodnotou: ● volání odkazem: ● volání referencí:
void byValue(int A) { A = 10; } void byPointer(int* pA) { *pA = 10; } void byReference(int& A) { A = 10; }
Zavolání funkce vypadá jako volání hodnotou ● při předávání referencí není vytvářena kopie objektu ● změna argumentu uvnitř funkce se ale projeví i mimo funkci 54
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Konstantní reference Konstantní reference ● void foo(const typ& param) ● umožňuje specifikovat záměr programátoru o nakládání s objektem (referencí, ale nebude měněn)
Kontrolováno během překladu ● konstatní reference na nekonstantní objekt - chyba ● změna nekonstantního objektu přes konstantní referenci - chyba void constReferenceDemo(const int& A, const int* pB) { // We can read values cout << "a + b: " << A + *pB << endl; // But we can't change them 55 A = 1; // error: assignment of read-only reference 'A' *pB = 1; // error: assignment of read-only location '* pB' } PB161 | Principy OOP - Dědičnost, rozhraní 29.9.2014 PB161
Konstantní reference – detaily Pro parametry v hlavičce funkce/metody platí, že konstantní reference je "catch-all" Oproti normální referenci, která umí “chytat” pouze lvalue (to, co někde bydlí, má to adresu), tak konstantní umí “chytat” všechno, i dočasné objekty Právě proto, že je konstantní reference "catchall", tak má při určování, která fce se zavolá (při stejné signatuře) nižší prioritu, než funkce pouze s referencí PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
56
Reference - ukázka referenceDemo.cpp více referencí na jedinou proměnnou nutnost inicializace reference předání argumentu referencí změna hodnoty mimo funkci konstantní reference
57
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Rozhraní a dědičnost Rozhraní a práce s ním je klíčový koncept ● ● ● ●
Abstrakce od konkrétní implementace Tvorba generického kódu Snadná rozšiřitelnost později Vyžaduje dobře navrženou hierarchii
Dědičnost umožňuje potomkovi vystupovat jako datový typ předka ● Konkrétní implementace za rozhraní
Násobná dědičnost vs. Kompozice ● Ne vždy je vhodné dědit, zvažte vždy kompozici PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
58
PB161
Shrnutí Už bylo ☺
59
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Bonus ☺
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Deadly sins of programming Zkušenost není pouze jak věci dělat, ale i to jak je nedělat Co ale s postupy, které jsou lákavé, tušíme kontroverzi, ale veříme, že právě my to “zvládneme” ? Vítejte ve světě programátorských hříchů ☺ Rowan Atkinson na “nás” sice pozapomněl ● https://www.youtube.com/watch?v=91DSNL1BEeY
Ale další už nikoli ● http://www.infoworld.com/d/developer-world/the-7-deadly-sinssoftware-development-872 ● http://msmvps.com/blogs/jon_skeet/archive/2006/06/10/deadlysins .aspx ● https://blogs.msdn.com/b/ericgu/archive/2006/08/03/687962.aspx ● http://www.cse.uaa.alaska.edu/~afkjm/cs470/handouts/SecuritySin s.pdf
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Deadly sins of software development Lust (smilstvo) (overengineering) Gluttony (nestřídmost) (failing to refactor) Greed (lakomství) (competing across teams) Sloth (lenost) (not validating inputs) Wrath (hněv) (not commenting code) Envy (závist) (not using version control) Pride (pýcha) (not unit testing) http://www.infoworld.com/d/developer-world/the7-deadly-sins-software-development-872 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Samostudium
65 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Problém diamant, virtuální dědičnost
66 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Problém typu diamant - motivace CPerson
Třída CPerson ● string email, getEmail()…
Třída pro studenta
CStudent
CTeacher
● class CStudent: public CPerson; ● getEmail() vrací formát xnovak@fi
Třída pro učitele
CTeachStudent
● class CTeacher: public CPerson; ● getEmail() vrací formát novak@fi
Cvičící, ale také student ● class CTeachStudent: public CStudent, public CTeacher; ● Co vrátí volání getEmail()? 67 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Problém typu diamant – kde je problém? Nelze zkompilovat Nevhodné použití dědičnosti (Vyskytuje se často)
68 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Diamant – ukázka #include
#include <string.h> using namespace std; #define MAX_EMAIL_LENGTH class CPerson {
int main() { CTeachStudent teachStud("[email protected]"); teachStud.getEmail();
30
protected: char m_email[MAX_EMAIL_LENGTH+1]; public: CPerson(const char* email) { strncpy(m_email, email, MAX_EMAIL_LENGTH); m_email[MAX_EMAIL_LENGTH] = 0; } const char* getEmail() { return m_email; } };
return 0; }
class CStudent : public CPerson { public: CStudent(const char* email) : CPerson(email) {} }; class CTeacher : public CPerson { public: CTeacher(const char* email) : CPerson(email) {} }; class CTeachStudent : public CTeacher, public CStudent { public: CTeachStudent(const char* email) : CTeacher(email), CStudent(email) {} };
PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
69 PB161
Problém typu diamant Vzniká při nevhodném využití násobné dědičnosti Třída CTeachStudent zdědila jednu kopii metody getEmail od CStudent a druhou od CTeacher Při volání metody getEmail() třídy CTeachStudent není jasné, která kopie dat/metod se má použít ● getEmail() z CStudent nebo CTeacher?
Obdobný problém by vnikl ve funkci printEmail() void CTeachStudent::printEmail() { cout << m_email; } 70 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Diamant – plná kvalifikace Řešení pomocí plné kvalifikace jména metody/atributu ● teachStud.CTeacher::getEmail(); ● CTeacher::m_email;
Je nutné znát hierarchii, porušuje myšlenku class CTeachStudent : public CTeacher, public CStudent { public: zapouzdření CTeachStudent(const char* email) : CTeacher(email), CStudent(email) {} void printEmail() { //cout << m_email; cout << CTeacher::m_email; } }; int main() { CTeachStudent teachStud("[email protected]"); //teachStud.getEmail(); teachStud.CTeacher::getEmail();
71
return 0; } PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Diamant - virtuální dědičnost Pomocí klíčového slova virtual přikážeme jedinou kopii atributů a tabulky metod ● používat opatrně ● class CStudent: virtual public CPerson;
Problémem je, že už při deklaraci CStudent musíme “tušit”, že později nastane diamant Pokud se mixuje virtuální a nevirtuální dědičnost ● jen některá větev používá virtuální dědičnost ● tak stále vzniká více kopií 72 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161
Dědičnost a virtuální dědičnost Problém typu diamand vzniká typicky při nevhodné OO hierarchii Virtuální dědičnost umožňuje obejít, ale MNOHEM vhodnější je přímo odstranit problém změnou hierarchie ● (pokud je možné)
73 PB161 | Principy OOP - Dědičnost, rozhraní
29.9.2014
PB161