Principy OOP - rozhraní, dědičnost 1 Principy OOP - Dědičnost, rozhraní 29.9.2014
Co nás dnes čeká Motivace pro a realizace rozhraní Dědičnost a kompozice Operátor reference & 2 Principy OOP - Dědičnost, rozhraní 29.9.2014
Rozhraní 3 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
Motivace pro rozhraní 5 Principy OOP - Dědičnost, rozhraní 29.9.2014
Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 8 Principy OOP - Dědičnost, rozhraní 29.9.2014
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. Principy OOP - Dědičnost, rozhraní 29.9.2014 = 0 označuje deklaraci funkce, u které nebude poskytnuta implementace. Implementaci poskytují potomci.
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 { return m_printeddocument; } virtual void PrintDocument(const string& document) { m_printeddocument = document; } ~CHPDeskJet5550() { cout << "~CHPDeskJet5550() called"; } Konkrétní implementace metod z rozhraní (předka) Třída může poskytnout vlastní destruktor s vlastním úklidem Principy OOP - Dědičnost, rozhraní 29.9.2014
3. Použití konkrétní 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"); Vytvoření proměnné printer1 typu CHPDeskJet5550 na zásobníku a její použití Dynamická alokace proměnné printer2 na haldě // Create new HPDeskJet5550 printer, retype to base class (interface) IPrinter* printer3 = new CHPDeskJet5550(); Přetypování dynamicky printer3->printdocument("secret"); alokovaného objektu typu cout << printer3->getpendingdocuments() << endl; CHPDeskJet5550 na typ předka - rozhraní // printer1 is automatically removed when function ends // dealloaction of instance CHPDeskJet5550 // destructor CHPDeskJet5550::~CHPDeskJet5550 is called delete printer2; // destructor CHPDeskJet5550::~CHPDeskJet5550 is called, // because IPrinter::~IPrinter is virtual destructor delete printer3; Použití objektu konkrétní tiskárny jen prostřednictvím rozhraní Principy OOP - Dědičnost, rozhraní 29.9.2014
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(); Objekt printer1 na printer3->printdocument("secret"); zásobníku se zruší cout << printer->getpendingdocuments() << endl; automaticky při konci funkce // printer1 is automatically removed when function ends // dealloaction of instance CHPDeskJet5550 // destructor CHPDeskJet5550::~CHPDeskJet5550 is called delete printer2; // destructor CHPDeskJet5550::~CHPDeskJet5550 is called, // because IPrinter::~IPrinter is virtual destructor delete printer3; Principy OOP - Dědičnost, rozhraní 29.9.2014 Dealokace dynamicky alokovaných objektů z haldy Objekt printer3 je typu IPrinter, díky virtuálnímu destruktoru se zavolá i destruktor třídy CHPDeskJet5550
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
Dědičnost 14 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
Dědičnost - syntaxe #include "cmousebase.h" Potomek Hlavičkový soubor předka 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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 } }; Principy OOP - Dědičnost, rozhraní 29.9.2014 25
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 } }; class C : private A { public: void test() { //privatemethoda(); protectedmethoda(); publicmethoda(); /** 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 26 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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; } }; Syntakticky správně, je ale vhodné? Principy OOP - Dědičnost, rozhraní 29.9.2014 28
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?) 29 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 34 Principy OOP - Dědičnost, rozhraní 29.9.2014
Multifunkční zařízení (tiskárna + scanner) Řešení pomocí dědičnosti Řešení pomocí kompozice 35 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 36 Principy OOP - Dědičnost, rozhraní 29.9.2014
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í 37 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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é) CPerson CStudent CTeacher CTeachStudent 42 Principy OOP - Dědičnost, rozhraní 29.9.2014
Abstraktní třída 43 Principy OOP - Dědičnost, rozhraní 29.9.2014
Generalizace 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 Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 např. všechny grafické objekty je možné vykreslit V C++ implementujeme pomocí společného předka class IDrawable; 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) Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
Čistě virtuální metoda (pure virtual) Metoda, která má ve třídě pouze svou deklaraci implementace je ponechána na potomky Syntaxe virtual návratový_typ metoda(parametry) = 0; Potomci standardním způsobem implementují překrývají čistě virtuální metodu předka Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
Abstraktní třída - obrázek Čistě abstraktní třída 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) Principy OOP - Dědičnost, rozhraní 29.9.2014
Abstraktní třída - ukázka // Abstract class, at least // one method is pure virtual class IPerson { public: }; virtual const char* getemail() = 0; virtual void print() = 0; int main() { IPerson* person = new CStudent("novak@fi.muni.cz"); 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; } }; Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
Abstraktní třída - ukázka 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 Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
Psaní dobrého kódu (2) Označte virtuální ty metody, které budou v potomkovi definovat jeho specializaci 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 Principy OOP - Dědičnost, rozhraní 29.9.2014 Principy OOP - Dědičnost, rozhraní 29.9.2014
Operátor reference & 52 Principy OOP - Dědičnost, rozhraní 29.9.2014
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í 53 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 A = 1; // error: assignment of read-only reference 'A' *pb = 1; // error: assignment of read-only location '* pb' } Principy OOP - Dědičnost, rozhraní 29.9.2014 55
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í 56 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 58 Principy OOP - Dědičnost, rozhraní 29.9.2014
Shrnutí Už bylo 59 Principy OOP - Dědičnost, rozhraní 29.9.2014
Bonus Principy OOP - Dědičnost, rozhraní 29.9.2014
Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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/the- 7-deadly-sins-software-development-872 Principy OOP - Dědičnost, rozhraní 29.9.2014
Principy OOP - Dědičnost, rozhraní 29.9.2014
Samostudium 65 Principy OOP - Dědičnost, rozhraní 29.9.2014
Problém diamant, virtuální dědičnost 66 Principy OOP - Dědičnost, rozhraní 29.9.2014
Problém typu diamant - motivace Třída CPerson string email, getemail() Třída pro studenta class CStudent: public CPerson; getemail() vrací formát xnovak@fi Třída pro učitele class CTeacher: public CPerson; getemail() vrací formát novak@fi Cvičící, ale také student CPerson CStudent CTeacher CTeachStudent class CTeachStudent: public CStudent, public CTeacher; Co vrátí volání getemail()? 67 Principy OOP - Dědičnost, rozhraní 29.9.2014
Problém typu diamant kde je problém? Nelze zkompilovat Nevhodné použití dědičnosti (Vyskytuje se často) 68 Principy OOP - Dědičnost, rozhraní 29.9.2014
Diamant ukázka #include <iostream> #include <string.h> using namespace std; #define MAX_EMAIL_LENGTH 30 class CPerson { 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; } }; int main() { CTeachStudent teachstud("novak@fi.muni.cz"); teachstud.getemail(); } 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) {} }; Principy OOP - Dědičnost, rozhraní 29.9.2014 69
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 zapouzdření class CTeachStudent : public CTeacher, public CStudent { public: CTeachStudent(const char* email) : CTeacher(email), CStudent(email) {} void printemail() { //cout << m_email; cout << CTeacher::m_email; } }; int main() { CTeachStudent teachstud("novak@fi.muni.cz"); //teachstud.getemail(); teachstud.cteacher::getemail(); 71 } return 0; Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014
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 Principy OOP - Dědičnost, rozhraní 29.9.2014