Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
• Функция eval(), получающая объект класса TextQuery и возвращающая объект класса QueryResult. Для поиска запрошенной строки функция eval() будет использовать переданный объект класса TextQuery.
• Функция rep(), возвращающая строковое представление базового запроса. Эту функцию использует функция eval() для создания объекта класса QueryResult, представляющего соответствия, а также оператор вывода, отображающий выражение запроса.
Абстрактный базовый классКак уже упоминалось, все четыре типа запроса не связаны друг с другом наследованием; концептуально они элементы одного уровня. Каждый класс использует тот же интерфейс, а значит, для представления этого интерфейса следует определить абстрактный базовый класс (см. раздел 15.4). Назовем этот абстрактный базовый класс Query_base, поскольку он должен служить корневым классом иерархии запроса.
Ключевая концепция. Наследование или композицияПроектирование иерархии наследования — это достаточно сложная тема, которая выходит за рамки данного вводного курса. Однако имеет смысл упомянуть об одном достаточно важном факторе проектирования, с которым должен быть знаком каждый программист.
При определении класса как открыто производного от другого производный и базовый классы реализуют взаимоотношения типа "является" (is а). В хорошо проработанных иерархиях объекты открыто унаследованных классов применимы везде, где ожидается объект базового класса.
Еще одним популярным способом взаимоотношений классов является принцип "имеет" (has а). Типы, связанные отношениями "имеет", подразумевают принадлежность.
В рассматриваемом примере с книжным магазином базовый класс представляет концепцию книги, продаваемой по предусмотренной цене, а класс Bulk_quote "является" конкретной книгой, продаваемой по розничной цене с определенной стратегией скидок. Классы приложения книжного магазина "имеют" цену и ISBN.
Класс Query_base определит функции eval() и rep() как чистые виртуальные (см. раздел 15.4). Каждый из классов, представляющих специфический вид запроса, должен переопределить эти функции. Классы WordQuery и NotQuery унаследуем непосредственно от класса Query_base. У классов AndQuery и OrQuery будет одна общая особенность, которой не будет у остальных классов в системе: у каждого будет по два операнда. Для моделирования этой особенности определим другой абстрактный базовый класс, BinaryQuery, представляющий запросы с двумя операндами. Классы AndQuery и OrQuery наследуются от класса BinaryQuery, который в свою очередь наследуется от класса Query_base. Результатом этих решений будет проект классов, представленный на рис. 15.2.
Рис. 15.2. Иерархия наследования Query_base
Сокрытие иерархии в классе интерфейсаРассматриваемая программа будет отрабатывать запросы, а не создавать их. Но чтобы запустить программу на выполнение, необходимо определить способ создания запроса. Проще всего сделать это непосредственно в коде при помощи выражения С++. Например, чтобы создать описанный ранее составной запрос, можно использовать следующий код:
Query q = Query("fiery") & Query("bird") | Query ("wind");
Это довольно сложное описание неявно предполагает, что код пользовательского уровня не будет использовать унаследованные классы непосредственно. Вместо этого будет создан класс интерфейса по имени Query (Запрос), который и скроет иерархию. Класс Query будет хранить указатель на класс Query_base. Этот указатель будет связан с объектом типа, производного от класса Query_base. Класс Query будет предоставлять те же функции, что и классы Query_base: функцию eval() для обработки соответствующего запроса и функцию rep() для создания строковой версии запроса. В нем также будет определен перегруженный оператор вывода, чтобы отображать соответствующий запрос.
Пользователи будут создавать объекты класса Query_base и работать с ними только косвенно, через функции объектов класса Query. Для класса Query, наряду с получающим строку конструктором, определим три перегруженных оператора. Каждая из этих функций будет динамически резервировать новый объект типа, производного от класса Query_base:
• Оператор & создает объект класса Query, связанный с новым объектом класса AndQuery.
• Оператор | создает объект класса Query, связанный с новым объектом класса OrQuery.
• Оператор ~ создает объект класса Query, связанный с новым объектом класса NotQuery.
• Конструктор класса Query, получающий строку и создающий новый объект класса WordQuery.
Как работают эти классыСледует понять, что работа этого приложения состоит в основном из построения объектов для представления запросов пользователя. Например, приведенное выше выражение создает коллекцию взаимодействовавших объектов, представленных на рис. 15.3.
Рис. 15.3. Объекты, созданные выражениями запросов
Как только создано дерево объектов, обработка (или отображение) данного запроса сводится к простому процессу (осуществляемому компилятором), который, следуя по линиям, опрашивает каждый объект дерева, чтобы выполнить (или отобразить) необходимые действия. Например, если происходит вызов функции eval() объекта q (т.е. корневого класса дерева), функция eval() опросит объект класса OrQuery, на который он указывает. Обработка этого объекта класса OrQuery приведет к вызову функции eval() для двух его операндов, что, в свою очередь, приведет к вызову функции eval() для объектов классов AndQuery и WordQuery, которые осуществляют поиск слова wind. Обработка объекта класса AndQuery, в свою очередь, приведет к обработке двух его объектов класса WordQuery, создав результаты для слов fiery и bird соответственно.
Новичкам в объектно-ориентированном программировании зачастую трудней всего разобраться в проекте программы. Но как только проект станет абсолютно понятен, его реализация не вызывает проблем. Чтобы проще было понять суть проекта, все используемые в этом примере классы были обобщены в табл. 15.1.
Таблица 15.1. Обзор проекта программы
Классы и операторы интерфейса программы запросов TextQuery Класс, который читает указанный файл и создает карту поиска. Этот класс предоставляет функцию поиска query(), которая получает строковый аргумент и возвращает объект класса QueryResult, представляющий строки, в которых присутствует ее аргумент (см. раздел 12.3.2) QueryResult Класс, содержащий результаты вызова функции query() (см. раздел 12.3.2) Query Класс интерфейса, указывающий на объект типа, производного от класса Query_base Query q(s) Связывает объект q класса Query с новым объектом класса WordQuery, содержащим строку s q1 & q2 Возвращает объект класса Query, связанный с новым объектом класса AndQuery, который содержит объекты q1 и q2 q1 | q2 Возвращает объект класса Query, связанный с новым объектом класса OrQuery, содержащим объекты q1 и q2 ~q Возвращает объект класса Query, связанный с новым объектом класса NotQuery, содержащим объект q Классы реализации программы запросов Query_base Абстрактный класс, базовый для классов запроса WordQuery Класс, производный от класса Query_base, который ищет указанное слово NotQuery Класс, производный от класса Query_base, представляющий набор строк, в которых указанный операнд Query отсутствует BinaryQuery Абстрактный базовый класс, производный от класса Query_base, который представляет запросы с двумя операндами типа Query OrQuery Класс, производный от класса BinaryQuery, который возвращает набор номеров строк, в которых присутствует хотя бы один из операндов AndQuery Класс, производный от класса BinaryQuery, который возвращает набор номеров строк, в которых присутствуют оба операнда Упражнения раздела 15.9.1Упражнение 15.31. При условии, что s1, s2, s3 и s4 являются строками укажите, какие объекты создаются в следующих выражениях: