Бублик Володимир Васильович Об'єктно-орієнтоване програмування Частина 2. Ієрархічне програмування. Лекція 9. Відкрите успадкування Лекції для студентів 2 курсу The Southern Alps
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 2 Повторення: масив, вбудований до стеку //Назва класу підкреслює спосіб організації ієрархії class StackContainingArray;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 3 Зауваження Все, про що йтиметься в лекції, дослівно переноситься на випадок параметризованих класів З метою спрощення позначень працюватимемо з простими класами, позначивши тип елемента через Elem і не вдаючись в деталі, як він визначається (через typedef чи template) (в конспекті лекцій С++ розділ 7) Завдання. Розповсюджувати приклади лекції на параметризовані класи
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 4 Масив служить компонентом стеку class StackContainingArray { public: StackContainingArray(size_t); ~StackContainingArray(); const Elem& top() const; void pop(); void push(const Elem& value); private: static const size_t _bos; size_t _top; Array _stackArray; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 5 Клас масивів class Array { public: Array ( size_t ); ~Array(); Elem& operator[] (size_t); const Elem& operator[] (size_t) const; private: Elem * _pa; size_t _size; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 6 Властивості вкладення Кожен композит (стек) містить в собі екземпляр компоненту (масив) Композит (стек) не має виняткових прав на компонент, а лише доступ лише до відкритої частини компоненту (масиву) Користувачі композиту (стеку) взагалі не мають доступу до компоненту (масиву) При необхідності доступ до компоненту делегується шляхом розширення функціональності композиту (чи завжди таке розширення функціональності виправдане?)
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 7 Стек з підгляданням Розширимо функціональність стеку операцією підглядання, делегованою масиву, відповідно змінивши назву класу
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 8 Стек з підгляданням class PeekBackStackContainingArray { public: PeekBackStackContainingArray( size_t ); ~ PeekBackStackContainingArray(); const Elem& top() const; void pop(); void push(const Elem& value); bool peekback(size_t, Elem &) const; private: static const size_t _bos; size_t _top; Array _stackArray; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 9 Реалізація підглядань bool PeekBackStackContainingArray:: peekback(size_t index, Elem & elem) const { if (index>_size) { elem = top(); return false; } //Прямий доступ до масиву elem = _stackArray [index]; return true; }
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 10 Дублювання операцій За доступ до масиву платимо дублюванням функціональності стеку const Elem& PeekBackStackContainingArray:: top() const { return _pa[_top]; }
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 11 Проблеми Не підтримується звязок класу PeekBackStackContainingArray з класом StackContainingArray Кожна із спільних операцій має власну реалізацію у своєму класі При необхідності внесення змін їх доведеться вносити у декілька класів
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 12 Відкрите успадкування Студент теж людина Похідний клас служить спеціалізацією базового Базовий клас грає роль узагальнення похідного класу прямокутник vs. квадрат? cтек vs. стек з підгляданням double vs. int
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 13 Правила відкритого успадкування Похідний клас успадковує (містить в собі) всі властивості (атрибути) базового класу Похідний клас успадковує і надає своїм клієнтам поведінку (відкриті методи) базового класу Похідний клас може мати власні додаткові властивості і поведінку Похідний клас не має доступу до закритої частини базового об'єкту
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 14 Клас прямокутників typedef double length_t; class Rectangle { private: length_t _height, _width; public: Rectangle (length_t heigh=10, length_t width=20); length_t getA() const; length_t getB() const; Rectangle& setA(length_t); Rectangle& setB(length_t); length_t area() const; length_t diagonal() const; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 15 Спеціалізація Квадрат це такий прямокутник, сторони якого рівні між собою source.getA()== source.getB() Бути квадратом властивість статична чи динамічна? (Підказка const double one = 1; належить до дійсного чи цілого типу?) Чим, крім множини значень, визначається тип? операціями
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 16 Клас квадратів //Кожен квадрат є повноцінним прямокутником class DerivedSquare: public Rectangle { public: DerivedSquare (length_t side=10 ); ~DerivedSquare(){}; DerivedSquare (const DerivedSquare&); DerivedSquare& operator=(const DerivedSquare&); };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 17 Конструктор похідного класу //Коректний конструктор квадрата //в списку ініціалізації конструктору прямокутника //передана довжина сторони DerivedSquare:: DerivedSquare (length_t side =10): Rectangle( side, side ) {}
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 18 Помилка в конструкторі //Некоректний конструктор квадрата //Список ініціалізації опущено: //створюється прямокутник //зі сторонами за замовчуванням DerivedSquare:: DerivedSquare (length_t side ) {}; DerivedSquare sq(30); Приведе до створення прямокутника зі сторонами 10 і 20
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 19 Правило виконання конструктора Перед виконанням конструктора похідного класу спочатку завжди викликається конструктор базового класу, незалежно від того, заданий він у списку ініціалізації чи ні Якщо у списку ініціалізації конструктор базового класу не згаданий, то його параметри беруться за замовчуванням
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 20 Копіювальний конструктор DerivedSquare::DerivedSquare (const DerivedSquare& source): Rectangle(source.getA(), source.getB()) {}; Проблема: DerivedSquare square(30); Rectangle copy (square); cout<<"Copied rec: "<<copy<<endl; // Чи стане прямокутник copy квадратом? // double d(1); ???
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 21 Видалення похідних об'єктів Оскільки атрибути похідних об'єктів можуть посилатися на атрибути базового об'єкту, то спочатку видаляються всі власні компоненти похідного об'єкту, а потім автоматично викликається деструктор базового об'єкта Правило зозулі Базовий об'єкт нічого не знає про свої похідні об'єкти
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 22 Правила доступу при успадкуванні Клієнт похідного класу має повний доступ до відкритої частини не тільки похідного, але і базового класу DerivedSquare square(30); cout<<square.getA()<<'x'<<square.getB()<<endl; Це приводить до некоректної поведінки //square.Rectangle::setB(20); square.setB(20); cout<<square<<endl;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 23 Налагодження поведінки //Квадрат, доповнений власними модифікаторами class DerivedSquare: public Rectangle { public: DerivedSquare (length_t side=10 ); ~DerivedSquare(){}; DerivedSquare (const DerivedSquare&); DerivedSquare& operator=(const DerivedSquare&); DerivedSquare& setA(length_t); DerivedSquare& setB(length_t); };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 24 Реалізація поведінки DerivedSquare& DerivedSquare:: setA (length_t a) { Rectangle::setB (a); Rectangle::setA (a); return *this; } DerivedSquare& DerivedSquare::setB(length_t b) { Rectangle::setB (b); Rectangle::setA (b); return *this; }
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 25 Обмеження доступу до базових методів class DerivedSquare: public Rectangle { public: DerivedSquare (length_t side=10 ); ~DerivedSquare(){}; DerivedSquare (const DerivedSquare&); DerivedSquare& operator=(const DerivedSquare&); DerivedSquare& setA(length_t); private: Rectangle::setB; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 26 Залишки проблеми //Спотворення квадрата шляхом доступу //модифікатора базового прямокутника DerivedSquare square; cout<<square.getA()<<'x'<<square.getB()<<endl; square.Rectangle::setB(20); cout<<square<<endl;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 27 Заміщення методів class DerivedSquare: public Rectangle { public: DerivedSquare (length_t side=10 ); ~DerivedSquare(){}; DerivedSquare& setA(length_t); length_t area() const; length_t diagonal() const; private: Rectangle::setB; };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 28 Реалізація заміщень length_t DerivedSquare::area() const { return getA() * getA(); } const double sqrt2 = sqrt(2.); length_t DerivedSquare::diagonal() const { return getA() * sqrt2; }
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 29 Заміщення утиліт //Вивід прямокутника ostream& operator<<(ostream& os, const Rectangle& rec) { os<<rec.getA()<<'x'<<rec.getB(); return os; } //Вивід квадрата ostream& operator<<(ostream& os,const DerivedSquare & s) { os<<s.getA(); return os; }
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 30 Діаграма класів Заміщення (закриття доступу) методів базового класу не відміняє їх застосування до похідного класу, а лише вимагає точнішого розв'язання області дії, наприклад, square.Rectangle::area();
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 31 Типізація об'єктів Точна типізація (створення нового об'єкту) Rectangle rec(10,20); DerivedSquare square(30); Rectangle new_rec(rec); DerivedSquare new_square(square); Поліморфна типізація (використання існуючого об'єкту) Rectangle& refrec(rec); Rectangle& refrecInSquare(square); Rectangle * ptrrec(&rec); Rectangle * ptrrecInSquare(&square);
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 32 Приклад. Базовий клас рахунків //Банківський рахунок class Account { private: const unsigned int _number; int _balance; string _owner; public: Account (string owner, int balance); string getName() const { return _owner;} int getBalance() const {return _balance;} unsigned int getNumber() const {return _number;} void setBalance(int balance) {_balance = balance;} int state() { return getBalance();} };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 33 Приклад. Похідний клас ощадних рахунків //Ощадний рахунок class SavingAccount: public Account{ private: unsigned int _savingRate; int _interest; public: SavingAccount(string owner, int balance, unsigned int savingRate); unsigned int getSavingRate() const {return _savingRate;} void setInterest(int interest) {_interest = interest;} int getInterest() const {return _interest;} int state() { setInterest(getBalance()* static_caste (getSavingRate())/100); return Account::state()+getInterest(); } };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 34 Приклад. Похідний клас бонусних рахунків //Бонусний ощадний рахунок class BonusSavingAccount: public SavingAccount { private: unsigned int _bonus; public: BonusSavingAccount(string owner, int balance, unsigned int savingRate, unsigned int bonus) : unsigned int getBonus() const {return _bonus;} int state() { return SavingAccount::state()+getBonus(); } };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 35 Діаграма класів
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 36 Інтерфейси доступу.1 //1. Використовується базовий інтерфейс Account ac("Ivanenko", 100); cout<<ac.getNumber()<<':<< ac.state()<<endl; Account * pac = ∾ cout getNumber()<<':<< pac->state()<<endl;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 37 Інтерфейси доступу.2 //1. Використовується похідний інтерфейс SavingAccount sac("Petrenko",200,10); cout<<sac.getNumber()<<':'<< sac.state()<<endl; //220 //2. Використовується базовий інтерфейс pac = &sac; cout getNumber()<<':'<< pac->state()<<endl;//200
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 38 Інтерфейси доступу.3 //1. Використовується похідний інтерфейс BonusSavingAccount bsac("Pavlenko", 300, 20, 100); cout<<bsac.getNumber()<<':'<<bsac.state()<<endl; //2. Використовується другий похідний інтерфейс SavingAccount * psac = &bsac; cout getNumber()<<':'<< psac->state()<<endl; //3. Використовується базовий інтерфейс pac = &bsac; cout getNumber()<<':'<< pac->state()<<endl;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 39 Другий похідний об'єкт: Об'єкт класу BonusSavingAccount
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 40 Виділення базового інтерфейсу Account
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 41 Виділення іншого похідного інтерфейсу SavingAccount
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 42 Зрізані об'єкти На відміну від відсилок і указників ім'я забирає частину об'єкта на відведене для нього місце SavingAccount sac(Petrenko", 100,10); //Забудемо ощадну частину Account forgetful = sac; BonusSavingAccount bsac("Pavlenko", 300, 20, 100); //Забудемо бонусну частину SavingAccount forgetBonus = bsac;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 43
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 44
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 45 Ієрархічні присвоєння //Демонстраційний базовий клас class ShadowMakerNoAssign { private: char _myChar; char * _pChar; public: ShadowMakerNoAssign(char c): _myChar(c), _pChar(&_myChar){}; const char& getChar()const {return _myChar;} void* const getPChar() const { return _pChar;} };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 46 Діаграма демонстраційного об'єкта // поверхневе копіювання nshma=nshmb;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 47 Похідний демонстраційний клас class ShadowMakerDerivedNoAssign:public ShadowMakerNoAssign { private: int _myInt; int * _pInt; public: ShadowMakerDerivedNoAssign(char c, int i): ShadowMakerNoAssign(c), _myInt(i), _pInt(&_myInt){}; const int& getInt() const {return _myInt;} void* const getPInt() const { return _pInt;} };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 48 Поверхневе присвоєння похідного класу dnshma=dnshmb;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 49 Присвоєння в базовому класі ShadowMakerAssign & ShadowMakerAssign:: operator= (const ShadowMakerAssign& tar) { _myChar = tar._myChar; return *this; } shma = shmb;
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 50 ShadowMakerDerivedAssign& ShadowMakerDerivedAssign :: operator= (const ShadowMakerDerivedAssign& tar) { //Виклик базового присвоєння this->ShadowMakerAss::operator =(tar); //Присвоєння похідного об'єкта _myInt = tar._myInt; return *this;} };
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 51 Результат збалансованого присвоєння
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 52 Програмування присвоєнь При успадкуванні присвоєння або повністю програмують для всіх класів у ланцюжку успадкувань, або повністю забороняють, розмістивши його у закритій частині класів Завдання. Протестуйте варіанти класу ShadowMakerDerived з програмованим присвоєнням і без нього в комбінації з варіантами класу ShadowMaker
© Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 53 Висновок Відкрите успадкування це повне успадкування поведінки і реалізації Скотт Мейерс: Найважливіше правило об'єктно- орієнтованого програмування на С++ звучить так: відкрите успадкування означає, що один клас є різновидом (підтипом) іншого (public inheritance means «is a»). Твердо запам'ятайте це. Пам'ятайте також про правила виконання стандартних операцій: конструктори, деструктор, присвоєння
Секрет Все, що ми сьогодні робили за допомогою успадкування, робилося тільки для того, щоб зрозуміти, як влаштовані ієрархії класів і чим вони відрізняються від ієрархій об'єктів (до речі, чим?) © Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 54
Секрет Ми лише гралися іграшковими класами як деталями конструктора, щоб приладнати один клас до іншого. Відмінність від справжніх класів така ж, як у деталей конструктора і будівельних конструкцій © Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 55
Секрет LEGO так і залишиться іграшкою, навіть якщо в її стилі забудувати цілий район (Legoland у Мюнхені штаб квартира Сіменса) Але кращим архітектором стане той, хто спочатку навчиться збирати з конструктора модель стадіону у Пекіні або Білого дому у Вашінгтоні, а вже затим спроектує Legoland © Бублик В.В. ООП-2. Ієрархічне програмування. Лекція 9. Відкрите успадкування 56