Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Различные пути передачи функциональности классу
В некоторых случаях одному классу необходимо передать некоторые свойства другого. Предположим, например, что вам необходимо создать класс каталога деталей PartsCatalog. На основе класса PartsCatalog предполагается создать коллекцию объектов, представляющую различные запчасти с уникальными номерами. В базе данных на основе класса PartsCatalog запрещается дублирование объектов, а для доступа к объекту необходимо указать его идентификационный номер. Ранее, в обзоре за вторую неделю, уже был объявлен и детально проанализирован класс PartsList. Чтобы не начинать работу с нуля, можно взять этот класс за основу при объявлении класса PartsCatalog. Для этого можно вложить класс PartsList в класс PartsCatalog, чтобы затем делегировать классу PartsCatalog ответственность за поддержание связанного списка в класс PartsList. Существует альтернативный путь. Можно произвести класс PartsCatalog от класса PartsList, таким образом унаследовав все свойства последнего. Помните однако, что открытое наследование (со спецификатором public) предполагает логическую принадлежность производного класса более общему базовому классу. Действительно ли в нашем случае класс PartsCatalog является частным проявлением класса PartList? Чтобы разобраться в этом, попробуйте ответить на ряд вопросов. 1. Содержит ли базовый класс PartsList методы, не применимые в классе PartsCatalog? Если да, то, вероятно, от открытого наследования лучше отказаться. 2. Будет ли один объект класса PartsCatalog соответствовать одному объекту класса PartsList? Если для создания объекта требуется не менее двух объектов PartsList, то, безусловно, необходимо применять вложение. 3. Обеспечит ли наследование от базового класса преимущества в работе благодаря использованию виртуальных функций или методов доступа к защищенным членам базового класса? В случае положительного ответа имеет смысл воспользоваться открытым или закрытым наследованием. Ответив на приведенные выше вопросы, вы должны принять решение, использовать ли вам в программе открытое наследование, закрытое наследование (см. далее в этом занятии) или вложение. Познакомимся с некоторыми терминами, которые потребуются нам при дальнейшем обсуждении этой темы. • Вложение — объект, относящийся к другому классу, используется в текущий класс. • Делегирование — передача ответственности за выполнение специальных функций вложенному классу. • Выполнение средствами класса — реализация специальных функций в классе за счет другого класса без использования открытого наследования. Делегирование
Почему же класс PartsCatalog нельзя произвести от PartsList? Дело в том, что класс PartsCatalog должен обладать совершенно иными свойствами и ero невозможно представить как частную реализацию класса PartsList. Посмотрите, класс PartsList — это коллекция объектов, упорядоченная по возрастанию номеров, элементы которой могут повторяться. Класс PartsCatalog представляет неупорядоченную коллекцию уникальных объектов. Конечно, при желании можно произвести класс PartsList от класса PartsList со спецификатором public, после чего соответствующим образом заместить функцию Insert() и оператор индексирования ([]). Однако такие действия крайне нелогичны и противоречат самой сути наследования. Вместо этого следует создать новый класс PartsCatalog, в котором нет оператора индексирования, не разрешается дублирование записей и перегружается operator+ для суммирования наборов записей. Функцию управления связанным списком оставим классу PartsList. Попробуем сначала решить эту задачу путем вложения одного класса в другой с делегированием ответственности от класса классу, как показано в листинге 15.5. Листинг 15.5. Делегирование ответственности классу PartsList, включенному в класс PartsCatalog 1: #include < iostream.h> 2: 3: // **************** Класс Part ************ 4: 5: // Абстрактный базовый класс всех деталей 6: class Part 7: { 8: public: 9: Part(): itsPartNumber(1) { } 10: Part(int PartNumber): 11: itsPartNumber(PartNumber){ } 12: virtual ~Part(){ } 13: int GetPartNumber() const 14: { return itsPartNumber; } 15: virtual void Display() const =0; 16: private: 17: int itsPartNumber; 18: }; 19: 20: // выполнение чистой виртуальной функции в 21: // стандартном виде для всех производных классов 22: void Part:: Display() const 23: { 24: cout < < " \nPart Number: " < < itsPartNumber < < endl; 25: } 26: 27: // ************ Автомобильные детали ********** 28: 29: class CarPart: public Part 30: { 31: public: 32: CarPart(): itsModelYear(94){ } 33: CarPart(int year, int partNumber); 34: virtual void Display() const 35: { 36: Part:: Display(); 37: cout < < " Model Year: "; 38: cout < < itsModelYear < < endl; 39: } 40: private: 41: int itsModelYear; 42: }; 43: 44: CarPart:: CarPart(int year, int partNumber): 45: itsModelYear(year), 46: Part(partNumber) 47: { } 48: 49: 50: // ************* Авиационные детали ************ 51: 52: class AirPlanePart: public Part 53: { 54: public: 55: AirPlanePart(): itsEngineNumber(1){ } 56: AirPlanePart 57: (int EngineNumber, int PartNumber) 58: virtual void Dlsplay() const 59: { 60: Part:: Display(); 61: cout < < " Engine No.: "; 62: cout < < itsEngineNumber < < endl; 63: } 64: private: 65: int itsEngineNumber; 66: }; 67: 68: AirPlanePart:: AirPlanePart 69: (int EngineNumber, int PartNumber): 70: itsEngineNumber(EngineNumber), 71: Part(PartNumber) 72: { } 73: 74: // *************** Узлы списка деталей ********** 75: class PartNode 76: { 77: public: 78: PartNode (Part*); 79: ~PartNode(); 80: void SetNext(PartNode * node) 81: { itsNext = node; } 82: PartNode * GetNext() const; 83: Part * GetPart() const; 84: private: 85: Part *itsPart; 86: PartNode * itsNext; 87: }; 88: // Выполнение PartNode... 89: 90: PartNode:: PartNode(Part* pPart): 91: itsPart(pPart), 92: itsNext(0) 93: { } 94: 95: PartNode:: ~PartNode() 96: { 97: delete itsPart; 98: itsPart = 0; 99: delete itsNext; 100: itsNext = 0; 101: } 102: 103: // Возвращается NULL, если нет следующего узла PartNode 104: PartNode * PartNode:: GetNext() const 105: { 106: return itsNext; 107: } 108: 109: Part * PartNode:: GetPart() const 110: { 111: if (itsPart) 112: return itsPart; 113: else 114: return NULL; //ошибка 115: } 116: 117: 118: 119: // **************** Список деталей *********** 120: class PartsList 121: { 122: public: 123: PartsList(); 124: ~PartsList(); 125: // необходимо, чтобы конструктор-копировщик и оператор соответствовали друг другу! 126: void Iterate(void (Part:: *f)()const) const; 127: Part* Find(int & position, int PartNumber) const; 128: Part* GetFirst() const; 129: void Insert(Part *); 130: Part* operator[](int) const; 131: int GetCount() const { return itsCount; } 132: static PartsList& GetGlobalPartsList() 133: { 134: return GiobalPartsList; 135: } 136: private: 137: PartNode * pHead; 138: int itsCount; 139: static PartsList GiobalPartsList; 140: }; 141: 142: PartsList PartsList:: GlobalPartsList; 143: 144: 145: PartsList:: PartsList(): 146: pHead(0), 147: itsCount(0) 148: { } 149: 150: PartsList:: ~PartsList() 151: { 152: delete pHead; 153: } 154: 155: Part* PartsList:: GetFirst() const 156: { 157: if (pHead) 158: return pHead-> GetPart(); 159: else 160: return NULL; // ловушка ошибок 161: } 162: 163: Part * PartsList:: operator[](int offSet) const 164: { 165: PartNode* pNode = pHead; 166: 167: if (! pHead) 168: return NULL; // ловушка ошибок 169: 170: if (offSet > itsCount) 171: return NULL; // ошибка 172: 173: for (int i=0; i< offSet; i++) 174: pNode = pNode-> GetNext(); 175: 176: return pNode-> GetPart(); 177: } 178: 179: Part* PartsList:: Find( 180: int & position, 181: int PartNumber) const 182: { 183: PartNode * pNode = 0; 184: for (pNode = pHead, position = 0; 185: pNode! =NULL; 186: pNode = pNode-> GetNext(), position++) 187: { 188: if (pNode-> GetPart()-> GetPartNumber()== PartNumber) 189: break; 190: } 191: if (pNode == NULL) 192: return NULL; 193: else 194: return pNode-> GetPart(); 195: } 196: 197: void PartsList:: Iterate(void (Part:: *func)()const) const 198: { 199: if (! pHead) 200: return; 201: PartNode* pNode = pHead; 202: do 203: (pNode-> GetPart()-> *func)(); 204: while (pNode = pNode-> GetNext()); 205: } 206: 207: void PartsList:: Insert(Part* pPart) 208: { 209: PartNode * pNode = new PartNode(pPart); 210: PartNode * pCurrent - pHead; 211: PartNode * pNext = 0; 212: 213: int New = pPart-> GetPartNumber(); 214: int Next = 0; 215: itsCount++; 216: 217: if (! pHead) 218: { 219: pHead = pNode; 220: return; 221: } 222: 223: // если это значение меньше головного узла, 224: // то текущий узел становится головным 225: if (pHead-> GetPart()-> GetPartNumber()-> New) 226: { 227: pNode-> SetNext(pHead); 228: pHead = pNode; 229: return; 230: } 231: 232: for (;; ) 233: { 234: // если нет следующего, вставляется текущий 235: if (! pCurrent-> GetNext()) 236: { 237: pCurrent-> SetNext(pNode); 238: return; 239: } 240: 241: // если текущий больше предыдущего, но меньше следующего, то вставляем 242: // здесь. Иначе присваиваем значение указателя Next 243: pNext = pCurrent-> GetNext(); 244: Next = pNext-> GetPart()-> GetPartNumber(); 245: if (Next > New) 246: { 247: pCurrent-> SetNext(pNode); 248: pNode-> SetNext(pNext); 249: return; 250: } 251: pCurrent = pNext; 252: } 253: } 254: 255: 256: 257: class PartsCatalog 258: { 259: public: 260: void Insert(Part *); 261: int Exists(int PartNumber); 262: Part * Get(int PartNumber); 263: operator+(const PartsCatalog & ); 264: void ShowAll() { thePartsList.Iterate(Part:: Display); } 265: private: 266: PartsList thePartsList; 267: }; 268: 269: void PartsCatalog:: Insert(Part * newPart) 270: { 271: int partNumber = newPart-> GetPartNumber(); 272: int offset; 273: 274: if (! thePartsList, Find(offset, partNumber)) 275: 276: thePartsList.Insert(newPart); 277: else 278: { 279: cout < < partNumber < < " был "; 280: switch (offset) 281: { 282: case 0: cout < < " first "; break; 283: case 1: cout < < " second "; break; 284: case 2: cout < < " third "; break; 285: default; cout < < offset+1 < < " th "; 286: } 287: cout < < " entry. Rejected! \n"; 288: } 289: } 290: 291: int PartsCatalog:: Exists(int PartNumber) 292: { 293: int offset; 294: thePartsList.Find(offset, PartNumber); 295: return offset; 296: } 297: 298: Part * PartsCatalog:: Get(int PartNumber) 299: { 300: int offset; 301: Part * thePart = thePartsList.Find(offset, PartNumber); 302: return thePart; 303: } 304: 305: 306: int main() 307: { 308: PartsCatalog pc; 309: Part * pPart = 0; 310: int PartNumber; 311: int value; 312: int choice; 313: 314: while (1) 315: { 316: cout < < " (0)Quit (1)Car (2)Plane: "; 317: cin > > choice; 318: 319: if (! choice) 320: break; 321: 322: cout < < " New PartNumber?: "; 323: cin > > PartNumber; 324: 325: if (choice == 1) 326: { 327: cout < < " Model Year?: "; 328: cin > > value; 329: pPart = new CarPart(value, PartNumber); 330: } 331: else 332: { 333: cout < < " Engine Number?: "; 334: cin > > value; 335: pPart = new AirPlanePart(value, PartNumber); 335: } 337: pc.Insart(pPart); 338: } 339: pc.ShowAli(); 340: return 0; 341: }
Результат: (0)Qult (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Примечание: Некоторые компиляторы не смогут откомпилировать строку 264, хотя она вполне соответствует стандартам C++. Если ваш компилятор возразит против записи этой строки, замените ее строкой 264: void ShowAll() { thePartsList.Iterate(& Part:: Display): } (Обратите внимание на добавление амперсанта (знак & ) перед Part: Display.) Если это сработает, свяжитесь с фирмой, поставившей вам этот компилятор, и поинтересуйтесь, где они его " откопали".
Анализ: В листинге 15.5 используются классы Part, PartNode и PartsList, с которыми вы уже познакомились при подведении итогов второй недели. Новый класс PartsCatalog объявляется в строках 257—267. Он использует PartsList как свою переменную-член, которой делегирует управление списком. Другими словами, класс PartsCatalog выполняется средствами классе PartsList. Обратите внимание, что клиенты класса PartsCatalog не имеет прямого доступа к классу PartsList. Интерфейс класса PartsList реализуется методами класса PartsCatalog, что существенно изменяет его поведение. Например, метод PartsCatalog:: Insert() не позволяет дублировать данные, вводимые в PartsList. Определение выполнения функции PartsCatalog: : Insert() начинается в строке 269. У объекта Part, передаваемого как параметр, запрашивается значение его переменной- члена itsPartNumber. Это значение передается методу Find() класса PartsList, и объект добавляется в список, если только в списке не найден другой объект с таким же номером. В противном случае возвращается сообщение об ошибке. Обратите внимание, что в методе Insert() класса PartCatolog используется переменная-член этого класса thePartList, являющаяся объектом класса PartList. Процедура поддержания связного списка и добавления объектов в него, а также поиска и возвращения данных из списка полностью реализуется вложенным классом PartsList, объект которого является переменной-членом класса PartsCatalog. Вместо того чтобы повторять все процедуры обработки записей списка в классе PartsCatalog, методами этого класса просто создается удобный интерфейс для уже существующего класса PartsList. Именно в этом и состоит суть модульности программирования на C++. Удачно созданный однажды модуль, такой как PartsLists, можно многократно использовать в других программах, например с классом PartsCatalog. При этом разработчиков нового класса PartsCatalog могут совершенно не интересовать детали выполнения модуля PartsList. Интерфейс класса PartsList (в данном случае под интерфейсом понимается его объявление) предоставляет всю информацию, необходимую разработчику нового класса PartsCatalog.
Закрытое наследование
Если бы для PartsCatalog был необходим доступ к защищенным членам PartsList (в данном примере таковых нет) или в PartsCatalog использовались замещенные методы PartsList, то его можно было бы просто унаследовать от PartsList. Однако, поскольку PartsCatalog не является объектом PartsList и нежелательно предоставлять весь набор функциональных возможностей PartsList клиентам PartsCatalof, следует применить закрытое наследование. Первое, что необходимо знать: при закрытом наследовании все переменные и функции-члены базового класса трактуются так, как если бы они были объявлены закрытыми, независимо от установок доступа в базовом классе. Таким образом, для любой функции, не являющейся функцией-членом PartsCatalog, недоступны функции, унаследованные из PartsList. Это очень важно: закрытое наследование не передает в производный класс интерфейс базового класса. Класс PartsList невидим для клиентов класса PartsCatalog. Поэтому последним недоступен интерфейс класса PartsList и они не могут вызывать его методы. Однако пользователям будут доступны все методы класса PartsCatalog, имеющие доступ ко всем членам класса PartsList, так как класс PartsCatalog является производным от PartList. Важно также то, что объекты PartsCatalog не являются объектами PartsList, как было бы при использовании открытого наследования. Класс PartsCatalog выполняется методами класса PartsList, как в случае с вложением. Применение закрытого наследования не менее удобно. Использование закрытого наследования показано в листинге 15.6. Класс PartsCatalog производится как private от класса PartsList. Листинг 15.6. Закрытое наследование 1: // Листинг 15.6. Закрытое наследование 2: #include < iostream.h> 3: 4: //****************Класс Part ************ 5: 6: // Абстрактный базовый класс всех деталей 7: class Part 8: { 9: public: 10: Part(): itsPartNumber(1) { } 11: Part(int PartNumber): 12: itsPartNumber(PartNumber){ } 13: virtual ~Part(){ } 14: int GetPartNumber() const 15: { return itsPartNumber; } 16: virtual void Display() const =0; 17: private: 18: int itsPartNumber; 19: }; 20: 21: // выполнение чистой виртуальной функции в 22: // стандартном виде для всех производных классов 23: void Part:: Display() const 24: { 25: cout < < " \nPart Number: " < < itsPartNumber < < endl; 26: } 27: 28: // **************** Car Part ************ 29: 30: class CarPart: public Part 31: { 32: public: 33: CarPart(): itsModelYear(94){ } 34: CarPart(int year, int partNumber); 35: virtual void Display() const 36: { 37: Part:: Display(); 38: cout < < " Model Year: "; 39: cout < < itsModelYear < < endl; 40: } 41: private: 42: int itsModelYear; 43: }; 44: 45: CarPart:: CarPart(int year, int partNumber): 46: itsModelYear(year), 47: Part(partNumber) 48: { } 49: 50: 51: // *********** Класс AirPlane Part ********** 52: 53: class AirPlanePart: public Part 54: { 55: public: 56: AirPlanePart(): itsEngineNumber(1){ } 57: AirPlanePart 58: (int EngineNumber, int PartNumber); 59: virtual void Display() const 60: { 61: Part:: Display(); 62: cout < < " Engine No.: "; 63: cout < < itsEngineNumber < < endl; 64: } 65: private: 66: int itsEngineNumDer; 67: }; 68: 69: AirPlanePart:: AirPlanePart 70: (int EngineNumber, int PartNumber): 71: itsEngineNumber(EngineNumber), 72: Part(PartNumber) 73: { } 74: 75: // ************ Класс Part Node ************ 76: class PartNode 77: { 78: public: 79: PartNode (Part> > ); 80: ~PartNode(); 81: void SetNext(PartNode * node) 82: { itsNext = node; } 83: PartNode * GetNext() const; 84: Part * GetPart() const; 85: private: 86: Part *itsPart; 87: PartNode * itsNext; 88: }; 89: //Выполнение PartNode... 90: 91: PartNode:: PartNode(Part* pPart): 92: itsPart(pPart), 93: itsNext(0) 94: { } 95: 96: PartNode:: ~PartNode() 97: { 98: delete itsPart; 99: itsPart = 0; 100: delete itsNext; 101: itsNext = 0; 102: } 103: 104: // Возвращает NULL NULL, если нет следующего узла PartNode 105: PartNode * PartNode:: GetNext() const 106: { 107: return itsNext; 108: } 109: 110: Part * PartNode:: GetPart() const 111: { 112: if (itsPart) 113: return itsPart; 114: else 115: return NULL; //ошибка 116: } 117: 118: 119: 120: // ************ Класс Part List ************ 121: class PartsList 122: { 123: public: 124: PartsList(); 125: ~PartsList(); 126: // Необходимо, чтобы конструктор-копировщик и оператор соответствовали друг другу! 127: void Iterate(void (Part:: *f)()const) const; 128: Part* Find(int & position, int PartNumber) const; 129: Part* GetFirst() const; 130: void Insert(Part *); 131: Part* operator[](int) const; 132: int GetCount() const { return itsCount; } 133: static PartsList& GetGlobalPartsList() 134: { 135: return GiobalPartsList; 136: } 137: private: 138: PartNode * pHead; 139: int itsCount; 140: static PartsList GiobalPartsList; 141: }; 142: 143: PartsList PartsList:: GlobalPartsList; 144: 145: 146: PartsList:: PartsList(): 147: pHead(0), 148: itsCount(0) 149: { } 150: 151: PartsList:: ~PartsList() 152: { 153: delete pHead; 154: } 155: 156: Part* PartsList:: GetFirst() const 157: { 158: if (pHead) 159: return pHead-> GetPart(); 160: else 161: return NULL; // ловушка ошибок 162: } 163: 164: Part * PartsList:: operator[](int offSet) const 165: { 166: PartNode* pNode = pHead; 167: 168: if (! pHead) 169: return NULL; // ловушка ошибок 170: 171: if (offSet > itsCount) 172: return NULL; // ошибка 173: 174: for (int i=0; i< offSet; i++) 175: pNode = pNode-> GetNext(); 176: 177: return pNode-> GetPart(); 178: } 179: 180: Part* PartsList:: Find( 181: int & position, 182: int PartNumber) const 183: { 184: PartNode * pNode = 0; 185: for (pNode = pHead, position = 0; 186: pNode! =NULL; 187: pNode = pNode-> GetNext(), position++) 188: { 189: if (pNode-> GetPart()-> GetPartNumber() == PartNumber) 190: break; 191: } 192: if (pNode == NULL) 193: return NULL; 194: else 195: return pNode-> GetPart(); 196: } 197: 198: void PartsList:: Iterate(void (Part:: *func)()const) const 199: { 200: if (! pHead) 201: return; 202: PartNode* pNode = pHead; 203: do 204: (pNode-> GetPart( )-> *func)(); 205: while (pNode = pNode-> GetNext()); 206: } 207: 208: void PartsList:: Insert(Part* pPart) 209: { 210: PartNode * pNode = new PartNode(pPart); 211: PartNode * pCurrent = pHead; 212: PartNode * pNext = 0; 213: 214: int New = pPart-> GetPartNumber(); 215: int Next = 0; 216: itsCount++; 217: 218: if (! pHead) 219: { 220: pHead = pNode; 221: return; 222: } 223: 224: // если это значение меньше головного узла, 225: // то текущий узел становится головным 226: if (pHead-> GetPart()-> GetPartNumber()-> New) 227: { 228: pNode-> SetNext(pHead); 229: pHead = pNode; 230: return; 231: } 232: 233: for (;; ) 234: { 235: // еcли нет следующего, вставляется текущий 236: if (! pCurrent-> GetNext()) 237: { 238: pCurrent-> SetNext(pNode); 239: return; 240: } 241: 242: // если текущий больше предыдущего, но меньше следующего, то вставляем 243: // здесь, Иначе присваиваем значение указателя Next 244: pNext = pCurrent-> GetNext(); 245: Next = pNext-> GetPart()-> GetPartNumber(); 246: if (Next > New) 247: { 248: pCurrent-> SetNext(pNode); 249: pNode-> SetNext(pNext); 250: return; 251: } 252: pCurrent = pNext; 253: } 254: } 255: 256: 257: 258: class PartsCatalog: private PartsList 259: { 260: public: 261: void Insert(Part *); 262: int Exists(int PartNumber); 263: Part * Get(int PartNumber); 264: operator+(const PartsCatalog & ); 265: void ShowAll() { Iterate(Part:: Display); } 266: private: 267: }; 268: 269: void PartsCatalog:: Insert(Part * newPart) 270: { 271: int partNumber = newPart-> GetPartNumber(); 272: int offset; 273: 274: if (! Find(offset, partNumber)) 275: PartsList:: Insert(newPart); 276: else 277: { 278: cout < < partNumber < < " was the "; 279: switch (offset) 280: { 281: case 0: cout < < " first "; break; 282: case 1: cout < < " second "; break; 283: case 2: cout < < " third "; break; 284: default: cout < < offset+1 < < " th "; 285: } 286: cout < < " entry. Rejected! \n"; 287: } 288: } 289: 290: int PartsCatalog:: Exists(int PartNumber) 291: { 292: int offset; 293: Find(offset, PartNumber); 294: return offset; 295: } 296: 297: Part * PartsCatalog:: Get(int PartNumber) 298: { 299: int offset; 300: return (Find(offset, PartNumber)); 301: 302: } 303: 304: int main() 305: { 306: PartsCatalog pc; 307: Part * pPart = 0; 308: int PartNumber; 309: int value; 310: int choice; 311: 312: while (1) 313: { 314: cout < < " (0)Quit (1)Car (2)Plane: "; 315: cin > > choice; 316: 317: if (! choice) 318: break; 319: 320: cout < < " New PartNumber?: "; 321: cin > > PartNumber; 322: 323: if (choice == 1) 324: { 325: cout < < " Model Year?: "; 326: cin > > value; 327: pPart = new CarPart(value, PartNumber); 328: } 329: else 330: { 331: cout < < " Engine Number?: "; 332: cin > > value; 333: pPart = newAirPlanePart(value, PartNumber); 334: } 335: pc.Insert(pPart); 336: } 337: pc.ShowAll(); 338: return 0; 339: }
Результат: (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Анализ: В листинге 15.6 был изменен интерфейс класса PartsCatalog и переписана функция main(). Интерфейсы других классов остались такими же, как и в листинге 15.5. В строке 258 листинга 15.6 класс PartsCatalog производится как private от класса PartsList. Интерфейс класса PartsCatalog остался таким же, как и в листинге 15.5, хотя, конечно же, необходимость в объектах класса PartsList как переменных-членах отпала. Функция-член ShowAll() класса PartsCatalog вызывает функцию Iterate() из PartsList, параметром которой задается указатель на функцию-член класса Part. Таким образом, функция ShowAll() выполняет роль открытого интерфейса, позволяя пользователям получать информацию, не обращаясь напрямую к закрытой функции Iterate(), прямой доступ к которой закрыт для клиентов класса PartsCatalog. Функция Insert( )тоже изменилась. Обратите внимание, в строке 274 функция Find() теперь вызывается непосредственно, поскольку она наследуется из базового класса. Чтобы при вызове функции Insert() не возникло зацикливания функции на самое себя, в строке 275 делается явный вызов функции с указанием имени класса. Таким образом, если методам класса PartsCatalog необходимо вызвать методы PartsList, они могут делать это напрямую. Единствейное исключение состоит в том, что при необходимости заместить метод базового класса в классе PartsList следует явно указать класс и имя функции. Закрытое наследование позволяет PartsCatalog унаследовать функциональность базового класса и создавать интерфейс, косвенно открывающий доступ к его методам, которые нельзя вызывать напрямую.
Рекомендуется: Применяйте открытое наследование, когда производный класс является разновидностью базового. Используйте вложение классов, когда необходимо делегировать выполнение задач другому классу, ограничив при этом доступ к его защищенным членам. Применяйте закрытое наследование, если необходимо реализовать один класс в пределах другого и обеспечить доступ к защищенным членам базового класса.
Не рекомендуется: Не применяйте закрытое наследование, если необходимо использовать более одного объекта базового класса. Для этих целей больше подойдет вложение классов. Например, если для одного объекта PartsCatalog необходимы два объекта PartsList, вы не сможете использовать закрытое наследование. Не используйте открытое наследование, если необходимо закрыть клиентам производного класса прямой доступ к методам базового класса.
Классы друзья
Иногда для выполнения задач, поставленных перед программой, необходимо обеспечить взаимодействие нескольких независимых классов. Например, классы PartNode и PartsList тесно взаимосвязаны, и было бы удобно, если бы в PartsList можно было напрямую использовать указатель itsPart класса PartNode. Конечно, можно было бы объявить itsPart как открытую или хотя бы защищенную переменную-член, но это далеко не лучший путь, противоречащий самой идее использования классов. Поскольку указатель itsPart является специфическим членом класса PartNode, его следует оставить недоступным для внешних классов. Однако, если вы хотите предоставить данные или закрытые методы какому-либо иному классу, достаточно объявить этот класс другом. Это расширит интерфейс вашего класса возможностями класса-друга. После того как в PartsNode класс PartsList будет объявлен другом, переменные- члены и методы класса PartsNode станут доступными для PartsList. Важно заметить, что дружественность класса не передается на другие классы. Иными словами, если вы мой друг, а Ваня — ваш друг, это вовсе не значит, что Ваня также и мой друг. Кроме того, дружба не наследуется. Опять же, хотя вы мой друг и я хочу рассказать вам свои секреты, это не означает, что я желаю поделиться ими с вашими детьми. Наконец, дружественность классов односторонняя. Объявление одного класса другом какого-либо иного класса не делает последний другом первого. Вы при желании может поделиться своими секретами со мной, но это не значит, что я должен рассказать вам свои секреты. В листинге 15.7 представлена версия листинга 15.6, в которой используется объявление класса друга. Так, класс PartsList объявляется как друг класса PartNode. Еще раз напомним, что это объявление не делает класс PartNode другом класса PartsList. Листинг 15.7. Использование классов-друзей 1: #include < iostream.h> 2: 3: 4: 5: 6: // **************** Класс Part ************ 7: 8: // Абстрактный базовый класс всех деталей 9: class Part 10: { 11: public: 12: Part(): itsPartNumber(1) { } 13: Part(int PartNumber): 14: itsPartNumber(PartNumber){ } 15: virtual ~Part(){ } 16: int GetPartNumber() const 17: { return itsPartNumber; } 18: virtual void Display() const =0; 19: private: 20: int itsPartNumber; 21: }; 22: 23: // выполнение чистой виртуальной функции в 24: // стандартном виде для всех производных классов 25: void Part:: Display() const 26: { 27: cout < < " \nPart Number: "; 28: cout < < itsPartNumber < < endl; 29: } 30: 31: // ************** Класс Car Part ************ 32: 33: class CarPart: public Part 34: { 35: public: 36: CarPart(): itsModelYear(94){ } 37: CarPart(int year, int partNumber); 38: virtual void Display() const 39: { 40: Part:: Display(); 41: cout < < " Model Year: "; 42: cout < < itsModelYear < < endl; 43: } 44: private: 45: int itsModelYear; 46: }; 47: 48: CarPart:: CarPart(int year, int partNumber): 49: itsModelYear(year), 50: Part(partNumber) 51: { } 52: 53: 54: // *********** Класс AirPlane Part *********** 55: 56: class AirPlanePart: public Part 57: { 58: public: 59: AirPlanePart(): itsEngineNumber(1){ }; 60: AirPlanePart 61: (int EngineNumber, int PartNumber); 62: virtual void Display() const 63: { 64: Part:: Display(); 65: cout < < " Engine No.: "; 66: cout < < itsEngineNumber < < endl; 67: } 68: private: 69: int itsEngineNumber; 70: }; 71: 72: AirPlanePart:: AirPlanePart 73: (int EngineNumber, int PartNumber): 74: itsEngineNumber(EngineNumber), 75: Part(PartNumber) 76: { } 77: 78: // **************** Класс Part Node ************ 79: class PartNode 80: { 81: public: 82: friend class PartsList; 83: PartNode (Part*); 84: ~PartNode(); 85: void SetNext(PartNode * node) 86: { itsNext = node; } 87: PartNode * GetNext() const; 88: Part * GetPart() const; 89: private: 90: Part *itsPart; 91: PartNode * itsNext; 92: }; 93: 94: 95: PartNode:: PartNode(Part* pPart): 96: itsPart(pPart), 97: itsNext(0) 98: { } 99: 100: PartNode:: ~PartNode() 101: { 102: delete itsPart; 103: itsPart = 0; 104: delete itsNext; 105: itsNext = 0; 106: } 107: 108: // Возвращается NULL, если нет следующего узла PartNode 109: PartNode * PartNode:: GetNext() const 110: { 111: return itsNext; 112: } 113: 114: Part * PartNode:: GetPart() const 115: { 116: if (itsPart) 117: return itsPart; 118: else 119: return NULL; //ошибка 120: } 121: 122: 123: // ************** Класс Part List 124: class PartsList 125: { 126: public: 127: PartsList(); 128: ~PartsList(); 129: // Необходимо, чтобы конструктор-копировщик и оператор соответствовали друг другу 130: void Iterate(void (Part:: *f)()const) const; 131: Part* Find(int & position, int PartNumber) const; 132: Part* GetFirst() const; 133: void Insert(Part *); 134: Part* operator[](int) const; 135: int GetCount() const { return itsCount; } 136: static PartsList& GetGlobalPartsList() 137: { 138: return GiobalPartsList; 139: } 140: private: 141: PartNode * pHead; 142: int itsCount; 143: static PartsList GiobalPartsList; 144: }; 145: 146: PartsList PartsList:: GlobalPartsList; 147: 148: // Implementations for Lists... 149: 150: PartsList:: PartsList(); 151: pHead(0), 152: itsCount(0) 153: { } 154: 155: PartsList:: ~PartsList() 156: { 157: delete pHead; 158: } 159: 160: Part* PartsList:: GetFirst() const 161: { 162: if (pHead) 163: return pHead-> itsPart; 164: else 165: return NULL; // ловушка ошибок 166: } 167: 168: Part * PartsList:: operator[](int offSet) const 169: { 170: PartNode* pNode = pHead; 171: 172: if (! pHead) 173: return NULL; // ловушка ошибок 174: 175: if (offSet > itsCount) 176: return NULL; // ошибка 177: 178: for (int i=0; i< offSet; i++) 179: pNode = pNode-> itsNext; 180: 181: return pNode-> itsPart; 182: } 183: 184: Part* PartsList:: Find(int & position, int PartNumber) const 185: { 186: PartNode * pNode = 0; 187: for (pNode = pHead, position = 0; 188: pNode! =NULL; 189: pNode = pNode-> itsNext, position++) 190: { 191: if (pNode-> itsPart-> GetPartNumber() == PartNumber) 192: break; 193: } 194: if (pNode == NULL) 195: return NULL; 196: else 197: return pNode-> itsPart; 198: } 199: 200: void PartsList:: Iterate(void (Part:: *func)()const) const 201: { 202: if (! pHead) 203: return; 204: PartNode* pNode = pHead; 205: do 206: (pNode-> itsPart-> *func)(); 207: while (pNode = pNode-> itsNext); 208: } 209: 210: void PartsList:: Insert(Part* pPart) 211: { 212: PartNode * pNode = new PartNode(pPart); 213: PartNode * pCurrent = pHead; 214: PartNode * pNext = 0; 215: 216: int New = pPart-> GetPartNumber(); 217: int Next = 0; 218: itsCount++; 219: 220: if (! pHead) 221: { 222: pHead = pNode; 223: return; 224: } 225: 226: // если это значение меньше головного узла, 227: // то текущий узел становится головным 228: if (pHead-> itsPart-> GetPartNumber() > New) 229: { 230: pNode-> itsNext = pHead; 231: pHead = pNode; 232: return; 233: } 234: 235: for (;; ) 236: { 237: // если нет следующего, вставляется текущий 238: if (! pCurrent-> itsNext) 239: { 240: pCurrent-> itsNext = pNode; 241: return; 242: } 243: 244: // если текущий больше предыдущего, но меньше следующего, то вставляем 245: // здесь. Иначе присваиваем значение указателя Next 246: pNext = pCurrent-> itsNext; 247: Next = pNext-> itsPart-> GetPartNumber(); 248: if (Next > New) 249: { 250: pCurrent-> itsNext = pNode; 251: pNode-> itsNext = pNext; 252: return; 253: } 254: pCurrent = pNext; 255: } 256: } 257: 258: class PartsCatalog: private PartsList 259: { 260: public: 261: void Insert(Part *); 262: int Exists(int PartNumber); 263: Part * Get(int PartNumber); 264: operator+(const PartsCatalog & ); 265: void ShowAll() { Iterate(Part:: Display); } 266: private: 267: }; 268: 269: void PartsCatalog:: Insert(Part * newPart) 270: { 271: int partNumber = newPart-> GetPartNumber(); 272: int offset; 273: 274: if (! Find(offset, partNumber)) 275: PartsList:: Insert(newPart); 276: else 277: { 278: cout < < partNumber < < " was the "; 279: switch (offset) 280: { 281: case 0: cout < < " first "; break; 282: case 1: cout < < " second "; break; 283: case 2: cout < < " third "; break; 284: default: cout < < offset+1 < < " th "; 285: } 286: cout < < " entry. Rejected! \n"; 287: } 288: } 289: 290: int PartsCatalog:: Exists(int PartNumber) 291: { 292: int offset; 293: Find(offset, PartNumber); 294: return offset; 295: } 296: 297: Part * PartsCatalog:: Get(int PartNumber) 298: { 299: int offset; 300: return (Find(offset, PartNumber)); 301: 302: } 303: 304: int main() 305: { 306: PartsCatalog pc; 307: Part * pPart = 0; 308: int PartNumber; 309: int value; 310: int choice; 311: 312: while (1) 313: { 314: cout < < " (0)Quit (1)Car (2)Plane: "; 315: cin > > choice; 316: 317: if (! choice) 318: break; 319: 320: cout < < " New PartNumber?: "; 321: cin > > PartNumber; 322: 323: if (choice == 1) 324: { 325: cout < < " Model Year?: "; 326: cin > > value; 327: pPart = new CarPart(value, PartNumber); 328: } 329: else 330: { 331: cout < < " Engine Number?: "; 332: cin > > value; 333: pPart = new AirPlanePart(value, PartNumber); 334: } 335: pc.Insert(pPart); 336: } 337: pc.ShowAll(); 338: return 0; 339: }
Результат: (0)Quit (1)Cat (2}Plane: 1 New PartNumber?: 1234 Model Year?: 94 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 4434 Model Year?: 93 (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 1234 Model Year?: 94 1234 was the first entry. Rejected! (0)Quit (1)Car (2)Plane: 1 New PartNumber?: 2345 Model Year?: 93 (0)Quit (1)Car (2)Plane: 0 Part Number: 1234 Model Year: 94 Part Number: 2345 Model Year: 93 Part Number: 4434 Model Year: 93
Анализ: В строке 82 класс PartsList объявляется другом класса PartNode. В данном случае объявление класса другом происходит в разделе public объявления класса PartNode, но так поступать вовсе не обязательно. Это объявление можно размещать в любом месте объявления класса, что не изменит его суть. В результате объявления класса как друга все закрытые методы и переменные-члены класса PartNode становятся доступными любой функции-члену класса PartsList. В строке 160 были внесены изменения в вызове функции-члена GetFirst() с учетом появившихся новых возможностей. Теперь вместо возвращения pHead-> GetPart эта функция может возвращать закрытую переменную-член pHead-> itsPart. Аналогичным образом в функции Insert() можно написать pNode-> itsNext = pHead вместо переменной-члена pHead-> SetNext(pHead). В данном случае внесенные изменения существенно не улучшили код программы, поэтому нет особых причин делать класс PartsList другом PartNode. В данном примере просто хотелось проиллюстрировать, как работает ключевое слово friend. Объявление классов-друзей следует применять с осторожностью. Класс объявляется как друг какого-либо иного класса в том случае, когда два класса тесно взаимодействуют друг с другом и открытие доступа одного класса к данным и методам другого класса существенно упрощает код программы. Однако зачастую проще организовать взаимодействие между классами с помощью открытых методов доступа.
Примечание: От начинающих программистов C++ часто можно услышать замечание, что объявление классов-друзей противоречит принципу инкапсуляции, лежащему в основе объектно-ориентированного программирования. Это, честно говоря, довольно широко распространенная бессмыслица. Объявление класса-друга просто расширяет интерфейс другого класса, что влияет на инкапсуляцию не больше, чем открытое наследование классов.
Дружественный класс Объявление одного класса другом какого-либо иного с помощью ключевого слова friend в объявлении второго класса открывает первому классу доступ к членам второго класса. Иными словами, я могу объявить вас своим другом, но вы не можете объявить себя моим другом. Пример: class PartNode{ public: friend class PartsList: // обьявление класса PartsList другом PartNode
Функции друзья
Иногда бывает необходимо предоставить права доступа не всему классу, а только одной или нескольким функциям-членам. Это реализуется посредством объявления друзьями функций-членов другого класса. Причем объявлять другом весь класс вовсе не обязательно. Фактически другом можно объявить любую функцию, независимо от того, является ли она функцией-членом другого класса или нет.
Популярное:
|
Последнее изменение этой страницы: 2017-03-08; Просмотров: 432; Нарушение авторского права страницы