Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:
Рассмотрим пример.
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
мог добавлять точки), то такое точное соответствие функциональных возможностей нашему представлению о фигуре стало бы невозможным. По аналогичным причинам мы поместили функцию
set_point
в класс protected
. В общем, только производный класс может “знать”, что означают точки и можно ли их изменять, не нарушая инвариант. Например, если класс
Regular_hexagon
объявлен как множество, состоящее из шести точек, то изменение даже одной точки может породить фигуру, не являющуюся правильным шестиугольником. С другой стороны, если мы изменим одну из точек прямоугольника, то в результате все равно получим прямоугольник. Фактически функция set_point
в этом случае оказывается ненужной, поэтому мы включили ее просто для того, чтобы обеспечить выполнение правил чтения и записи каждого атрибута класса Shape
. Например, если бы мы захотели создать класс Mutable_rectangle
, то могли бы вывести его из класса Rectangle
и снабдить операциями, изменяющими точки.
Поделиться:
Популярные книги
Боги, пиво и дурак. Том 3
3. Боги, пиво и дурак
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Мымра!
1. Мымрики
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Белые погоны
3. Гибрид
Фантастика:
фэнтези
попаданцы
технофэнтези
аниме
5.00
рейтинг книги
Любовь Носорога
Любовные романы:
современные любовные романы
9.11
рейтинг книги
Я все еще граф. Книга IX
9. Дорогой барон!
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Делегат
6. Сопряжение
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Неудержимый. Книга XVI
16. Неудержимый
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Чужая дочь
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Ночь со зверем
3. Оборотни-медведи
Любовные романы:
любовно-фантастические романы
5.25
рейтинг книги
Месть бывшему. Замуж за босса
3. Власть. Страсть. Любовь
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Совок – 3
3. Совок
Фантастика:
фэнтези
детективная фантастика
попаданцы
7.92
рейтинг книги
Титан империи 7
7. Титан Империи
Фантастика:
боевая фантастика
попаданцы
аниме
5.00
рейтинг книги
Измена. Он все еще любит!
Любовные романы:
современные любовные романы
6.00
рейтинг книги
Вечный Данж. Трилогия
Фантастика:
фэнтези
юмористическая фантастика
6.77