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

 

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

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

Мизрохи С.В. Turbo Pascal и объектно-ориентированное программирование — М.: Финансы и статистика , 1992. — 192 c.
ISBN 5-279-00903-2
Скачать (прямая ссылка): efektispolzc2000.djvu
Предыдущая << 1 .. 76 77 78 79 80 81 < 82 > 83 84 85 86 87 88 .. 105 >> Следующая

Код выглядит довольно неуклюже, но, по крайней мере, работает. К сожалению, Избежать такой неуклюжести достаточно трудно. Даже если бы одна из наследуемых функций draw была закрытой и, следовательно, недоступной, неоднозначность "е равно осталась бы. (Для этого имеются веские причины, по их подробное разъяс-їєниє можно найти в правиле 26, поэтому здесь не буду повторяться.)
Явная квалификация членов не только выглядит неуклюже, но еще и влечет собой ряд ограничений. Когда вы явным образом вызываете виртуальную функ-ию, используя название класса, она перестает быть виртуальной. Вместо этого
ElESIHiHiHHMi Наследование и ООП
вызывается именно та функция, которая была вами указана, даже для объекта производного класса:
class SpecialLotterySimulation: public LotterySimulation { public:
virtual int draw ();
};
pis = new SpecialLotterySimulation;
pls->draw(); Il Ошибка! По-прежнему неоднозначность.
pls->Lottery: -.draw() ; Il Вызов Lottery : :draw.
pls->GraphicalObject::draw(); // Вызов GraphicalObject::draw.
В данном случае следует обратить внимание на то, что хотя pis указывает на объект SpecialLotterySimulation, нет способа (исключая приведение типов - см. правило 39) для вызова функции draw этого класса. '
Но подождите - и это еще не все! Функции draw в классах Lottery и Gra-phicalDbject объявлены виртуальными так, чтобы подклассы могли их переопределять (см. правило 36), но что если класс LotterySimulation захочет переопределить их обе? Неприятность заключается в том, что этого сделать не удастся, поскольку класс может иметь только одну функцию с названием draw, не требующую аргументов. (Из этого закона существует исключение, когда одна функция объявлена с const, а другая - без const, см. правило 21.)
Одно время эта проблема считалась достаточно серьезной для внесения изменений в стандарт языка. В ARM, руководстве но С++, рассмотрен вопрос, как добиться того, чтобы наследуемые виртуальные функции можно было «переименовывать», но затем обнаружилось, что данную проблему разрешается обойти добавлением пары дополнительных классов:
class AuxLottery: public Lottery { public:
virtual int lotteryDraw() = 0;
virtual int draw() { return lotteryDraw(); }
};
class AuxGraphicalObject: public GraphicalObject { public:
virtual int graphicalObjectDraw() = 0;
virtual int draw () { return graphicalObjectDraw(); }
};
class LotterySimulation: public AuxLottery, public AuxGraphicalObject { public:
virtual int lotteryDraw();
virtual int graphicalObjectDrawO ;
};
Каждый из двух новых классов AuxLottery и AuxGraphicalObject, по существу, объявляет новое имя для наследуемых функций draw. Они принимают форму чисто виртуальных, в данном случае функций lotteryDraw и graphi-calObjectDraw, и поэтому подклассы обязаны их переопределить. Более того,
Правило 43
183
каждый класс переопределяет наследуемую функцию draw, вызывая в ней новую виртуальную функцию. Суммарный эффект состоит в том, что внутри иерархии классов неоднозначное название draw фактически расщепляется на два функционально эквивалентных названия: lotteryDraw и graphicalObjectDraw.
LotterySimulation *pls = new LotterySimulation; Lottery *pl = pis; GraphicalObject *pgo = pis;
Il Это приводит к вызову LotterySimulation::lotteryDraw. pl->draw();
Il Это приводит к вызову LotterySimulation: : graphicalObjectDraw. pgo->draw();
Подобный образ действий, предусматривающий активное использование продуманной комбинации чисто виртуальных, обычных виртуальных и встраиваемых функций (см. правило 33), следует взять на заметку. Во-первых, это позволяет решить проблему, с которой вы можете столкнуться в любой момент; во-вторых, может служить вам напоминанием о трудностях, возникающих при множественном наследовании. Да, такая тактика действенна, но действительно ли вы хотите, чтобы вам приходилось вводить новые классы только для переопределения виртуальных функций? Наличие классов AuxLottery и AuxGraphicalObject существенно для корректного функционирования иерархии, но они не соответствуют абстракции ни в предметной области, ни в области реализации. Это просто аппарат реализации - и ничего более. Как известно, хорошее программное обеспечение аппарат-но независимо. Это правило вполне применимо и в данном случае.
Проблема неоднозначности, хотя сама по себе она и интересна, - это только малая толика тех курьезов, которые возникают при увлечении множественным наследованием. Другая проблема вырастает из эмпирического наблюдения, что иерархия наследования, начинающаяся следующим образом:
class В { ... } ; class С { ... } ;
class D: public В, public С { ... } ;
имеет неприятное свойство заканчиваться чем-нибудь вроде:
class А { ... } ;
class В: virtual public А { ... }; class С: virtual public А { ... } ; class D: public В, public С { ... };
Такие иерархии наследования не очень дружелюбны. Если вы создаете подобную иерархию, перед вами немедленно встает вопрос: следует ли делать класс виртуальным базовым классом, то есть должно ли наследование от А быть виртуальным? На практике ответ утвердительный; лишь изредка у вас может возникнуть необходимость в том, чтобы объект типа D содержал несколько копий элементов данных класса А. В силу признания этой закономерности объявленные Выше классы В и С объявляют класс А виртуальным базовым классом.
Предыдущая << 1 .. 76 77 78 79 80 81 < 82 > 83 84 85 86 87 88 .. 105 >> Следующая
Реклама
Авторские права © 2009 AdsNet. Все права защищены.
Rambler's Top100