ООП. Пособие по ООП в С++. Создание сложных программных систем Объектно ориентированная технология
Скачать 2.1 Mb.
|
Отношение части и целого. Если объект или список объектов включены в класс, то говорят об отношении агрегации. Так два крыла самолета являются его частью – то его неотъемлемые составляющие. При уничтожении самолета, уничтожены будут и его крылья. История полетов данного самолета связана с ним и является так же частью жизненного цикла аппарата. Но при уничтожении самолета его история может сохраниться. Приведенные примеры характеризуют два вида агрегации: композиция (сильная агрегация) и наполнение (слабая агрегация) 7.3.1 Композиция – сильная агрегация Это отношение между классами, когда объекты одного класса являются элементами другого класса. В С++ композиция реализуется включением в класс фиксированного числа полей, которые являются объектами другого класса (статический массив или просто поля типа класс). Количество полей может быть любым. Применение композиции зависит как от свойств объектов, включаемых в класс, так и от количества объектных полей. При удалении класса, объекты в ключенные в удаляемый объект удаляются. Библиотека позволяет читателю держать на руках не более 5 книг. Запись о книге взятой читателем является частью карточки читателя. Тогда карточка читателя CardReader может иметь список с фиксированным числом элементов. Т.е свойство recordlist –список книг взятых читателем можно реализовать статическим массивом из 5 элементов. Особенность отношения композиции: при удалении карточки читателя, удаляется и список взятых книг. +setinvnumer() : void +setdatain() : void +setdataout() : void +getinvnumer() : int +getdatain() : string +getdataout() : string -invnumer : int -datain : string -dataout : string RecordBook +setnumreader() : void +addrecordbook(в rb : RecordBook) : void +getlistrecordbook() : RecordBook[0..*] +getnumreader() : int -numreader : int -recordlist : RecordBook[0..*] CardReader -Содержит запись 1 0..* Реализация классов class RecordBook{ private : int invnumer; string datain; string dataout; public : void setdatain(string din){ datain=din; } void setdataout(string dout){ dataout=dout; } void setinvnumer( int num){ invnumer=num; } string getdatain(){ return datain; } string getdataout(){ return dataout; } int getinvnumer(){ return invnumer; } }; class CardReader{ private : int numreader; RecordBook recordlist[5]; int n; public : CardReader(){this.n=0;} void addRecordBook(RecordBook r){ recordlist[n]=r; } int getnumreader(){ return numreader; } vector } void setnumreader( int num){ numreader=num; } }; ostream& operator <<(ostream &s,CardReader &o){ s< ( int i=0;i } int _tmain( int argc, _TCHAR* argv[]) { ………………………………………………………………………………………… string d1= "10.01.2019" , d2= "17.02.2019" ; R.setinvnumer(createRecordBook(listBook)); R.setdatain(d1); R.setdataout(d2); CardReader CR; int numreader; cout<< "Введите номер читателя" < CR.setnumreader(numreader); CR.addRecordBook(R); CR.addRecordBook(R); return 0; } 7.3.2 Наполнение – слабая агрегация Отношение между классами, при котором количество объектов некоторого класса, включаемых в класс не ограничено и может меняться (динамические массивы или списки из объектов некоторого класса). Добавим в модель класс FondBooks (фонд книг библиотеки). Этот класс представляет все,имеющиеся в библиотеке книги - список книг, в этот список можно добавлять сведения о книгах, удалять. Представить в классе такой список можно динамическим массивом или файлом. +setname(в s : string) : void +setauthor(в author1 : string) : void +setinvnum(в n : int) : void +Book(в n : string, в a : string, в num : int) +getname() : string +getauthor() : string +getinvnum() : int -name : string -author : string -invnumer : int Book +addBook(в b : Book) +getlistBooks() : *Book +FondBooks() -listBooks : *Book FondBooks 1 * Реализация классов class Book{ private : string name; string author; int invnumer; public : Book(string name1,string author1, int invn){ name=name1; author=author1; invnumer=invn; } void setname(string sname){ name=sname; } void setauthor(string sauthor){ author=sauthor; } void setinvnumer( int num){ invnumer=num; } string getname(){ return name; } string getauthor(){ return name; } string getinvnum(){ return name; } }; class FondBooks{ private : vector : FondBooks(){n=0;listBooks=0;} void addBookinFond(Book b){ listBooks.push_back(b); } vector return listBooks; } }; void outputBook(vector ( int i=0;i std::cout<' ' <
<
' <
} int _tmain( int argc, _TCHAR* argv[]) { FondBooks fond; fond.push_back(Book( "AAA" , "Ab" ,111)); fond.push_back(Book( "BBB" , "Bb" ,122)); fond.push_back(Book( "CCC" , "Cb" ,133)); vector 0; } 7.4 Зависимость Для организации диалога с читателем введем в систему класс «Menu» с одним методом showlistreader, который показывает список книг, взятых читателем. Параметром для метода является массив listreader класса «CardReader» читателя. Таким образом, изменения, внесенные в класс «CardReader» могут потребовать и изменения класса «Menu». Класс «Menu» не принадлежит предметной области, а представляет системный класс приложения. +showlistbookreader(в r : *RecordBook) : void Menu +setnumreader() : void +addrecordbook(в rb : RecordBook) : void +getlistrecordbook() : RecordBook[0..*] +getnumreader() : int -numreader : int -recordlist : RecordBook[0..*] CardReader Рис. 7 — Зависимость Реализация классов class CardReader{ private : int numreader; vector : void addRecordBook(RecordBook r){ recordlist.push_back(r); } int getnumreader(){ return numreader; } vector } void setnumreader( int num){ numreader=num; } }; class Menu{ static void showlistbookreader(vector ( int i=0;i < < ' < { //............................... CardReader r;r.getrecordlist(); Menu::showlistbookreader(r.getrecordlist()); return 0; } 8 Полиморфизм Поддерживается только в наследовании. Это механизм, обеспечивающий возможность определения метода (методов) с одинаковой сигнатурой для классов различных уровней иерархии. В разделе 5.8.4.2 мы познакомились с перегрузкой методов, а теперь с переопределением методов. В разделе 5.8.4.2 мы познакомились с перегрузкой методов, а теперь с переопределением методов (функций-членов). Реализуется посредством переопределения функций. Метод базового класса и метод производного класса считаются переопределенными, если их сигнатуры идентичны, но функцию они будут реализовывать каждый по своему. Полиморфизм позволяет строить более гибкие и совершенные иерархии классов, заменяя в производных классах методы в соответствии с требованиями разрабатываемой программы. Рассмотрим, как при таком наследовании is-a обрабатываются ссылки и указатели на объекты. В С++ нельзя присваивать указателю одного типа адрес переменной другого типа, а так же ссылке на один тип ссылаться на другой тип. Пример. double x=2.5; int *pi=&x; long &r1=x; Ссылка или указатель на базовый класс может ссылаться на объект производного класса без явного приведения типа. Использование указателя и ссылки на объект базового класса для доступа к объектам наследникам. Указателю на объект базового класса можно присваивать адрес объекта производного класса (приведение вверх). Полиморфизм применяется к объектам, а не к классам. Это механизм, позволяющий определить в родительском классе группу методов, которые наследники могут переопределить и применять их к своим объектам, сохраняя функцию метода, но применительно к нуждам своего класса – т.е. создать единый интерфейс для всех объектов класса. Единый интерфейс позволяет программистам помнить функции родительского, зная, что метод производного класса выполняет то же действие, что и родительский (меньше надо запоминать). Механизм связывания метода с объектом. Методы применяются к объектам, т.е. метод при вызове связывается с объектом. Как компилятор понимает, какой из переопределенных методов должен быть связан с объектом, ведь сигнатуры у них одинаковы? Существует два механизма связывания: • Ранний полиморфизм ( раннее связывание). • Позднее (динамическое) связывание. 8.1 Ранний (Простой) полиморфизм Реализация раннего связывания метода и объекта - на этапе компиляции. Правила простого полиморфизма 4. Метод жестко связан с объектом на этапе компиляции. 5. Преобразование указателя производного класса в указатель базового класса. При присваивании указателю базового класса указателя производного класса операция выполнится корректно. Но поля и методы производного класса не будут видны указателю базового класса. Объект базового класса вызовет свои методы. 6. Внешние функции. Формальным параметрам - указателям на объект базового класса, при вызове метода можно передать в качестве фактического параметра – объект производного класса. Передача осуществляется по адресу. Но при этом передается адрес, который становится известным только на этапе выполнения, а связывание метода с объектами на этапе компиляции. Поэтому обращение через переданный объект произойдет обращение к базовому классу. Пример простого полиморфизма. Класс геометрических фигур. Базовый класс Base имеет методы вычисления площади (S) и периметра (P). Производные классы Круг и Квадрат переопределяют методы S и P, в каждом классе по своему. Переопределение методов S и P наследниками. class Base { private: char nameF[20]; public: Base(); Base( char *s); void set_name( char *s); char * get_name() ; double S(); //вычисление пощади double P(); //вычисление периметра }; Base::Base(){ strcpy_s(nameF, "" );} Base:: Base( char *s){ strcpy_s(nameF,s);} void Base::set_name( char *s) { strcpy_s(nameF,s); } char * Base::get_name() { return nameF; } double Base::S() { return 0; } double Base::P() { return 0; } //Наследник - Круг class Circle: public Base { private: double r; public: Circle(){r=0;} Circle( double r1, char *s):Base(s) {r=r1;} Circle() {cout<<"delete object";} double S(); double P(); }; double Circle::S() { return 3.14*r*r; } double Circle::P() { return (2*3.14*r); } //Наследник - Квадрат class Rect: public Base { private: double a,b; public: Rect():Base(){a=b=0;} Rect( double a1, double b1, char *s):Base(s){a=a1; b=b1;} double S(); double P(); }; double Rect::S() { return a*b; } double Rect::P() { return (2*a+2*b); } int main() { Base B; cout<< "P=" < << "S=" < ; strcpy_s(s, "Circle" ); Circle C(3,s); B=C; //присваивание объектов побайтно, но присаиваются тоько данные, а ссылки на методы – нет, поэтому вызовутся методы родительского класса. cout< < << "S=" < //присваивание адреса объекта С cout< get_name()<< "P=" < P()<< ' ' << "S=" < >S()< Результат 8.2 Поздний (Сложный) полиморфизм Для выполнения правильного связывания объекта с переопределенным методом необходимо использование сложного полиморфизма (позднее связывание). Такой полиморфизм реализуется посредством виртуальных функций. Виртуальные функции определяются в базовом классе с ключевым словом virtual и они переопределяются (замещаются в производных классах). При этом прототипы функций в разных классах идентичны, но функции отличаются алгоритмами. Три правила полиморфизма (определенные в разделе 8.1), начинают работать правильно, связывают объект и метод на этапе выполнения правильно: 1. При использовании указателя на базовый класс, которому присвоен адрес объекта производного класса, вызовет метод присвоенного объекта. 2. Такой же результат будет и у внешней функции, параметр которой указатель на базовый класс, а примет она в качестве параметра адрес производного класса. 3. Виртуальная функция остается таковой во всех производных классах. Если она в каком-то классе не переопределена, то механизм виртуальных функций в этом классе сохраняется. Виртуальная функция может быть дружественной другому классу. При реализации виртуальной функции наследниками слово virtual не указывается. Пример реализации класса геометрическая фигура и реализация позднего полиморфизма. В данном примере достаточно определить виртуальные методы в базовом классе, реализация методов в производных классах не изменяется. //Базовый класс – Плоская геометрическая фигура class Base { private: char nameF[20]; public: Base(); Base( char *s); void set_name( char *s); char * get_name() ; virtual double S(); //вычисление пощади – виртуаьная функция virtual double P(); //вычисление периметра– виртуаьная функция }; Base::Base(){ strcpy_s(nameF,"");} Base:: Base( char *s){ strcpy_s(nameF,s);} void Base::set_name( char *s) { strcpy_s(nameF,s); } char * Base::get_name() { return nameF; } double Base::S() { return 0; } double Base::P() { return 0; } //Наследник - Круг class Circle: public Base { private: double r; public: Circle(){r=0;} Circle( double r1, char *s):Base(s) {r=r1;} Circle() {cout<<"delete object";} double S(); double P(); }; //Наследник - Квадрат class Rect: public Base { private: double a,b; public: Rect():Base(){a=b=0;} Rect( double a1, double b1, char *s):Base(s){a=a1; b=b1;} double S(); double P(); }; int _tmain( int argc, _TCHAR* argv[]) { Base B; cout<< "P=" < << "S=" < ; strcpy_s(s, "Circle" ); Circle C(3,s); B=C; //позднее связывание не выпоняется так как В не ссылка cout< < << "S=" < get_name()<< "P=" < P()<< ' ' << "S=" < >S()< for ( int i=0;i<5;i++) Masobject[i]= new Base; strcpy_s(s, "Base" ); //заполнение массива ссылками на объекты производных классов Masobject[0]=&Base(s); strcpy_s(s, "Circle" ); Masobject[1]=&C; Circle C1(8,s); Masobject[2]=&C1; strcpy_s(s, "Rect" ); Masobject[3]=&Rect(2,3,s); Masobject[3]=&Rect(2,3,s); cout< Результат Механизм позднего связывания Основан на таблице виртуальных методов ТВМ. Таблица хранит информацию о переопределяемых методах. Ссылка на таблицу присутствует в объекте, использующем виртуальные функции. При вызове переопределенного метода он будет найден в ТВМ. 9 Абстрактные классы Это класс, который не предполагает создание экземпяра. В ООП родительский класс, выражает некоторую общую концепцию, описывает интерфейс для использования в производных классах. Этот интерфейс поддерживают производные классы. Абстрактные классы невозможно использовать для следующих элементов: • переменных и данных членов; • типов аргументов; • типов возвращаемых функциями значений; • типов явных преобразований. Другое ограничение состоит в том, что при вызове конструктором абстрактного класса чистой виртуальной функции (прямо или косвенно) результат будет неопределенным. Однако конструкторы и деструкторы абстрактных классов могут вызывать другие функции-члены. Для абстрактных классов можно определять чистые виртуальные функции, но вызывать их можно только непосредственно с использованием следующего синтаксиса: Имя абстрактного класса::имя функции) Это помогает при разработке иерархий классов, базовые классы которых содержат чистые виртуальные деструкторы, поскольку деструкторы базовых классов всегда вызываются в процессе удаления объекта. Рассмотрим следующий пример. Абстрактный класс предоставляет программисту удобство при использовании производных классов, так как они используют интерфейс базового класса, заставляя в дочерних кассах переопределить чисто виртуальные функции. Абстрактный класс содержит члены - данные и члены - функции, которые будут |