PB161 Principy OOP - rozhraní, dědičnost PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Co nás dnes čeká… Motivace pro a realizace rozhraní Dědičnost a kompozice Operátor reference & PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Rozhraní 3 PB161 | Principy OOP - Dědičnost, rozhraní
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 ●… PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Motivace pro rozhraní PB161 | Principy OOP - Dědičnost, rozhraní
PB161 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 PB161 | Principy OOP - Dědičnost, rozhraní int main() { IPrinter* printer = GetSelectedPrinter(); printer->PrintDocument(); printer->GetPendingDocuments(); return 0; } IPrinter* GetSelectedPrinter() { // user selects printer via GUI // e.g., InkPrinter -> selectedPrinter // e.g., LaserPrinter -> selectedPrinter return selectedPrinter; } Nový kód Existující kód
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í
PB Deklarace rozhraní PB161 | Principy OOP - Dědičnost, rozhraní class IPrinter { public: virtual string GetPendingDocuments() const = 0; virtual void PrintDocument(const string& document) = 0; virtual ~IPrinter() {} }; 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í. = 0 označuje deklaraci funkce, u které nebude poskytnuta implementace. Implementaci poskytují potomci. Rozhraní by mělo 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.
PB Implementace konkrétní třídy PB161 | Principy OOP - Dědičnost, rozhraní class CHPDeskJet5550 : public IPrinter { string m_printedDocument; public: CHPDeskJet5550() {} virtual string GetPendingDocuments() const { return m_printedDocument; } virtual void PrintDocument(const string& document) { m_printedDocument = document; } ~CHPDeskJet5550() { cout << "~CHPDeskJet5550() called"; } }; 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. Třída může poskytnout vlastní destruktor s vlastním ‘úklidem’ Konkrétní implementace metod z rozhraní (předka)
PB Použití konkrétní instance PB161 | Principy OOP - Dědičnost, rozhraní // 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 GetPendingDocuments() << endl; // 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; Vytvoření proměnné printer1 typu CHPDeskJet5550 na zásobníku a její použití Dynamická alokace proměnné printer2 na haldě Použití objektu konkrétní tiskárny jen prostřednictvím rozhraní Přetypování dynamicky alokovaného objektu typu CHPDeskJet5550 na typ předka - rozhraní
PB Zrušení instance PB161 | Principy OOP - Dědičnost, rozhraní // 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 GetPendingDocuments() << endl; // 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; Objekt printer1 na zásobníku se zruší automaticky při konci funkce 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
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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Dědičnost PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
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) PB161 | Principy OOP - Dědičnost, rozhraní
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í PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Dědičnost - syntaxe PB161 | Principy OOP - Dědičnost, rozhraní #include "cmousebase.h" class CFieldMouse : public CMouseBase { public: CFieldMouse(); protected: bool increaseSizeByFood(const unsigned int foodAmount); }; Předek Potomek Hlavičkový soubor předka Modifikátor definující způsob dědění metod a atributů
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 PB161 | Principy OOP - Dědičnost, rozhraní
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í PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
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) PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 kód z inheritanceRightsDemo.cpp PB161 | Principy OOP - Dědičnost, rozhraní // // 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 kód z inheritanceRightsDemo.cpp (2) PB161 | Principy OOP - Dědičnost, rozhraní /** 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 } }; class C : private A { public: void test() { //privateMethodA(); protectedMethodA(); publicMethodA(); } };
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í) PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Syntaxe násobné dědičnosti Syntakticky správně, je ale vhodné? PB161 | Principy OOP - Dědičnost, rozhraní Předek 1 Předek 2 Dědíme z obou předků class CRAMMemory { int m_ramSize; public: CRAMMemory(unsigned int size) { m_ramSize = size; } int getRAMSize() const { return m_ramSize; } }; class CCPU { int m_clockFrequency; public: CCPU(unsigned int freq) { m_clockFrequency = freq; } int getCPUFreq() const { return m_clockFrequency; } }; 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; } };
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í
PB161 Kompozice namísto násobné dědičnosti PB161 | Principy OOP - Dědičnost, rozhraní 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(); } }; class CNotebookInherit : public CRAMMemory, public CCPU { }; Násobná dědičnost Kompozice
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 PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 PB161 | Principy OOP - Dědičnost, rozhraní int main() { IPrinter* printer = GetSelectedPrinter(); printer->PrintDocument(); printer->GetPendingDocuments(); return 0; } IPrinter* GetSelectedPrinter() { // user selects printer via GUI // e.g., InkPrinter -> selectedPrinter // e.g., LaserPrinter -> selectedPrinter return selectedPrinter; } Nový kód Existující kód
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í
PB161 Multifunkční zařízení (tiskárna + scanner) PB161 | Principy OOP - Dědičnost, rozhraní Řešení pomocí dědičnostiŘešení pomocí kompozice
PB161 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í
PB161 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í
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 PB161 | Principy OOP - Dědičnost, rozhraní
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 PB161 | Principy OOP - Dědičnost, rozhraní
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 itance-a-is-an-inaccessible-base-of-b itance-a-is-an-inaccessible-base-of-b PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Přetypování private/protected potomka (2) PB161 | Principy OOP - Dědičnost, rozhraní 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(); }
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é) PB161 | Principy OOP - Dědičnost, rozhraní CPerson CStudentCTeacher CTeachStudent
PB161 Abstraktní třída PB161 | Principy OOP - Dědičnost, rozhraní
PB161 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 PB161 | Principy OOP - Dědičnost, rozhraní
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 ●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) PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Č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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Abstraktní třída 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 class CPersonInterface { public: virtual const char* get () = 0; virtual void print() = 0; }; PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Abstraktní třída - obrázek PB161 | Principy OOP - Dědičnost, rozhraní Čistě abstraktní třída Abstraktní třída (může být část implementace) Třída s implementací (děláme objekty) PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Abstraktní třída - ukázka PB161 | Principy OOP - Dědičnost, rozhraní // Abstract class, at least // one method is pure virtual class IPerson { public: virtual const char* get () = 0; virtual void print() = 0; }; class CStudent : public IPerson { char m_ [MAX_ _LENGTH+1]; public: CStudent(const char* ) { strncpy(m_ , , MAX_ _LENGTH); m_ [MAX_ _LENGTH] = 0; } virtual const char* get () { return m_ ; } virtual void print() { cout << "Student: " << m_ << endl; } }; int main() { IPerson* person = new person->print(); delete person; return 0; } PB161 | Principy OOP - Dědičnost, rozhraní
PB161 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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 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 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Operátor reference & PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Reference na proměnnou Alternativní jméno pro objekt/proměnou (~alias) ●operátor reference je & 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í 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; refToA1 += 9; refToA2 += 11; operátor reference
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í: 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 PB161 | Principy OOP - Dědičnost, rozhraní void byValue(int A) { A = 10; } void byPointer(int* pA) { *pA = 10; } void byReference(int& A) { A = 10; }
PB161 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 PB161 | Principy OOP - Dědičnost, rozhraní 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' }
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 "catch- all", 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í
PB161 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 PB161 | Principy OOP - Dědičnost, rozhraní
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í
PB161 Shrnutí Už bylo PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Bonus PB161 | Principy OOP - Dědičnost, rozhraní
PB161 PB161 | Principy OOP - Dědičnost, rozhraní
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 ● Ale další už nikoli ● software-development-872http:// software-development-872 ● ● ● s.pdfhttp:// s.pdf PB161 | Principy OOP - Dědičnost, rozhraní
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) 7-deadly-sins-software-development deadly-sins-software-development-872 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Samostudium 65 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Problém diamant, virtuální dědičnost 66 PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Problém typu diamant - motivace Třída CPerson ●string , get ()… Třída pro studenta ●class CStudent: public CPerson; ●get () vrací formát Třída pro učitele ●class CTeacher: public CPerson; ●get () vrací formát Cvičící, ale také student ●class CTeachStudent: public CStudent, public CTeacher; ●Co vrátí volání get ()? CPerson CStudentCTeacher CTeachStudent PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Problém typu diamant – kde je problém? Nelze zkompilovat Nevhodné použití dědičnosti (Vyskytuje se často) PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Diamant – ukázka #include using namespace std; #define MAX_ _LENGTH 30 class CPerson { protected: char m_ [MAX_ _LENGTH+1]; public: CPerson(const char* ) { strncpy(m_ , , MAX_ _LENGTH); m_ [MAX_ _LENGTH] = 0; } const char* get () { return m_ ; } }; class CStudent : public CPerson { public: CStudent(const char* ) : CPerson( ) {} }; class CTeacher : public CPerson { public: CTeacher(const char* ) : CPerson( ) {} }; class CTeachStudent : public CTeacher, public CStudent { public: CTeachStudent(const char* ) : CTeacher( ), CStudent( ) {} }; int main() { CTeachStudent teachStud.get (); return 0; } PB161 | Principy OOP - Dědičnost, rozhraní
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 get od CStudent a druhou od CTeacher Při volání metody get () třídy CTeachStudent není jasné, která kopie dat/metod se má použít ●get () z CStudent nebo CTeacher? Obdobný problém by vnikl ve funkci print () void CTeachStudent::print () { cout << m_ ; } PB161 | Principy OOP - Dědičnost, rozhraní
PB161 Diamant – plná kvalifikace Řešení pomocí plné kvalifikace jména metody/atributu ●teachStud.CTeacher::get (); ●CTeacher::m_ ; Je nutné znát hierarchii, porušuje myšlenku zapouzdření class CTeachStudent : public CTeacher, public CStudent { public: CTeachStudent(const char* ) : CTeacher( ), CStudent( ) {} void print () { //cout << m_ ; cout << CTeacher::m_ ; } }; int main() { CTeachStudent //teachStud.get (); teachStud.CTeacher::get (); return 0; } PB161 | Principy OOP - Dědičnost, rozhraní
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í PB161 | Principy OOP - Dědičnost, rozhraní
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é) PB161 | Principy OOP - Dědičnost, rozhraní