
Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdfГлава 11 • Конструкторы и деструкторы: потенциальные проблемы |
483 |
Если задается только прототип функции и функция вызывается клиентом, компоновидик даст ошибку. Он не видит код. Компилятор сообидит, что последние три строки функции mainO ошибочны. Закомментируйте объявления для операции присваивания и конструктора копирования, и компилятор безропотно примет клиентский код.
Листинг 11.8. Пример использования закрытых прототипов,
|
чтобы некорректное использование объектов было незаконным |
|
||||
#inclucle <iostream> |
|
|
|
|
||
using namespace std; |
|
|
|
|
||
class Window { |
|
// динамически распределяемый |
символьный |
массив |
||
|
char *str; |
|
||||
|
int len; |
|
// закрытый |
конструктор копирования |
|
|
Window(const Window& w); |
|
|||||
Window& operator = (const Window &w); . |
// закрытое |
присваивание |
|
|
||
public: |
|
|
|
|
|
|
WindowO |
|
// пустая строка String |
|
|
||
{ len = 0; str = new char; str[0] = 0; } |
|
|
||||
"WindowO |
|
// возвращает динамически распределяемую |
память |
|||
{ delete str; } |
|
|||||
void operator += (const char s[]) |
// параметр-массив |
|
|
|||
{ |
len = strlen(str) + strlen(s); |
// выделение достаточного объема |
|
|||
|
char* p = new char[len + 1]; |
|
||||
|
if (p==NULL) exit(1); |
// динамически распределяемой |
памяти |
|
||
|
// формирование данных из компонентов |
|
||||
|
strcpy(p,str); strcat(p,s); |
|
||||
|
delete str; str = p; } |
|
|
|
|
|
const char* showO |
const |
// указатель на содержимое |
|
|
||
{ return str; } } |
; |
|
|
|||
void display(const Window window) |
// не передавать объекты по значению |
|
||||
{ cout « window. showO; } |
|
|
|
|
||
int mainO |
|
|
|
// разумно |
|
|
{ |
Window w1; w1 += "Добро пожаловать, уважаемые покупатели!\n"; |
|
||||
|
Window w2 = w1; |
|
// неразумное использование: синтаксическая ошибка |
|||
|
w2 = w1; |
|
// еще менее разумно: синтаксическая ошибка |
|||
error |
|
// передача позначению: синтаксическая ошибка |
||||
|
display(w2); |
|
||||
|
return 0; |
|
|
|
|
|
Этот способ защитит ваши классы от постоянного использования их програм мистами клиентской части. Если клиентский код, помечаемый в функции main() листинга 11.8 как необдуманный, по какой-то причине нужно поддерживать и важна производительность программы, класс должен предусматривать конст руктор копирования и операцию присваивания или несколько операций присваи вания. Следует предусмотреть операции преобразования, если объекты класса должны инициализироваться из простых объектов данных, а не из объектов того же типа. Еще одна веская причина для добавления операций преобразования со стоит в том, что они позволяют избежать определения нескольких перегруженных операторных функций. За счет этого уменьшается число функций в классе, но потребуются дополнительные вызовы конструкторов и добавочные операции по распределению памяти.
ЧлС/иЯЬ III
бъектноориентированное программирование
сагрегированием
инаследованием
1 ^ ^ этой части книги обсуждаются методы объектно-ориентированного
ш^!Я^-лрограммирования. Рассматриваются инструментальные средства про-
^ 4^^^^ граммиста: композиция и наследование классов. Некоторые програм мисты не знают, какой метод выбрать и как избежать чрезмерного усложнения программы.
В главе 12 описывается синтаксис использования объектов как компонентов другого класса, определяются правила доступа к таким объектам и их элементам данных, поясняется, как инициализировать компоненты составного объекта. Кроме того, эта глава знакомит читателей с методами совместного использования компонентов объектов как статических компонентов и с помощью компонен тов-ссылок, описывается применение вложенных и "дружественных" классов.
Вглаве 13 представлены методы использования наследования. Рассказывается
осинтаксисе наследования C+ + , обсуждаются различные режимы наследования
иих влияние на получаемый в результате объект. Кроме того, в ней определяются права доступа к базовым компонентам производного объекта, правила области действия с учетом наследования и поясняются правила разрешения имен, когда производный метод скрывает базовый метод с тем же именем. Рассматриваются также правила создания и уничтожения производных объектов, описывается по следовательность вызова конструкторов и деструкторов.
Вглаве 14 рассматривается унифицированный язык моделирования — UML (Unified Modeling Language). Он становится все более популярным в объектноориентированном программировании и применяется для описания проектов. Эта глава поможет читателю сделать выбор между наследованием и композицией класса и подобрать подлодяш,ие инструменты для разработки классов. Кроме того, здесь рассказывается об опасностях чрезмерного применения наследования и зна чительного усложнения программ.
488 |
Часть III • Прогрол^1М1ирование с агрегированием и наслеАОвание1У1 |
•Создание конструкторов и деструкторов для правильной инициализации объектов, управления ресурсами
и для дальнейшего переноса обязанностей на серверные классы.
•Передача программисту, сопровождающему приложение,
ипрограммистам, отвечающим за клиентскую часть,
идей разработчика и его знания поведения сервера, например,
с помощью модификаторов const, применяемых к элементам данных, параметрам, возвращаемым значениям и методам.
Эти идеи лежат в основе базовой техники программирования, которая выражает ся в "самодокументируемом" объектно-ориентированном коде. Такой исходный код прост в понимании и его легко сопровождать. С помощью данных идей можно полностью реализовать потенциал C+ + , Без них программа будет состоять из сильно связанных друг с другом фрагментов с большим числом зависимостей. Такой программный код труден в понимании и модификации, причем независимо от того, на каком языке он написан — на C+ + , Java, COBOL или FORTRAN.
В этой части книги рассматривается проектирование программ, содержащих несколько взаимодействующих классов, изучается композиция классов, когда объекты одного класса используются как элементы данных, локальные перемен ные или параметры другого класса. Это мощная техника организации взаимо действия между классами программы. Архитектурные решения, которые нужно реализовать с помощью композиции классов, поддерживаются правилами вызова конструкторов C + + и передачи данных из клиента в компоненты определяемых программистом классов.
Еще одним методом кооперации между классами является наследование, позволяющее проектировать похожие классы,— один класс дополняет элементы данных и методы другого класса. Это основной способ повторного использования программного кода в C+ + . Здесь обсуждаются вопросы проектирования и при менения наследования в той или иной ситуации, рассматривается множество средств языка C+ + , которые используются для поддержки наследования: синтак сис наследования, экземпляры объектов, передача данных для инициализации наследуемых компонентов, неоднозначность имен и правила разрешения этой не однозначности.
Программисты, работающие с C+ + , любят применять наследование. Многие эксперты считают, что использование наследования является основой объектноориентированного программирования. Это не совсем так. Основа объектноориентированного программирования — использование классов как фундамента объектно-ориентированной программы (для связывания данных и операций, управления доступом к компонентам и т. д.).
Наследование не является фундаментом объектно-ориентированного програм мирования. Это техника написания программного кода и его повторного использо вания. В таком качестве она очень важна в C+ + , поэтому применять ее следует корректно.
Использование объектов классов как элементов данных
Основная цель конструктора класса в С+Н |
позволить программисту свя |
зать вместе логически соотнесенные данные и операции (см. главу 9). |
Почти все примеры классов C+ + , встречавшиеся ранее в этой книге, включа ли в себя элементы данных встроенных типов — целые и с плавающей точкой. В некоторых более сложных примерах использовались массивы символов. Факти чески это были указатели на массивы символов, распределяемые в динамической области памяти. С точки зрения композиции классов, указатель аналогичен целым
Глава 12 в Преимущества и недостатки составных классов |
489 |
значениям и числам с плавающей точкой. Он не имеет доступной извне внутренней структуры. Между тем компоненты классов могут быть более сложными, чем значения встроенных типов.
В главах 10 и 11 было показано, что в языке C + + большое внимание уделяет ся равноценной интерпретации встроенных типов и типов, определяемых про граммистом. Если данные встроенных типов можно использовать как компоненты классов, то нет никаких причин для запрещения применять в качестве элементов данных объекты некоторых других классов, также содержащие компоненты.
C + + позволяет использовать объекты классов как компоненты объектов других классов. Если один класс содержит множество элементов данных, можно объединить группу родственных данных в один объект, объявив его компонентом класса. Вместо небольшого числа крупных классов со многими компонентами получится большое число классов с меньшим количеством компонентов. Каждый программист в процессе разработки будет заниматься своим делом. Кроме того, применение большого числа более мелких классов повышает модульность про граммы, содействует сокрытию ненужных деталей от клиента. Недостатком такой чрезмерной модульности является то, что клиентам придется иметь дело с боль шим числом мелких классов, а это затрудняет их изучение.
Классы, в состав которых в качестве элементов данных входят объекты других классов, называются составными или композитными классами. Почти все классы содержат компоненты (элементы данных), следовательно, являются со ставными, однако этот термин применяется в основном к классам, компоненты которых содержат, в свою очередь, собственные компоненты. В теории объектноориентированного проектирования использование объектов одного класса в каче стве компонентов другого класса называется агрегированием или композицией класса.
Например, рассмотрим класс Rectangle, содержащий координаты х w у соот ветственно верхнего левого и нижнего правого углов прямоугольника (общепри нятое соглашение в программировании графических приложений).
class |
Rectangle { |
|
|
int |
х1, у1; |
/ / |
координаты верхней левой точки |
int |
х2, у2; |
/ / |
координаты нижней правой точки |
int |
thickness; |
/ / |
толщина границы прямоугольника |
public: |
|
|
|
Rectangle (int inX1, int inYl, int |
inX2, int |
inY2, int wiclth=1); |
|
void move(int a, int b); |
/ / |
перемещение прямоугольника |
|
void |
setThickness(int width = 1 ) ; |
/ / |
изменение толщины |
bool |
pointIn(int X, int y) const; |
/ / |
точка в прямоугольнике? |
. . . |
. } ; |
/ / |
остальная часть Rectangle |
Rectangle::Rectangle (int inX1, int inY1, int inX2, int inY2, int width)
{ x1 = inX1; y1 = inY1; |
|
|
x2 = inX2; у2 = inY2; |
|
// установка элементов данных |
thickness = width; } |
|
|
void Rectangle::move(int a, int b) |
|
|
{ x1 +=a; y1 += b; |
|
// перемещение каждого угла |
x2 +=a; у2 += b; } |
|
|
void Rectangle::setThickness(int |
width) |
// выполнение работы |
{ thickness = width; } |
|
|
bool Rectangle::pointIn(int x, int y) const |
// точка внутри? |
|
{ bool xIsBetweenBorders = (xKx |
&& x<x2 | |(x2<x && x<x1); |
bool ylsBetweenBorders = (y>y1 &&у<у2) | |(y<y1 && у>у2); return (xIsBetweenBorders &&ylsBetweenBorders); }