Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

NameQuery // Shakespeare

NotQuery //! Shakespeare

OrQuery // Shakespeare || Marlowe

AndQuery // William & & Shakespeare

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

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

 

untamed || fiery

 

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

 

Alice || Emma & & Weeks

 

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

AndQuery

OrQuery

   NameQuery (" Alice" )

   NameQuery (" Emma" )

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;

};

Хранить указатели на объекты можно и с помощью типа 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_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();

}

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


Поделиться:



Последнее изменение этой страницы: 2019-04-09; Просмотров: 274; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.012 с.)
Главная | Случайная страница | Обратная связь