Это относительно сложный класс, разработанный для поддержки работы множества графических классов и представления общего понятия о фигуре на экране. Однако в нем всего четыре данных-членов и пятнадцать функций. Более того, эти функции почти все тривиальны, так что мы можем сосредоточиться на вопросах проектирования. В оставшейся части главы мы пройдемся по всем членам шаг за шагом и объясним их роль в классе.
14.2.1. Абстрактный класс
Сначала рассмотрим конструктор класса
Shape
:
protected:
Shape;
который находится в разделе
protected
. Это значит, что его можно непосредственно
использовать только в классах, производных от класса
Shape
(используя обозначение
:Shape
). Иначе говоря, класс
Shape
можно использовать только в качестве базы для других классов, таких как
Line
и
Open_polyline
. Цель ключевого слова
protected:
— гарантировать, что мы не сможем создать объекты класса
Shape
непосредственно.
Рассмотрим пример.
Shape ss; // ошибка: невозможно создать объект класса Shape
Класс
Shape
может быть использован только в роли базового класса. В данном случае ничего страшного не произошло бы, если бы мы позволили создавать объекты класса
Shape
непосредственно, но, ограничив его применение, мы открыли возможность его модификации, что было бы невозможно, если бы кто-то мог его использовать непосредственно. Кроме того, запретив прямое создание объектов класса
Shape
, мы непосредственно моделируем идею о том, что абстрактной фигуры в природе не существует, а реальными являются лишь конкретные фигуры, такие как объекты класса
Circle
и
Closed_polyline
. Подумайте об этом! Как выглядит абстрактная фигура? Единственный разумный ответ на такой вопрос — встречный вопрос: какая фигура? Понятие о фигуре, воплощенное в классе
Shape
, носит абстрактный характер. Это важное и часто полезное свойство, поэтому мы не хотим компрометировать его в нашей программе. Позволить пользователям непосредственно создавать объекты класса Shape противоречило бы нашим представлениям о классах как о прямых воплощениях понятий. Конструктор определяется следующим образом:
Shape::Shape
:lcolor(fl_color), // цвет линий и символов по умолчанию
ls(0), // стиль по умолчанию
fcolor(Color::invisible) // без заполнения
{
}
Это конструктор по умолчанию, поэтому все его члены также задаются по умолчанию. Здесь снова в качестве основы использована библиотека FLTK. Однако понятия цвета и стиля, принятые в библиотеке FLTK, прямо не упоминаются. Они являются частью реализации классов
Shape
,
Color
и
Line_style
.
Объект класса
vector<Points>
по умолчанию считается пустым вектором.
Класс является абстрактным (abstract), если его можно использовать только в качестве базового класса. Для того чтобы класс стал абстрактным, в нем часто объявляют чисто виртуальную функцию (pure virtual function), которую мы рассмотрим в разделе 14.3.5. Класс, который можно использовать для создания объектов, т.е. не абстрактный класс, называется конкретным (concrete). Обратите внимание на то, что слова абстрактный и конкретный часто используются и в быту. Представим себе, что мы идем в магазин покупать фотоаппарат. Однако мы не можем просто попросить какой-то фотоаппарат и принести его домой. Какую торговую марку вы предпочитаете? Какую модель фотоаппарата хотите купить? Слово фотоаппарат — это обобщение; оно ссылается на абстрактное понятие. Название “Olympus E-3” означает конкретную разновидность фотоаппарата, конкретный экземпляр которого с уникальным серийным номером мы можем купить (в обмен на большую сумму денег). Итак, фотоаппарат — это абстрактный (базовый) класс, “Olimpus E-3” — конкретный (производный) класс, а реальный фотоаппарат в моей руке (если я его купил) — это объект.
Объявление
virtual ~Shape { }
определяет виртуальный деструктор. Мы не будем пока его использовать и рассмотрим позднее, в разделе 17.5.2.
14.2.2.
Управление доступом
Класс
Shape
объявляет все данные-члены закрытыми.
private:
vector<Point> points;
Color lcolor;
Line_style ls;
Color fcolor;
Поскольку данные-члены класса
Shape
объявлены закрытыми, нам нужно предусмотреть функции доступа. Существует несколько стилей решения этой задачи. Мы выбрали простой, удобный и понятный. Если у нас есть член, представляющий свойство
X
, то мы предусмотрели пару функций,
X
и
set_X
, для чтения и записи соответственно. Рассмотрим пример.
void Shape::set_color(Color col)
{
lcolor = col;
}
Color Shape::color const
{
return lcolor;
}
Основной недостаток этого стиля заключается в том, что мы не можем назвать переменную так же, как функцию для ее чтения. Как всегда, мы предпочли выбрать наиболее удобные имена для функций, поскольку они являются частью открытого интерфейса. Как назвать закрытые переменные, менее важно. Обратите внимание на то, что мы использовали ключевое слово
const
, чтобы подчеркнуть, что функция чтения не может модифицировать члены своего класса
Shape
(см. раздел 9.7.4).
В классе
Shape
хранится вектор объектов класса
Point
с именем
points
, которые предназначены для его производных классов. Для добавления объектов класса
Point
в вектор
points
предусмотрена функция
add
.
void Shape::add(Point p) // защищенный
{
points.push_back(p);
}
Естественно, сначала вектор
points
пуст. Мы решили снабдить класс
Shape
полным функциональным интерфейсом, а не предоставлять функциям-членам классов, производных от класса
Shape
, прямого доступа к его данным-членам. Одним людям создание функционального интерфейса кажется глупым, поскольку они считают, что недопустимо делать какие-либо данные-члены класса открытыми. Другим наш подход кажется слишком узким, потому что мы не разрешаем членам производных классов прямой доступ к членам базового класса.
Классы, производные от класса
Shape
, например
Circle
и
Polygon
, “понимают”, что означают их точки. Базовый класс
Shape
этого “не понимает”, он просто хранит точки. Следовательно, производные классы должны иметь контроль над тем, как добавляются точки. Рассмотрим пример.
• Классы
Circle
и
Rectangle
не позволяют пользователю добавлять точки, они просто “не видят” в этом смысла. Что такое прямоугольник с дополнительной точкой? (См. раздел 12.7.6.)
• Класс
Lines
позволяет добавлять любые пары точек (но не отдельные точки; см. раздел 13.3).
• Классы
Open_polyline
и
Marks
позволяют добавлять любое количество точек.
• Класс
Polygon
позволяет добавлять точки только с помощью функции
add
, проверяющей пересечения (раздел 13.8).
Мы поместили функцию
add
в раздел
protected
(т.е. сделали ее доступной только для производных классов), чтобы гарантировать, что производные классы смогут управлять добавлением точек. Если бы функция
add
находилась в разделе
public
(т.е. каждый класс мог добавлять точки) или
private
(только класс
Shape
мог добавлять точки), то такое точное соответствие функциональных возможностей нашему представлению о фигуре стало бы невозможным.