246071-Либерти-Освой-самостоятельно-С-за-21-день (852741), страница 67
Текст из файла (страница 67)
Остальные пять методов — Sleep(), Eat(),Reproduce(),Move()иSpeak()—объявленыкакчистыевиртуальныефункции.КлассMammalпроизводитсяотAnimalвстроках29—37инесодержитникакихданных.Внем замещается функция Reproduce(), чтобы задать способ размножения, общий для всехмлекопитающих.КлассFishпроизводитсянепосредственноотклассаAnimal,поэтомуфункцияReproduce()внемзамещаетсяиначе,чемвклассеMammal(иэтосоответствуетреальности).Во всех других классах, производимых от класса Mammal, теперь нет необходимостизамещать общий для всех метод Reproduce(), хотя при желании это можно сделать дляопределенного класса, как, например, в нашей программе это было сделано в строке 83 дляклассаDog.ВсеостальныечистыевиртуальныефункциибылизамещенывклассахFish,HorseиDog,поэтомудлякаждогоизнихможносоздаватьсоответствующиеобъекты.В теле программы используется указатель класса Animal, с помощью которого делаютсяссылки на все объекты производных классов.
В зависимости от того, с каким объектом связануказательвтекущиймомент,вызываютсясоответствующиевиртуальныефункции.При попытке создать объекты для классов абстрактных типов данных Animal или Mammalкомпиляторпокажетсообщениеобошибке.КогдаследуетиспользоватьабстрактныетипыданныхВоднихпримерахпрограмм,рассмотренныхнамиранее,классAnimalявлялсяабстрактнымтипомданных,вдругих—нет.Вкакихжеслучаяхнужнообъявлятькласскакабстрактныйтипданных?Нет никаких правил, которые требовали бы объявления класса как абстрактного.Программист принимает решение о создании абстрактного типа данных, основываясь на том,какую роль играет этот класс в программе.
Так, если вы хотите смоделировать виртуальнуюферму или зоопарк, то имеет смысл класс Animal объявить как абстрактный и для созданияобъектовпроизводитьотнегодругиеклассы,такиекакDog.Если же вы хотите смоделировать виртуальную псарню, то теперь класс Dog будетабстрактным, от которого можно производить подклассы, представляющие разные породысобак. Количество уровней абстрактных классов следует выбирать в зависимости от того,насколькодетальновыхотитесмоделироватьреальныйобъектилиявление.Рекомендуется:Используйтеабстрактныетипыданныхдлясозданияобщегоинтерфейсадля всех производных классов.
Обязательно замещайте в производных классах все чистыевиртуальные функции. Объявляйте все функции, которые нужно замещать в производныхклассах,какчистыевиртуальныефункции.Нерекомендуется:Непытайтесьсоздатьобъектабстрактногокласса.ЛогикаиспользованияабстрактныхклассовВпоследнеевремявпрограммированиинаC++активноиспользуетсяконцепциясозданияабстрактныхлогическихконструкций.Спомощьютакихконструкцийможнонаходитьрешениядля многих общих задач и создавать при этом программы, которые легко читаются идокументируются.
Рассмотрим пример создания логической конструкции с использованиемнаследованияклассов.Представим, что нужно создать класс Timer, который умеет отсчитывать секунды. Такойкласс может иметь целочисленную переменную-член itsSeconds, а также метод,осуществляющийприращениепеременнойitsSeconds.Теперьпредположим,чтопрограммадолжнаотслеживатьисообщатьокаждомизменениипеременнойitsSeconds.Первоерешение,котороеприходитнаум,—этодобавитьвклассTimerметодуведомленияобизменениипеременной-члена.Нологическиэтонесовсемверно,таккакпрограмма уведомления может быть достаточно сложной и по сути своей не являетсялогическойчастьюпрограммыотсчетавремени.Гораздологичнеерассматриватьпрограммуотслеживанияиинформированияобизменениипеременной как абстрактный класс, который в равной степени может использоваться как спрограммой отсчета времени, так и с любой другой программой с периодическиизменяющимисяпеременными.Таким образом, лучшим решением будет создание абстрактного класса обозревателяObserverсчистойвиртуальнойфункциейUpdate().Теперьсоздадимвторойабстрактныйкласс—Subject.ОнсодержитмассивобъектовклассаObserver.
Кроме того, в нем объявлены два дополнительных метода: Register(), которыйрегистрирует объекты класса Observer, и Notify(), который отслеживает изменения указаннойпеременной.Эта конструкция классов может затем использоваться во многих программах. Те классы,которыебудутотслеживатьизмененияисообщатьоних,наследуютсяотклассаObserver.КлассTimer в нашем примере наследуется от класса Subject. При изменении контролируемойпеременной (в нашем примере — itsSeconds) вызывается метод Notify(), унаследованный отклассаSubject.Наконец,можносоздатьновыйклассObserverTimer,унаследованныйсразуотдвухбазовыхклассов — Observer и Timer, который будет сочетать в себе возможности отсчитывать время исообщатьобэтом.Парасловомножественномнаследовании,абстрактныхтипахданныхиязыкеJavaМногие программисты знают, что в основу языка Java положен C++.
Также известно, чтосоздателиязыкаJavaудалилиизнеговозможностьмножественногонаследованияпотому,что,поихмнению,этосредствослишкомусложняетпрограммныйкодиидетвразрезсконцепциейупрощенияпрограммныхкодов,положеннойвосновуJava.СточкизрениясоздателейJava,90%всех возможностей, предоставляемых множественным наследованием, можно получить спомощьюинтерфейса.Интерфейс в терминологии Java представляет собой нечто подобное абстрактному типуданных,втомсмысле,чтовнемтакжеопределяютсяфункции,которыемогутбытьреализованытолько в производных классах. Но новые классы не производятся непосредственно отинтерфейса.Классыпроизводятотдругихклассовивнихпередаютсяфункцииинтерфейса,чтонапоминает множественное наследование.
Так, союз абстрактных классов и множественногонаследования породил на свет аналог классов- мандатов, в результате чего удалось избежатьчрезмерного усложнения программных кодов, как в случае с множественным наследованием.Кроме того, поскольку интерфейсы не содержат ни выполняемых функций, ни переменныхчленов,отпадаетнеобходимостьввиртуальномнаследовании.Насколько удобны или целесообразны эти изменения, зависит от привычек конкретногопрограммиста.Вовсякомслучае,есливыхорошоразберетесьвмножественномнаследованиииабстрактныхтипахданныхязыкаC++,тоэтопослужитхорошейбазойприизучениииосвоениипоследнихдостиженийитенденцийпрограммирования,реализованныхвязыкеJava(еслиувасвозникнетинтерескнему).Использование логических конструкций в языках C++ и Java подробно рассматривается вследующей статье: Robert Martin, C++ and Java: А Critical Comparison // C++ Report.
— January,1997.РезюмеСегодня вы познакомились с методами преодоления некоторых ограничений одиночногонаследования. Вы узнали об опасности передачи вверх по иерархии классов интерфейсапроизводных функций и об ограничениях приведения типа данных объектов базового класса кпроизводным классам во время выполнения программы.
Кроме того, вы узнали, когда и какиспользуетсямножественноенаследованиеклассов,какиепроблемыприэтоммогутвозникнутьикакихпреодолеть.На этом занятии также было представлено объявление абстрактных типов данных испособы создания абстрактного класса с помощью чистых виртуальных функций. Особоевнимание уделялось логике использования абстрактных данных для моделирования реальныхситуаций.ВопросыиответыЧтоозначаетпередачафункциональностивверхпоиерархииклассов?Речь идет о переносе описаний общих функций-членов в базовые классы более высокогоуровня. Если одна и та же функция используется в производных классах, имеет смысл описатьэтуфункциювобщемдлянихбазовомклассе.Вовсехлислучаяхпередачафункциональностивверхцелесообразнавпрограмме?Если передаются вверх по иерархии только функции общего использования, то этоцелесообразно,носмыслтеряется,есливбазовыеклассыпередаетсяспецифичныйинтерфейспроизводных классов.
Другими словами, если метод не может быть использован во всехпроизводныхклассах,тонетсмыслаописыватьеговбазовомклассе.Впротивномслучаевамвовремявыполненияпрограммыпридетсяотслеживатьтиптекущегообъекта,преждечемвызватьфункцию.Вчемпроблемасконтролемтипаобъектапривыполнениипрограммы?Вбольшихпрограммахдлявыполненияконтролязатипомобъектапридетсяиспользоватьдостаточно массивный и сложный программный блок. Идея использования виртуальныхфункций состоит в том, что тип объекта определяется программой автоматически с помощьювиртуальной таблицы, вместо того чтобы использовать для этого специальные программныеблоки.Чтоплохоговприведениитипаобъектов?Приведение типов объектов к определенному типу данных, используемому конкретнойфункцией, довольно часто и эффективно используется в программах на C++. Но еслипрограммист применяет приведение типов для того, чтобы обойти заложенный в C++ строгийконтроль за соответствием типов данных, например в случае приведения типа указателя кустановленному во время выполнения программы типу объекта, то это говорит о серьезныхнедостаткахвструктурепрограммы,противоречащихидеологииC++.Почемубынесделатьвсефункциивиртуальными?Для поддержания работы виртуальных функций создается виртуальная таблица, чтоувеличивает потребление памяти программой и время выполнения программы.