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

 

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

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

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

for (list<BankAccount*>::iterator р = allAccounts.begin (); р != allAccounts. end (); ++p) {
if (*p указывает на SavingsAccount)
static_cast<SavingsAccount*>(*p)->creditlnterest(); else
static_cast<CheckingAccount*>(*p)->creditlnterest();
}
Создавая код по принципу «если это объект типа Tl, - сделать что-либо; если же это тип Т2, - сделать что-либо другое», знайте, что вы в корне не правы. Такое решение противоречит духу С++. Да, подобная стратегия является разумной ДЛЯ С и Pascal, но не для С++. В С++ для той же цели служат виртуальные функции.
Помните, что при работе с виртуальными функциями компилятор несет ответственность за то, чтобы в зависимости от типа объекта была вызвана нужная
Правило 39 IS
! 165
функция. Не засоряйте кол условными операторами и операторами выбора; позвольте компилятору выполнять такую работу за вас. Вот каким образом это делается:
class BankAccount {...}; // Как выше.
// Новый класс, представляющий счета с начисляемыми процентами, class InterestBearingAccount: public BankAccount { public :
virtual void creditlnterest () = 0;
};
class SavingsAccount: public InterestBearingAccount {
- - - Il Как выше.
};
class CheckingAccount: public InterestBearingAccount {
... Il Как выше.
};
Графически это выглядит так:
SavingsAccount
Поскольку как на сберегательные, так и на текущие счета начисляются проценты, желание поместить эти общие функции в общем базовом классе вполне закономерно. Однако если допустить, что не на все счета банка будут начисляться проценты (исходя из моего опыта, это весьма разумное предположение), вы не можете поместить эти счета в класс BankAccount. В результате вы вводите новый подкласс класса BankAccount, называемый InterestBearingAccount, и делаете так, чтобы SavingsAccount и CheckingAccount наследовали от него.
То обстоятельство, что проценты начисляются и на сберегательные, и на текущие счета, выражается в следующем: функция creditlnterest класса InterestBearingAccount объявлена как чисто виртуальная, что предполага-ет ее определение в подклассах SavingsAccount и CheckingAccount.
Эта новая "иерархия классов позволяет переписать ваш цикл таким образом:
// Уже лучше, хотя еще не идеально.
for (list<BankAccount*>:: iterator р = allAccounts.begin (); р I= al!Accounts.end() ;
Наследование и ООП
++P) {
static_cast<InterestBearingAccount*>(*р)->creditlnterest()•
}
Хотя этот цикл все еще содержит не слишком приятное приведение типов, он намного лучше защищен от ошибок, поскольку будет продолжать правильно работать, даже если к вашему приложению будут добавлены новые подклассы классаInterestBearingAccount.
Для того чтобы полностью избавиться от приведения типов, вы должны внести в проект дополнительные изменения. Один из подходов заключается в том, чтобы сделать спецификацию списка счетов более строгой. Если бы вы могли использовать список объектов InterestBearingAccount, а не объектов BankAc-count, все вышло бы просто замечательно:
// Все счета в банке, на которые начисляются проценты. list<InterestBearingAccount*> allIBAccounts;
// Цикл компилируется и работает как сейчас, так и в дальнейшем, for (list<InterestBearingAccount*>:: iterator р =
allIBAccounts.begin ();
р != allIBAccounts.end();
++P) {
(*p)->creditlnterest();
}
Если замена списка на более специализированный невозможна, тогда, вероят но, имеет смысл сказать, что операция creditlnterest применима ко всем бан ковским счетам, но для беспроцентных счетов она - просто пустая функция. Эт можно выразить следующим образом:
class BankAccount { public :
virtual void creditlnterest() {}
};
class SavingsAccount: public BankAccount { ... }; class CheckingAccount: public BankAccount { ... }; list<BankAccount*> allAccounts; Il Смотрите-ка, нет приведения типа!
for (list<BankAccount*>:riterator p = allAccounts.begin(); p != allAccounts.end(); + +P) {
(*p)->creditlnterest();
}
Заметим, что виртуальная функция BankAccount: :creditlnterest имеет пустую реализацию по умолчанию. Это удачный способ указать, что поведение данной функции по умолчанию заключается в том, чтобы ничего не делать; однако такая реализация может вызвать, в свою очередь, непредвиденные осложнения. Подробное рассмотрение проблемы, а также обсуждение способов ее решения приводятся в правиле 36. Заметим также, что creditlnterest неявным образом представляет собой встраиваемую функцию. В этом нет ничего плохого,
Правило 39
^ Hk НН^Ш і
но, поскольку она виртуальна, ее встраивание, вероятно, не будет осуществляться компилятором. Почему так происходит, объясняется в правиле 33.
Как вы видели, понижающего приведения типов можно избежать несколькими способами. Наилучший из них - заменить такие преобразования типов вызовами виртуальных функций, при этом по возможности делая каждую виртуальную функцию пустой в тех классах, к которым она в действительности неприменима, второй метод - усилить строгость типизации так, чтобы между объявляемым типом указателя и типом указателя, который, как вы знаете, реально находится в программе, не возникало неоднозначности. Каких бы усилий ни стоило избавление от понижающих приведений типов, эти усилия будут потрачены не зря, поскольку понижающее приведение типов выглядит безобразно, часто является источником ошибок и дает код, трудный для понимания, разработки и поддержки.
Предыдущая << 1 .. 68 69 70 71 72 73 < 74 > 75 76 77 78 79 80 .. 105 >> Следующая
Реклама
Авторские права © 2009 AdsNet. Все права защищены.
Rambler's Top100