AndQuery *aq;
string *word;
};
enum opTypes {
Not_query=1, O_query, And_query, Name_query
};
class AndQuery {
public:
// ...
private:
/*
* opTypes хранит информацию о фактических типах операндов запроса
* op_type - это сами операнды
*/
op_type _lop, _rop;
opTypes _lop_type, _rop_type;
};
Хранить указатели на объекты можно и с помощью типа void*:
class AndQuery {
public:
// ...
private:
void * _lop, _rop;
opTypes _lop_type, _rop_type;
};
Нам все равно нужен дискриминант, поскольку напрямую использовать объект, адресуемый указателем типа void*, нельзя, равно как невозможно определить тип такого объекта по указателю. (Мы не рекомендуем применять описанное решение в C++, хотя в языке C это весьма распространенный подход.)
Основной недостаток рассмотренных решений состоит в том, что ответственность за определение типа возлагается на программиста. Например, в случае решения, основанного на void*-указателях, операцию eval() для объекта AndQuery можно реализовать так:
void
AndQuery::
eval()
{
// не объектно-ориентированный подход
// ответственность за разрешение типа ложится на программиста
// определить фактический тип левого операнда
switch( _lop_type ) {
case And_query:
AndQuery *paq = static_castAndQuery*(_lop);
paq-eval();
break;
case Or_query:
OrQuery *pqq = static_castOrQuery*(_lop);
poq-eval();
break;
case Not_query:
NotQuery *pnotq = static_castNotQuery*(_lop);
pnotq-eval();
break;
case Name_query:
AndQuery *pnmq = static_castNameQuery*(_lop);
pnmq-eval();
break;
}
// то же для правого операнда
}
В результате явного управления разрешением типов увеличивается размер и сложность кода и добавление нового типа или исключение существующего при сохранении работоспособности программы затрудняется.
Объектно-ориентированное программирование предлагает альтернативное решение, в котором работа по разрешению типов перекладывается с программиста на компилятор. Например, так выглядит код операции eval() для класса AndQuery в случае применения объектно-ориентированного подхода (eval() объявлена виртуальной):
// объектно-ориентированное решение
// ответственность за разрешение типов перекладывается на компилятор
// примечание: теперь _lop и _rop - объекты типа класса
// их определения будут приведены ниже
void
AndQuery::
eval()
{
_lop-eval();
_rop-eval();
}
Если потребуется добавить или исключить какие-либо типы, эту часть программы не придется ни переписывать, ни перекомпилировать.
17.1.1. Объектно-ориентированное проектирование
Из чего складывается объектно-ориентированное проектирование четырех рассмотренных выше видов запросов? Как решаются проблемы их внутреннего представления?
С помощью
В нашем абстрактном классе Query определены данные и функции-члены, общие для всех четырех типов запроса. При порождении из Query производного класса, скажем AndQuery, мы выделяем уникальные характеристики каждого вида запроса. К примеру, NameQuery - это специальный вид Query, в котором операндом всегда является строка. Мы будем называть NameQuery производным и говорить, что Query является его базовым классом. (То же самое относится и к классам, представляющим другие типы запросов.) Производный класс наследует данные и функции-члены базового и может обращаться к ним непосредственно, как к собственным членам.