|
Программирование >> Инициализация объектов класса, структура
class AndQuery { public: public: ... private: void * lop, rop; opTypes lop type, rop type; Нам все равно нужен дискриминант, поскольку напрямую использовать объект, адресуемый указателем типа void*, нельзя, равно как невозможно определить тип такого объекта но указателю. (Mi не рекомендуем применять описанное решение в C++, хотя в языке C это весьма распространенный подход.) Основной недостаток рассмотренных решений состоит в том, что ответственность за определение типа возлагается на программиста. Например, в случае решения, основанного на void*-указателях, операцию eval() для объекта AndQuery можно void AndQuery:: eval() не объектно-ориентированна подход ответственность за разрешение типа ложится на программиста опредеть фактический тип левого операнда switch( lop type ) { case And query: AndQuery *paq = static cast<AndQuery*>( lop); paq->eval(); break; case Or query: OrQuery *pqq = static cast<OrQuery*>( lop); poq->eval(); break; case Not query: NotQuery *pnotq = static cast<NotQuery*>( lop); pnotq->eval(); break; case Name query: AndQuery *pnmq = static cast<NameQuery*>( lop); pnmq->eval(); break; то же для правого операнда реализовать так: В результате явного управления разрешением типов увеличивается размер и сложность кода и добавление нового типа или исключение существующего при сохранении работоспособности программы затрудняется. Объектно-ориентированное программирование предлагает альтернативное решение, в котором работа по разрешению типов перекладывается с программиста на компилятор. Например, так выглядит код операции eval() для класса AndQuery в случае применения объектно-ориентированного подхода (eval() объявлена виртуальной): объектно-ориентированное решение ответственность за разрешение типов перекладывается на компилятор примечание: теперь lop и rop - объекты их определения будут приведены ниже void AndQuery:: eval() lop->eval(); rop->eval(); Если потребуется добавить или исключить какие-либо типы, эту часть программы не придется ни переписывать, ни перекомпилировать. 17.1.1. Объектно-ориентированное проектирование Из чего складывается объектно-ориентированное проектирование четырех рассмотренных выше видов запросов? Как решаются проблемы их внутреннего представления? С помощью наследования можно определить взаимосвязи между независимыми классами запросов. Для этого мы вводим в рассмотрение абстрактный класс Query, который будет служить для них базовым (соответственно сами эти классы будут считаться производными). Абстрактный класс можно представить себе как неполн1й, который становится более или менее завершенным, когда из него порождаются производные классы, - в нашем случае AndQuery, OrQuery, NotQuery и NameQuery. В нашем абстрактном классе Query определены данные и функции-члены, общие для всех четырех типов запроса. При порождении из Query производного класса, скажем AndQuery, мы выделяем уникальные характеристики каждого вида запроса. К примеру, NameQuery - это специальный вид Query, в котором операндом всегда является строка. Мы будем называть NameQuery производным и говорить, что Query является его базовым классом. (То же самое относится и к классам, представляющим другие типы запросов.) Производный класс наследует данные и функции-члены базового и может обращаться к ним непосредственно, как к собственным членам. Основное преимущество иерархии наследования в том, что мы программируем открытый интерфейс абстрактного базового класса, а не отдельных производных от него специализированных типов, что позволяет защитить наш код от последующих изменений иерархии. Например, мы определяем eval() как открытую виртуальную функцию абстрактного базового класса Query. Пользовательский код, записанный в виде: rop->eval(); экранирован от любых изменений в языке запросов. Это не только позволяет добавлять, модифицировать и удалять типы, не изменяя программы пользователя, но и освобождает автора нового вида запроса от необходимости заново реализовывать поведение или действия, общие для всех типов в иерархии. Такая гибкость достигается за счет двух характеристик механизма наследования: полиморфизма и динамического связывания. pery может адресовать бой из классов, производн от Query void eval( const Query *pquery ) { pquery->eval(); него. Если определить обычную функцию eval() следующим образом: int main() { AndQuery aq; NotQuery notq; OrQuery *oq = new OrQuery; Nameery nq( Botticelli ); правильно: бой производн от Query класс компилятор автоматически преобразует в базов класс eval( &aq ); eval( ¬q ); eval( oq ); eval( &nq ); то мы вправе вызывать ее, передавая адрес объекта любого из четырех типов запросов: В то же время попытка передать eval() адрес объекта класса, не являющегося int main() { string name( Scooby-Doo ); ошибка: тип string не является производн от Query eval( &name ); производным от Query, вызовет ошибку компиляции: Внутри eval() выполнение инструкции вида pquery->eval(); должно вызывать нужную виртуальную функцию-член eval() в зависимости от фактического класса объекта, адресуемого указателем pquery. В примере выше pquery последовательно адресует объекты AndQuery, NotQuery, OrQuery и NameQuery. В каждой точке вызова определяется фактический тип класса объекта и вызывается подходящий экземпляр eval() . Механизм, с помощью которого это достигается, называется динамическим связыванием. (Мы вернемся к проектированию и использованию виртуальных функций в разделе 17.5.) Когда мы говорим о полиморфизме в языке C++, то имеем в виду главным образом способность указателя или ссылки на базовый класс адресовать любой из производных от
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки. |