Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Глава22.Знакомствосвиртуальнымифункциями-членами:настоящиел




к классу дополнительный скрытый указатель — именно один указатель на класс! Класс, который не содержит виртуальных функций и не наследует никаких виртуаль- ных функций от базовых классов, не будет содержать этого указателя. Однако один указатель не такая уж большая цена безопасной работы программы.

Лучше всегда объявлять деструкторы виртуальными, даже если ваш класс не наследуется (пока не наследуется!): ведь никогда не известно, в какой момент появится некто (может, это будете вы сами), желающий воспользоваться вашим классом как базовым для своего собственного класса. Если вы не объявили деструктор виртуальным, обязательно до- кументируйте это!

 

 


Часть IV. Наследование


Глава23


Разложение классов

Разложение

Реализация абстрактных классов Рационализация бюджета:

 

 

наследования позволяет классу наследовать свойства базового класса.

Наследование помогает в достижении многих целей; например, благодаря ему я плачу обучение моего сына. Оно помогает избежать повторения кода и сократить время, затрачиваемое на написание программ. Благодаря наследованию можно повторно использовать уже                             код в новых программах, переопределяя функции.

Главное преимущество наследования — возможность указывать тип взаимосвязи между классами. Это так называемая взаимосвязь типа ЯВЛЯЕТСЯ: микроволновая печь    печью и т. д.

Разложение это прекрасный способ создания правильных связей. К примеру, связь микроволновой печи с конвекционной печью кажется естественной. Утвержде- ние же о том, что микроволновая печь является особым типом тостера, скорее всего, вас несколько насторожит. Конечно, оба эти прибора нагревают, оба используют электричество и оба находятся на кухне, но на этом сходство заканчивается — микро- волновая печь не готовит тосты.

Процедура определения классов, свойственных данной проблеме, и задания кор- ректных связей между этими классами известна под названием разложение (factoring) (это слово относится к арифметике, с которой вы мучились в средней школе; помни- те, как вы занимались разложением числа на простые множители: 12 равно 2, женное на 2 и на 3...).

 

Чтобы                как работает разложение, вернемся назад и посмотрим на классы Checking  и                использованные в программе BUDGET, которая  приводится в конце каждой части. Я мог бы до посинения рассказывать об этих классах, однако, к счастью, объектно-ориентированные программисты придумали довольно наглядный и краткий путь описания классов. Классы Checking и Savings показаны на рис. 23.1. Для того чтобы правильно понять этот рисунок, необходимо знать несколько правил.

Большой прямоугольник       это класс. Имя класса написано сверху.

|   Имена в меньших прямоугольниках — это

Имена не в прямоугольниках — это

'•-   Имена, которые выступают за пределы прямоугольника, ограничивающего класс, являются открытыми; к этим членам могут обращаться функции, не

1  являющиеся членами класса или его наследников. Члены, которые нахо- I                       полностью внутри прямоугольника, недоступны снаружи класса.

"   Толстая стрелка обозначает связь типа ЯВЛЯЕТСЯ. Тонкая стрелка обозначает связь типа СОДЕРЖИТ.

Глава 23. Разложение классов                                                                243


Checking withdrawal!)

deposit!)


 

 

withdrawal!) deposit!)


Savings


JpNext

|                count                                   count


accountNumber


next(}


accountNumber

J


 

 

23.1. Независимые классы Checking и Saving

Автомобиль  ЯВЛЯЕТСЯ   транспортным     средством   и  при  этом СОДЕРЖИТ мотор.

 

На рис. 23.1 вы можете увидеть, что  классы                          и Savings имеют много общего. Например, оба  класса  включают  функции-члены                                                  (} и deposi t (). Поскольку эти классы не идентичны, они, конечно же, должны оста- ваться раздельными (в реальном банковском приложении эти два класса отличались бы гораздо существеннее). Однако мы должны найти способ избежать дублирования.

Можно сделать так, чтобы один из этих классов наследовал другой. Класс Savings имеет больше  членов,  чем так что мы могли бы унаследовать Savings от Checking. Такой путь реализации этих классов приведен на рис. 23.2. Класс Savings наследует все члены класса Checking. Кроме того, в классе добавлен член

и переопределена  функция                                    Эта функция переопределена, поскольку правила снятия денег со сберегательного счета отличаются от правил снятия с чекового

счета (хотя меня эти правила вообще не поскольку у меня нет де-


Checking

 

deposit;)


нег, которые можно было бы снять со счета).

Хотя   наследование      Savings от Checking и сберегает наш труд, нас оно


|accountNo()


pi-ret pcoNuenxtt

accountNumber

 

 

Savings


не очень удовлетворяет. Главная проблема состоит в том, что оно искажает истинное положение вещей. При таком использовании наследования подразуме- вается,  что  счет         является специальным случаем счета

"Ну и что? скажете вы. — Такое на- следование работает и сохраняет нам си- лы и время". Это, конечно, так, но мои предупреждения это не просто сотря-


| withdrawal!)]

 

 

23.2. Класс Savings реализован


сание воздуха. Такие искажения запуты- вают программиста уже и сейчас, но еще больше будут мешать в Од- нажды программист, не знакомый с на- шими "приемчиками", будет читать нашу


как подкласс  Checking                программу, пытаясь понять, что же она

 

244                                                                                ЧастьIV.Наследовани


делает. Вводящие в заблуждение представления очень трудны для понимания и веде- ния программы.

Кроме того, такие искажения могут привести к проблемам в будущем. Например, представьте себе, что банк изменит свою политику относительно чековых счетов. Скажем, он решит взимать гонорар за обслуживание чековых счетов только в том слу- чае, если минимальный баланс упадет ниже некоторого значения в течение месяца.

Такое изменение политики банка можно легко отразить в классе checking. Все, что нужно сделать, — это добавить новый член в класс checking, чтобы следить за минимальным балансом в течение месяца. Назовем его

Однако теперь возникает проблема. Если Savings наследует Checking, значит, Savings тоже получает этот член. При этом он не используется, поскольку в сберега- тельных счетах минимальный баланс не нужен. Так что дополнительный член просто присутствует в классе. Итак, каждый объект чекового счета имеет дополнительный член Один дополнительный член — это не так уж и много, но он вносит свою лепту в общую неразбериху.

Такие изменения имеют свойство накапливаться. Сегодня это один член, а зав- тра — измененная функция-член. В результате объекты класса Savings будут содер- жать множество дополнительных данных, которые нужны исключительно в классе Checking. Если вы будете невнимательны, изменения в классе Checking могут пе- рейти к классу Savings и привести к его некорректной работе.

Как же этого избежать? Если поменять местами Checking и               проблема не исчезнет. Нужен некий третий класс (назовем его Account), который будет воплощать в себе все то общее, что есть у Checking и Savings. Такая связь приведена на рис. 23.3.

 

 

Account

 

deposit!)

 

count

balance

|

 


Checking withdrawal!) j minimumBalance


Savings withdrawal!)


23.3. Классы Checking и Savings, базирующиеся на классе Account

Каким образом создание нового класса Account решит наши проблемы? Во- первых, класс сделает более аккуратным описание реального мира (чем бы он ни являлся). В нашей концепции мира (по крайней мере, в моей) действительно есть что-то, могущее назваться счетом. Сберегательные и чековые счета являются частным случаем этой более фундаментальной концепции.

Кроме того, класс Savings отмежевывается от изменений в классе Checking (и наоборот). Если банк решит провести фундаментальные изменения во всех счетах, можно просто изменить класс Account, и все подклассы автоматически

Глава23. Разложениеклассов                                                                                        245


унаследуют эти изменения. Но если банк изменит политику только для чековых счетов, можно просто модифицировать класс Checking, не изменяя при этом класс Savings .

Такая процедура отбора общих свойств похожих классов и называется разложени- ем. Этот процесс очень важен в языках по причинам, ко- торые были приведены выше, а также потому, что разложение помогает избавиться от избыточности. Позвольте мне повториться: избыточность — это не просто плохо, это очень плохо...

Разложение будет обоснованным только в том случае, когда взаимосвязь, представляемая наследованием, соответствует реальности. Выделение общих свойств класса Mouse и Joystic k и разложение их на "множители" вполне допустимо. И мышь и джойстик являются аппаратными устройствами пози- ционирования. Но выделение общих свойств классов Mouse и Display ни- чем не обосновано.

Разложение может давать (и обычно дает) результат на нескольких уровнях абст- ракции. Например, программа, написанная для более "продвинутого" банка, может иметь структуру классов, показанную на рис. 23.4.

Из этого рисунка видно, что между классами Checking и Savings и более общим классом Account вставлен еще один класс. Он называется Conventional и объединя- ет в себе особенности обычных счетов. Другие типы счетов, например счета ценных бумаг и биржевые счета, также объявляются как отдельные классы.

Такая многослойная структура классов весьма распространена и даже желательна (пока отношения, которые она представляет, отражают реальность. Однако не

вайте, что для любого заданного набора классов не существует одной единственно правильной иерархии классов).

Представим, что банк позволяет держателям счетов удаленно обращаться к чеко- вым счетам и счетам ценных бумаг. Снимать же деньги с других типов счетов можно только в банке. Хотя структура классов, приведенная на рис. 23.4, выглядит естест- венной, в данных условиях более приемлема  другая  структура                                        23.5). Програм- мист должен решить, какая структура классов лучше всего подходит к данным усло- виям, и стремиться к наиболее ясному и естественному представлению.

 

Реализация                       классов

Такое интеллектуальное упражнение, как разложение, поднимает еще одну про- блему. Вернемся к классам банковских счетов еще раз, а именно к общему базовому классу Account. На минуту задумайтесь над тем, как вы будете определять различные функции класса Account.

Большинство функций-членов класса Account не составят проблем, поскольку оба типа счета реализуют их одинаково.  Однако  функция                                                                                                                                      () отли- чается в зависимости от типа счета. Правила снятия со сберегательного и чекового счетов различны. Мы вынуждены реализовывать Savings : :withdrawal () не так, как Checking: : withdrawal () . Но как реализовать функцию                             withdrawal () ?

Попросим банковского служащего помочь нам. Я так и представляю себе эту беседу:

"Каковы правила снятия денег со счета?" — спросите вы с надеждой.

"Какого именно счета, сберегательного или чекового?" — ответит он вопросом на вопрос.

"Со счета, — скажете вы,       просто со счета!" Пустой взгляд в ответ...

 






















Часть IV. Наследование


Account

 

 


Conventional

 

Savings        Checking SpecialChecking


Timed                                                  Market

 

CD        K501                      Stock        Mutual Funds


Puc. 23.4. Развитая структура банковских счетов

Account

 

 


Stock                   Checking SpecialChecking


Market       Savings

 

 

CD


23.5. Альтернативная иерархия классов

Проблема в том, что такой вопрос не имеет смысла. Нет такой веши, как "просто Все счета (в данном примере) должны быть чековыми или сберега- тельными. Концепция счета — это абстракция, с помощью которой мы объединя- ем общие свойства для конкретных счетов. Это незавершенная концепция, по- скольку в ней отсутствует такое важное свойство, как  функции                                                                                     () (если вы углубитесь в детали, то найдете и другие свойства, которых не хватает "просто счету").

Абстрактный класс — это тот класс, который реализуется только в подклассе. Кон- кретный — тот, который не является абстрактным.

Чтобы объяснить, что я имею в виду, позвольте позаимствовать пример из мира Наблюдая разные особи теплокровных и живородящих, вы можете заклю-

чить, что они все укладываются в концепцию под названием "млекопитающие". Вы можете выделить такие классы млекопитающих, как собачьи, кошачьи и гуманоиды. Однако невозможно найти где-либо на земле просто млекопитающее. Другими слова- ми, млекопитающие не могут содержать особь под названием "млекопитающее". Млекопитающее — это концепция высокого уровня, которую создал человек, и эк-

не существует.

Обратите внимание, что утверждать это с уверенностью я могу только по исте- чении некоторого времени. Ученые постоянно открывают новые виды животных. Проблема в том, что каждое существо обладает свойствами, которых не имеют другие; однако вполне вероятно, что в будущем кто-то найдет такое свойство у других существ.

Отражая эту ситуацию, C++ предоставляет возможность оставлять абстрактные классы незавершенными.


Поделиться:



Последнее изменение этой страницы: 2019-04-19; Просмотров: 176; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.034 с.)
Главная | Случайная страница | Обратная связь