Информационный сайт

 

Реклама
bulletinsite.net -> Книги на сайте -> Программисту -> Мизрохи С.В. -> "Turbo Pascal и объектно-ориентированное программирование" -> 73

Turbo Pascal и объектно-ориентированное программирование - Мизрохи С.В.

Мизрохи С.В. Turbo Pascal и объектно-ориентированное программирование — М.: Финансы и статистика , 1992. — 192 c.
ISBN 5-279-00903-2
Скачать (прямая ссылка): efektispolzc2000.djvu
Предыдущая << 1 .. 67 68 69 70 71 72 < 73 > 74 75 76 77 78 79 .. 105 >> Следующая

Правило 39. Избегайте приведения типов вниз по иерархии наследования
В наше беспокойное время нелишне быть к курсе финансовых новостей, поэтому рассмотрим класс-протокол (см. правило 34) для банковских счетов:
class Person { ... } ; class BankAccount { public :
BankAccount(const Person *primaryOwner,
const Person *jointOwner); virtual -BankAccount();
virtual void makeDeposit (double amount) = 0; virtual void makeWithdrawal (double amount) = 0; virtual double balance () const = 0;
};
Многие банки сегодня предлагают широкий выбор типов счетов, но давайте немного упростим ситуацию и предположим, что имеется только один тип банковских счетов, а именно сберегательный счет:
class SavingsAccount: public BankAccount { public :
SavingsAccount(const Person *primaryOwner, const Person *jointOwner); -SavingsAccount();
void creditlnterest () ; // Начислить проценты на счет.
г
Правило 39 і|feKMHHUE3J]
Трудно, конечно назвать его крупным сберегательным счетом, но, сами понимаете, время нынче трудное. В любом случае для наших целей этого достаточно.
Банк, вероятно, должен поддерживать список своих счетов, например посредством шаблона класса list из стандартной библиотеки (см. правило 49). Предположим, что этот список изобретательно назван allAccounts:
list<BankAccount*> allAccounts; // Все счета в банке.
Подобно всем стандартным контейнерам, контейнеры list хранят копии помещаемых в них объектов, поэтому во избежание хранения нескольких копий каждого счета BankAccount банк решил, что allAccounts будет содержать указатели, а не сами объекты BankAccount.
Теперь представьте себе, что вам необходимо написать код для итерации по всем счетам с определением процентов, начисляемых по каждому счету. Вы можете испробовать следующий вариант:
// Этот цикл не будет компилироваться (смотрите далее, если вам // никогда не встречался код с итераторами) . for (list<BankAccount*>:riterator р = allAccounts.begin (); р != allAccounts. end (); + +P) {
(*р)->creditlnterest() ; // Ошибка!
но ваш компилятор быстро приведет вас в чувство: allAccounts содержит указатели на объекты BankAccount, а не на объекты SavingsAccount, поэтому в цикле р указывает на BankAccount. Это делает вызов creditlnterest недопустимым, поскольку creditlnterest объявлена только для объектов SavingsAccount, а не BankAccount.
Если строка list<BankAccount*>:!iterator p=allAccounts.begin() скорее напоминает вам помехи на линии, чем код на С++, вы, очевидно, не имели удовольствия познакомиться с шаблонами контейнерных классов из стандартной библиотеки. Эта часть библиотеки известна как Стандартная библиотека шаблонов (STL); ее обзор вы найдете в правиле 49. На данный же момент вам лишь необходимо знать, что переменная р выступает как указатель, который проходит в цикле по всем элементам allAccounts от первого до последнего. Иными словами, р работа-ет так, как будто его тип - BankAccount * *, а список элементов хранится в массиве.
То, что вышеприведенный цикл не компилируется, крайне неприятно. Конечно, allAccounts определен как содержащий BankAccount*, но вы-то зиа-ете, что на самом деле он содержит указатели SavingsAccount*, - единст-Венный класс, который может быть инстанцирован. Глупый компилятор] И вы Решаете, что нужно сообщить ему то, что вы считаете очевидным, и о чем у него <<lte хватает мозгов» догадаться самому: allAccounts в действительности содержит SavingsAccount.
// Этот цикл откомпилируется, но он все равно неудачен, for (list<BankAccount*>:riterator р = allAccounts.begin(); p != allAccounts.end();
++P) {
Иг&ЯНМНННЕ. Наследование и ООП
static_cast<SavingsAccount*> (*р) ->creditlnterest () ,-
}
Все ваши проблемы решены четко, изящно, кратко, и все с использованием приведения типов. Вы знаете, какой тип указателя в действительности содержит al lAccounts, а ваш «глупый» компилятор - нет, и вы используете приведение типов, чтобы сообщить ему об этом. Что может быть более логичным?
Хотелось бы привести здесь библейскую аналогию. Преобразование типов для программиста на С++ - то же самое, что яблоко для Евы.
Приведение типов такого рода - указателей на базовые классы в указатели на производные классы - называется понижающим приведением (downcast), поскольку вы преобразуете типы вниз по иерархии наследования. В только что рассмотренном примере понижающее приведение типов сработало, но, как вы увидите, при дальнейшей поддержке кода это приведет к серьезным проблемам.
Однако вернемся к банку. Предположим, что воодушевленный успехом сберегательных счетов, банк решил ввести и текущие счета. При этом на текущие счета, точно так же, как и на сберегательные, начисляются проценты:
class CheckingAccount: public BankAccount { public:
void creditlnterest (); Il Начислить проценты на счет.
};
Нет смысла говорить, что allAccounts теперь будет содержать список указателей как на сберегательные, так и на текущие счета. И неожиданно в цикле начисления процентов, написанном вами, возникают серьезные проблемы.
Первая проблема связана с тем, что цикл, как и прежде, будет компилироваться, не требуя изменений, отражающих появление объектов CheckingAccount. Это произойдет, поскольку компилятор будет по-прежнему верить вам, когда вы сообщаете ему посредством static_cast, что *р в действительности указывает на SavingsAccount*. (В конце концов, вы босс.) Вот первая проблема, возникающая при поддержке такого кода. Проблема номер два заключается в том, что для решения первой у вас появится искушение написать код примерно в таком духе:
Предыдущая << 1 .. 67 68 69 70 71 72 < 73 > 74 75 76 77 78 79 .. 105 >> Следующая
Реклама
Авторские права © 2009 AdsNet. Все права защищены.
Rambler's Top100