Оказалось, что анализ предметной области был неполон, в результате чего разработанный интерфейс не согласуется с конкретной реализацией проекта. Нельзя сказать, что анализ был неправильным, он просто неполон. Эта проблема связана с тем, что этапы анализа, проектирования и реализации отделены друг от друга и не допускают обратной связи и пересмотра. Но хотя мы не можем все продумать и все предвидеть, необходимо отличать неизбежные неправильные шаги от ошибок, обусловленных собственной невнимательностью или нехваткой времени.
В таком случае мы должны либо сами модифицировать иерархию классов Query, либо договориться, чтобы это сделали за нас. В данной ситуации мы, как авторы всей системы, сами изменим код, модифицировав конструкторы подтипов и включив виртуальную функцию-член add_op() для добавления операндов после определения оператора (мы покажем, как она применяется, чуть ниже, при рассмотрении функций evalRParen() и evalWord()).
17.7.1. Определение класса UserQuery
Объект класса UserQuery можно инициализировать указателем на вектор строк, представляющий запрос пользователя, или передать ему адрес этого вектора позже, с помощью функции-члена query(). Это позволяет использовать один объект для нескольких запросов. Фактическое построение иерархии классов Query выполняется функцией eval_query():
// определить объект, не имея запроса пользователя
UserQuery user_query;
string text;
vectorstring query_text;
// обработать запросы пользователя
do {
while( cin text )
query_text.push_back( text );
// передать запрос объекту UserQuery
user_query.query( &query_text );
// вычислить результат запроса и вернуть
// корень иерархии Query*
Query *query = user_query.eval_query();
}
while ( /* пользователь продолжает формулировать запросы */ );
Вот определение нашего класса UserQuery:
#ifndef USER_QUERY_H
#define USER_QUERY_H
#include string
#include vector
#include map
#include stack
typedef pairshort,short location;
typedef vectorlocation,allocator loc;
#include quot;Query.h"t;
class UserQuery {
public:
UserQuery( vector string,allocator *pquery = 0 )
: _query( pquery ), _eval( 0 ), _paren( 0 ) {}
Query *eval_query(); // строит иерархию
void query( vector string,allocator *pq );
void displayQuery();
static void word_map( mapstring,loc*,lessstring,allocator *pwm ) {
if ( !_word_map ) _word_map = pwm;
}
private:
enum QueryType { WORD = 1, AND, OR, NOT, RPAREN, LPAREN };
QueryType evalQueryString( const string &query );
void evalWord( const string &query );
void evalAnd();
void evalOr();
void evalNot();
void evalRParen();
bool integrity_check();
int _paren;
Query *_eval;
vectorstring *_query;
stackQuery*, vectorQuery* _query_stack;
stackQuery*, vectorQuery* _current_op;
static short _lparenOn, _rparenOn;
static mapstring,loc*,lessstring,allocator *_word_map;
};
#endif
Обратите внимание, что два объявленных нами стека содержат указатели на объекты типа Query, а не сами объекты. Хотя правильное поведение обеспечивается обеими реализациями, хранение объектов значительно менее эффективно, поскольку каждый объект (и его операнды) должен быть почленно скопирован в стек (напомним, что операнды копируются виртуальной функцией clone()) только для того, чтобы вскоре быть уничтоженным. Если мы не собираемся модифицировать объекты, помещаемые в контейнер, то хранение указателей на них намного эффективнее.