Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
183
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

fiery ( 1 ) lines match bird ( 1 ) lines match

fiery && bird ( 1 ) lines match shyly ( 1 ) lines match

fiery && bird || shyly ( 2 ) lines match

Requested query: fiery && bird || shyly

( 3 ) like a fiery bird in flight. A beautiful fiery bird, he tells her,

( 6 ) Shyly, she asks, "I mean, Daddy, is there?"

Чтобы можно было группировать части запроса, наша система должна поддерживать скобки. Например:

fiery && (bird || shyly)

выдает все вхождения fiery bird или fiery shyly1. Результат исполнения этого запроса приведен в начале данного раздела. Кроме того, система не должна многократно отображать одну и ту же строку.

17.1. Определение иерархии классов

В этой главе мы построим иерархию классов для представления запроса

NameQuery

// Shakespeare

NotQuery

// ! Shakespeare

OrQuery

// Shakespeare ||

 

Marlowe

пользователя. Сначала реализуем каждую операцию в виде отдельного класса:

AndQuery

// William && Shakespeare

В каждом классе определим функцию-член eval(), которая выполняет соответствующую операцию. К примеру, для NameQuery она возвращает вектор позиций, содержащий координаты (номера строки и колонки) начала каждого вхождения слова (см. раздел 6.8); для OrQuery строит объединение векторов позиций обоих своих операндов и т.д.

Таким образом, запрос

untamed || fiery

состоит из объекта класса OrQuery, который содержит два объекта NameQuery в качестве операндов. Для простых запросов этого достаточно, но при обработке составных запросов типа

1 Напомним, что для упрощения реализации необходимо, чтобы между любыми двумя словами, включая скобки и операторы запроса, был пробел. В реальной системе такое требование вряд ли разумно, но мы полагаем, что для вводного курсе, каковым является наша книга, это вполне приемлемо.

Alice || Emma && Weeks

возникает проблема. Данный запрос состоит из двух подзапросов: объекта OrQuery, содержащего объекты NameQuery для представления слов Alice и Emma, и объекта

AndQuery

OrQuery

NameQuery

("Alice")

NameQuery ("Emma")

AndQuery. Правым операндом AndQuery является объект NameQuery для слова Weeks.

NameQuery ("Weeks")

Но левый операнд – это объект OrQuery, предшествующий оператору &&. На его месте мог бы быть объект NotQuery или другой объект AndQuery. Как же следует представить операнд, если он может принадлежать к типу любого из четырех классов? Эта проблема имеет две стороны:

необходимо уметь объявлять тип операнда в классах OrQuery, AndQuery и NotQuery так, чтобы с его помощью можно было представить тип любого из четырех классов запросов;

какое бы решение мы ни выбрали в предыдущем случае, мы должны иметь возможность вызывать соответствующий классу каждого операнда вариант функции-члена eval().

Решение, не согласующееся с объектной ориентированностью, состоит в том, чтобы определить тип операнда как объединение и включить дискриминант, показывающий текущий тип операнда:

// не объектно-ориентированное решение union op_type {

//объединение не может содержать объекты классов с

//ассоциированными конструкторами

NotQuery *nq; OrQuery *oq; 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;

};

class AndQuery { public:

// ...

private:

void * _lop, _rop; opTypes _lop_type,

_rop_type;

Хранить указатели на объекты можно и с помощью типа void*:

};

Нам все равно нужен дискриминант, поскольку напрямую использовать объект, адресуемый указателем типа void*, нельзя, равно как невозможно определить тип такого объекта по указателю. (Мы не рекомендуем применять описанное решение в 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 в случае применения

//объектно-ориентированное решение

//ответственность за разрешение типов перекладывается на

компилятор

//примечание: теперь _lop и _rop - объекты типа класса

//их определения будут приведены ниже

void AndQuery:: eval()

{

_lop->eval(); _rop->eval();

объектно-ориентированного подхода (eval() объявлена виртуальной):

}

Если потребуется добавить или исключить какие-либо типы, эту часть программы не придется ни переписывать, ни перекомпилировать.