Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
278
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

I 530

Часть ill • Программирование с огрегировонием и наследованием

Определения вложенных классов могут включаться в зарытые или общедо­ ступные части клиентского класса. В любом случае вложенный класс скрыт от остальной программы. Имя класса известно только в области действия (фигурных скобок) композитного класса, где определен вложенный класс.

Вложенные классы не могут использоваться для объявления переменных, имеющих тип этих классов, вне области действия составного класса. Определения класса скрываются таким же образом, как и элементы данных. Следовательно, если имя нужно использовать вне того класса, где оно определено, применяйте операцию области действия.

int mainO

 

5, 7, 11

13, 17, 19, 23, 29

} ;

/ / исходные данные

{ double а[] = {3,

History h;

< 9 i++)

/ /

конструктор

по умолчанию

for (int i=0; i

/ /

доступно

8 слотов

 

h.add (a[i]);

 

/ /

установка

предыстории

h.printO;

 

/ /

вывод предыстории

 

h.averageO;

 

/ /

вычисление

среднего

Sample s = 5;

s = 5;

/ /

не допускается для вложенного класса

History::Sample

/ /

операция области действия решает проблему

return 0; }

 

 

 

 

 

 

Чтобы последний оператор был допустимым, класс Sample нужно определять в разделе public клиентского класса History. Тогда клиент класса History сможет использовать имя Sample, уточненное именем агрегатного класса.

C-f-+ позволяет комбинировать в одном операторе определения класса и его экземпляры. Между тем это нельзя считать хорошей практикой программирова­ ния, поскольку такие экземпляры часто являются глобальными.

class Sample {

/ /

глобальный в файле

double value;

 

 

public:

 

 

 

Sample (double x = 0)

 

{

value = x;

}

 

void

set (double x)

 

{

value = x;

}

 

double show ()

const { return value;

}

}

s1,s2;

/ /

глобальные объекты класса Sample

В то же время это вполне подходит для вложенных классов, так как компонент­ ные данные обычно глобальны в области действия класса.

class

History {

 

 

 

 

 

 

class Sample {

 

 

/ /

 

невидим вне области действия

клиента

double

value;

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

Sample (double x = 0)

 

 

 

 

 

{

value = x;

}

 

 

 

 

 

void

set (double x)

 

 

 

 

 

{

value = x;

}

 

 

 

 

 

double

show ()

const

 

 

 

 

 

{

return value;

}

 

 

 

 

}

*data;

 

 

/ /

конец определения вложенного

класса

int

size,

count,

idx;

 

 

 

 

 

public:

 

 

 

 

 

 

 

 

HistoryO

: size(3),

count(O),

idx(O)

/ / сделать массив пустым

{ data

= new Sample[size];

/ /

выделение новой памяти

 

Глава 12 • Преимущества и недостатки составных классов

531

i f (data

== NULL)

 

{

cout

« " Нет,памяти\п"; exit(1); } }

 

. . . }

;

/ / остальная часть класса

History

Не следует злоупотреблять данным средством, ибо в результате может получить­ ся запутанная программа.

Если в других классах нужно использовать эти классы компонентов как их серверные классы, то не следует определять класс компонента как вложенный. Когда другие классы не нуждаются в классах компонентов, вложенные классы не дают никаких преимуществ, однако их применение позволяет избежать конф­ ликтов имен, если в нескольких частях программы для совершенно разных целей будет использоваться одно и то же имя класса. Тогда имя вложенного класса не будет "загрязнять" пространство имен программы.

Например, в других классах для определения иных результатов измерений других типов с другого датчика и даже числа значений может использоваться имя Sample. Для разрешения такого конфликта надо ввести два имени класса, напри­ мер Samplel и Sample2. Однако удобнее использовать одно имя и сделать класс вложенным.

Такие имена классов, как Node, очень популярны для компонентов связанного списка. Если некоторый класс Node полезен для другой связанной структуры (например, стека или двоичного дерева), то его следует объявлять в глобальном пространстве имен. Однако нередко разные связанные структуры содержат разно­ образные элементы информации, и один класс Node не может их обслуживать.

В данной ситуации надо определить класс Node как локальный в каждом из контейнерных классов. При этом устраняются потенциальные конфликты имен. Устранение глобальных имен ведет к тому, что программистам, разрабатываюш,им разные контейнерные классы, не нужно координировать свои действия.

struct Node { char* value; Node* next; } ;

/ /

хороший кандидат на вложенный класс

/ /

указатель на содержимое информации (например, слово)

/ /

указатель на следующий узел

"Дружественные" классы

Разработчик класса должен предоставить доступ к своему классу, чтобы удов­ летворить потребности клиентов, не открывая класс и не создавая ненужные за­ висимости.

Размещение элементов данных в секции public класса используется довольно редко. При этом предоставляется чрезмерно широкий доступ без явного указания того, кто его использует. Если возникает проблема при отладке, не ясно, какие именно клиенты должны подлежать проверке. Когда меняется представление данных, также не вполне очевидно, на каких клиентов это повлияет. Использова­ ние закрытых данных и функций возможно только через общедоступные функциичлены.

Доступ к отличным от public компонентам класса через функции-члены может усложнить исходный код клиента. Как было показано в главе 11, С4-+ предлагает способ для расширения доступа к частным разделам класса. "Дружественная" функция класса имеет те же полномочия доступа, что и компонентная функция класса.

Это нарушает инкапсуляцию и должно использоваться по возможности реже. С другой стороны, список "дружественных" функций и классов — часть явного определения класса. Он доступен для проверки и при необходимости может ис­ пользоваться для идентификации клиентов, на которых влияют изменения. Так как "дружественная" функция обращается к закрытым объектам класса, практически бесполезно применять ее вне контекста класса.

532

Часть III • Программирование с агрегированием т насдедоеанием

"Дружественными" могут быть не только "автономные" глобальные функции, но и компоненты другого класса. В этом случае функция-член одного класса будет "дружественной" функцией другого.

Можно определить все функции одного класса как функции friend другого класса. Тогда функции-члены этого класса смогут обращаться к закрытым компо­ нентам другого класса без помощи функций доступа.

Чаще "дружественными" функциями серверного класса объявляются только некоторые функции-члены клиента.

Когда класс используется как сервер только одним клиентским классом, архи­ тектуру клиента и сервера можно упростить, сделав клиентский класс "друже­ ственным" для серверного класса. Тогда каждая функция-член класса-клиента (например, History) сможет обращаться к отличным от public компонентам сер­ верного класса (например, Sample). Данный синтаксис предусматривает примене­ ние ключевого слова friend, предшествующего имени класса (в любой части серверного класса).

class Sample {

 

/ / описание friend

 

 

friend History;

double

value;

 

 

public:

 

 

 

 

Sample (double x = 0)

 

 

{

value = x;

}

 

void

set (double

x)

 

 

{

value = x;

}

 

double

show () const

 

 

{

return value; }

 

} ;

Если класс History еще не определен — это синтаксическая ошибка. Между тем, класс History не определяется перед классом Sample, так как в нем используется имя Sample.

class

History

{

 

int

size,

count,

idx;

Sample

*data;

 

public:

 

 

 

 

HistoryO

: size(3),

count(O),

{ data

= new Sample[size];

i f

(data

== NULL)

 

 

{

cout

« " Her памяти\п"

. .

. }

;

 

 

 

// циклическая зависимость

idx(O)

// пустой массив

памяти

 

// выделение новой

e x i t d ) ;

}

}

 

 

/ /

остальная часть

класса History

Это типичный пример циклической зависимости в программе. Класс History использует имя Sample, следовательно, определение Sample перед определением класса History. С другой стороны, класс Sample применяет имя History, и, следо­ вательно, необходимо определять History перед определением класса Sample.

Существуют два разных способа, позволяющих сообщить компилятору, что означает History в определении Sample. Один из них — применить упреждающее описание, при котором указывается, что имя является именем класса. Здесь имя History определяется как имя класса.

class

History

{

/ /

класс объявлен в другом месте

class

Sample

{

 

 

 

friend

History;

/ /

объявление friend

double value;

Глава 12 • Преимущества и недостатки составных классов

533

 

public-

 

 

 

 

Sample (double X = 0)

 

 

 

{ value = х; }

 

 

 

void set (double x)

 

 

 

{ value = x; }

 

 

 

double show () const

 

 

 

{ return value; }

 

 

 

};

 

 

 

Другой способ решить эту проблему —

указать непосредственно в объявлении

friend, что History является классом.

 

 

class Sample {

// объявление friend

 

 

friend class History;

 

 

double value;

 

 

public:

 

 

 

 

Sample (double x = 0)

 

 

 

{ value = x; }

 

 

 

void set (double x)

 

 

 

{ value = x; }

 

 

 

double show () const

 

 

 

{ return value; }

 

 

 

};

 

 

 

 

Конечно, хорошо было бы иметь только один способ, а енде лучше, если бы

компоновш,ик мог сам разрешать эти перекрестные ссылки.

 

 

Теперьфункции-члены History могут непосредственно обраидаться к отличным

от public данным.

 

 

void History::print () const

// вывод только допустимых элементов

{ for (int i = 0; i < count; i++)

//

cout « " " « data[i].show();

// не нужно использовать методы

 

 

cout «

" " « data[i].value;

 

 

cout «

endl; } .

 

 

Более того,классу Sample теперь не требуются функции-члены для предоставле­

ния доступа, его функция friend в них не нуждается.

class Sample {

// объявление friend

friend class History;

double value;

 

public:

 

Sample (double x = 0)

// другие функции ненужны

{ value = x; }

};

 

В технике разработки П О и методологии программирования с подозрением

относятся к компонентам friend, поскольку они намеренно нарушают принцип

сокрытия информации. Кроме того, они усложняют архитектуру программы

и затрудняют обучение программированию.

Вместо применения элементов friend класс Sample можно сделать вложенным в класс History и объявить общедоступные поля. Поскольку никакой другой

класс,кроме History, к этим полям не обраш,ается, принцип сокрытия информа­

ции соблюдается.

class History {

idx;

intsize, count,

struct Sample {

// невидим вне клиента

534

Часть III • Програттироваи1^е с агрегирование!^ и наследованием

scope

double value; Sample (double x = 0)

{ value = x; } } *data; public:

HistoryO : size(3), count(O), idx(O) { data = new Sample[size];

if (data == NULL)

//элемент данных public

//динамические данные History

//сделать массив пустым

//выделить новую память

{ cout « " Нет памяти\п" e x i t d ) : }

}

. . . } :

/ /

остальная часть класса History

 

 

Реализация функций-членов History при этом будет такой же, как в предыду^ щей версии. Они имеют полный доступ к отличным OTpublic компонентам Sample.

Используйте элементы friend осторожно и всегда рассматривайте другие альтернативы.

Итоги

в данной главе рассматривалось использование классов C++ как компонентов композиции классов. Классы следует определять как соотносящиеся друг с другом взаимодействующие компоненты, а не считать их автономными сегментами про­ граммного кода.

Агрегирование — одна из наиболее популярных форм связи между классами. Использование объектов классов как элементов данных ставит ряд технических и концептуальных вопросов: как определить компоненты класса, как их инициа­ лизировать соответствуюш^1м состоянием для использования в клиенте (компо­ нентном классе).

Рассматривались также другие способы связи объектов — применение указа­ телей и ссылок. Это еще более сложная техника связей между объектами. По­ дробное обсуждение данных методов программирования слишком далеко увело бы вас от изучения синтаксиса C++. Однако, как бы не сложилась ваша карьера профессионального программиста, вам придется включать одни объекты в другие и связывать их с помощью указателей и ссылок.

Кроме того, отмечался специальный случай агрегирования классов — контей­ нерные классы, содержащие набор компонентов-объектов. Это также очень эф­ фективное соотношение между классами, которое можно реализовать несколькими способами. Данные способы предусматривают создание контейнерных классов или использование библиотечных контейнеров (либо сочетание того и другого).

Надеемся, вам будет приятно и интересно с ними работать.

^ ^ / ^ IS

п.юдобные классы и их интерпретация

Темы данной главы

^Интерпретация подобных классов

^Синтаксис наследования в С++

•^ Доступ к сервису базовых и производных классов

^Доступ к базовым компонентам объекта производного класса

^Правила области действия и разрешения имен при наследовании

•^ Конструкторы и деструкторы производных классов

^Итоги

^

1^]^

данной главе, как и в предыдущей, читатели более подробно познако-

^'•к.мятся с синтаксисом С+Н

ключевыми словами, списками инициа-

^^^^'Z^ лизации и др.

Впервой части книги рассказывалось о вычислительных аспектах C+ + . Она была посвящена таким традиционным темам программирования, как типы данных, идентификаторы, ключевые слова, выражения, операторы, условия, циклы и дру­ гие управляющие структуры. Применение этих инструментов позволяет достичь стоящих при написании программы целей и получить результаты, отвечающие требованиям вычислений.

Кроме того, говорилось о методах агрегирования — включения компонентов данных в массивы, структуры и другие определяемые программистом типы, объе­ динении операторов и управляющих конструкций в функции. В C++ это сложнее, чем в других языках, особенно когда дело касается областей действия, передачи параметров, возврата значений, указателей и ссылок. Читатели узнали также

овозможностях и опасностях динамического управления памятью в C++. Эти ин­ струментальные средства предполагают разбиение программы на взаимодейству­ ющие части. В то же время они более удобны для программиста. Вычислительных целей программы можно достичь с помощью множества альтернативных архитек­ турных решений, но качество программы при этом будет различаться.

Навыки корректного комбинирования элементов программы C++ и разделе­ ния тех компонентов, которые не следует объединять вместе, являются необходи­ мой предпосылкой для написания легко сопровождаемых и модифицируемых программ C++.

536 Часть 111 • Программирование с агрегированием и насдадованием

Вторая часть книги была посвящена применению знаний, полученных в ее первой части, для написания классов С4-+. В ней рассказывалось о синтаксисе классов, области их действия, элементах данных и функциях, доступе к данным

ифункциям, сообщениях (их синтаксисе и смысле), инициализации объектов,

остатических данных и функциях. Читатели узнали об операторных функциях, за

счет применения которых программа на C + + становится эффективной, однако они усложняют архитектуру классов. Кроме того, вы познакомились с "дружест­ венными" функциями, научились распознавать в конструкции класса опасные эле­ менты и избегать их негативного влияния на программу. Написание программы с использованием классов усложняет C+ + , но оно того стоит.

Навыки объединения (включения в один класс) имеющих отношение друг к другу данных и функций являются необходимой предпосылкой написания объектноориентированных программ. Основное различие между традиционными и объектноориентированными программами состоит в том, что традиционные программы C+ + строятся с помощью взаимодействующих глобальных функций, связывающих шаги каждой операции. Объектно-ориентированная программа состоит из взаимодей­ ствующих классов, объединяющих данные и операции.

Первые две части книги — это введение к объектно-ориентированному про­ граммированию. Во всех примерах использовался только один класс, поскольку основное внимание уделялось архитектуре классов, а не связям между ними. В третьей части книги вы приступили к изучению построения программ C+ + как набора взаимодействующих классов. Это требует реализации связей между классами программы. В главе 12 было показано, как можно использовать один объект (сервер) в качестве компонента другого объекта (клиента). Функции-члены объекта-компонента предоставляют свой сервис функциям-членам составного класса. Это наиболее распространенная простая связь между объектами.

Один объект может ссылаться на другой, являющийся элементом данных следующего объекта. Клиентский объект получает доступ к функциям-членам серверного объекта, передавая сообщения его элементу данных (указателю). Кроме того, один объект может использоваться как ссылочный элемент данных объекта-клиента. Синтаксически такой вариант аналогичен простой композиции классов, но фактически это совершенно разные связи между объектами.

При простой композиции класса серверный объект (компонент) является эле­ ментом данных объекта-клиента (составного объекта). При такой связи клиент­ ский объект имеет эксклюзивные права на объект-компонент. Когда серверный объект представляет ссылочный элемент данных объекта-клиента, он может со­ вместно использоваться несколькими клиентами, и несколько объектов могут ссылаться на один серверный объект. Изменения в серверном объекте влияют на состояние клиентского объекта. Вряд ли имеет смысл обсуждать, какая связь "лучше" — эксклюзивная композиция классов или совместное использование компонентов. Между тем во многих практических ситуациях одна связь действи­ тельно "лучше" другой, так как точнее представляет выраженную в программе C + + взаимосвязь между реальными объектами. Поэтому важно выбирать те связи, которые лучше моделируют реальную ситуацию.

Кроме того, рассматривалась такая популярная связь между объектами, как контейнер, когда один объект включает в себя набор объектов другого класса — компонентов контейнера (а не один единственный объект). Такая взаимосвязь часто встречается в программах C+ + , и нужно хорошо освоиться с организацией объектов в приложении и с выбором подходящих связей между ними.

В данной главе продолжается исследование взаимодействия между фрагмента­ ми программы C++ . Здесь читатели познакомятся с наследованием как механиз­ мом, представляющим связь ме>кду классами в приложении. На этом этапе разница между связью объектов и классов может казаться вам незначительной, но к концу главы вы ее почувствуете.

Глава 13 • Подобные классы и их интерпретация

537

Наследование очень часто применяется в программах C+ + . Оно представляет мощный механизм для повторного использования фрагментов программ C++, для разделения труда программистов и использования модульности в приложениях. Для корректного применения наследования следует изучить его синтаксис, методы создания экземпляров производных объектов, технику доступа к компонентам, правила вызова функций, разрешения имен и многое другое. Важно также знать, когда использовать наследование. Программисты, применяющие C+ + , иногда злоупотребляют наследованием. Они создают дополнительные взаимосвязи и за­ висимости, которые затрудняют понимание программы.

Будьте внимательны.

Интерпретация подобных классов

Программы моделируют различные реальные объекты через их данные (со­ стояние объекта) и операции (его поведение). Это азбука объектно-ориентирован­ ного проектирования ПО, но каждый разработчик должен сам решить, что именно следует включать в каждый класс. Моделирование реальных ситуаций должно отражать "общие свойства" объектов, например инвентарных записей, счетчиков событий или банковских счетов.

Все "общие свойства" с точки зрения наблюдателя, и C++ предусматривает разнообразные механизмы для объектов.

Первый механизм, предлагаемый C++ для воплощения общих свойств реаль­ ных объектов в классе, это сама конструкция класса. Она используется для показа общности объектов. Вы верите, что эти объекты можно охарактеризовать одним и тем же набором атрибутов и шаблонами поведения. Объекты различаются зна­ чениями атрибутов: точки разных углов фигуры имеют разные координаты, разные инвентарные единицы — различные названия, на каждом банковском счете — свой собственный баланс, и у них разные владельцы. Общность в том, что каждый прямоугольник имеет угловые точки, каждая инвентарная единица — название, каждому счету соответствует баланс и владелец. Если для одного банковского счета требуется задавать проценты, а для другого — нет, то обычно они не рас­ сматриваются как объекты одного класса.

Часто ситуация бывает не ясна. Например, каждый болт в инвентарной описи имеет индивидуальные характеристики и может отличаться в приложении от дру­ гих болтов. Чтобы описать каждый болт, придется создать для него отдельный класс, присвоить ему индивидуальный набор элементов данных и функций, задать уникальные имена. Такие имена могут отражать уникальный характер каждого болта в приложении, например RustyBolt, UglyBolt и BoltFoundlnPothole. Не исключено, что это осложняет ситуацию и имеет смысл только в случае, если от­ дельные болты не обладают общими свойствами и ведут себя по-разному.

Между тем у болтов в инвентарной описи много общего, так что можно пред­ ставить каждый болт, используя для элементов данных одни и те же имена. Таким образом, не нужно будет представлять каждый болт как объект другого класса. Возникновение проблемы можно предотвратить, используя всего лишь один к^шсс, например Bolt, и представляя каждый болт в приложении как объект данного класса с такими атрибутами, как дата покупки, имя поставщика и шаг резьбы. Аналогично, можно представить все гайки в инвентарной описи как объекты одного класса Nut, если для описания ка>вдого такого объекта достаточно одного и того же набора атрибутов (цвет, материал, размер и т. д.).

Если класс Bolt, Nut и другие инвентарные элементы используют одни и те же имена элементов данных, нужно применить только один класс Inventoryltem, представляющий эти разные объекты. Если все болты с точки зрения приложения одинаковы, можно представить их как, один объект и указать в числе атрибутов класса количество болтов. Так как все болты одинаковы, разница в шаге резьбы не столь важна. Если же она имеет значение, такая конструкция не подойдет.

538 Часть III« Программирование с агрегировонием и наследованием

Если приложение интересует лишь общая стоимость болтов, гаек и других ин­ вентарных единиц, можно представить инвентарную опись как объект типа Asset с атрибутами, соответствуюидими целям приложения.

Часто между классами суидествует обндность. Группы их объектов могут быть в чем-то похожи, но различаться набором атрибутов и операций.

Например, для мелких болтов может задаваться вес партии в 100 штук, а для больших болтов — вес одной штуки. Иногда важно также значение максимально­ го усилия, которое должно применяться к крупному болту.

Точно так же для сотрудников, получаюидих почасовую оплату, в качестве эле­ ментов данных может указываться зарплата в час с числом наработанных в неделю часов. У сотрудников с месячным окладом многие атрибуты будут такими же (фа­ милия, адрес, дата приема на работу и т. д.), но вместо почасовой оплаты задается годовая.

Некоторые группы объектов могут различаться набором операций или осуще­ ствлять дополнительные операции. Например, сберегательный счет предусмат­ ривает выплату процентов, а расчетный — плату за операции. Если все эти характеристики собрать в один класс, такой вариант будет отвечать требованиям клиента, но окажется ненадежным. Клиент может некорректно использовать объект, предположив наличие у него свойств, присущих другому объекту, напри­ мер, он может попытаться выплатить проценты по расчетному счету или взять плату за операцию по сберегательному счету.

Тем не менее слияние всех атрибутов и операций в один класс — вполне жиз­ неспособный метод абстракции. Именно клиент должен убедиться, что каждый объект используется согласно его характеристикам.

Слияние свойств подклассов в один класс

Рассмотрим такой пример.

Возьмем упрощенный класс Account с элементом данных balance и функциямичленами withdrawO и deposit(). Для расчетного счета операция снятия денег должна предусматривать плату (например, 20 центов). Для сберегательного счета ежедневно начисляются проценты (6% годовых). Уровень платы за операцию и проценты годовых представлены в классе Account как элементы данных. Для простоты примера здесь не обсу>вдаются методы спецификации и изменения чис­ ловых литералов и другие бесчисленные практические детали, например фамилия владельца, адрес, возраст, номер социального страхования, плата за превышение кредита и другие неприятные и приятные детали банковского бизнеса.

В листинге 13.1 показана программа, реализующая свойства обоих счетов в комбинированном классе Account. Клиент определяет объекты Account и выпол­ няет соответствующие операции. Такой вид клиента типичен для "дообъектноориентированного" программирования.

Листинг 13.1. Пример комбинирования различных свойств в одном классе Account

#inclucle <iostream> using namespace std;

class Account {

double

balance;

/ /

для всех видов счетов

double

rate;

/ /

только

для

сберегательных

double

fee;

/ /

только

для

расчетных

public:

Account(double initBalance = 0)

// только для расчетных счетов

{ balance = initBalance; fee = 0.2; }

// использовать плату за операцию, а не %

 

Глава 13 • Подобные КАОССЫ И ИХ интерпретация

539

Account(double initBalance, double initRate)

// для сберегательного счета

 

{ balance = initBalance; rate = initRate; }

 

// не плата, а %

 

double getBalO

 

 

 

// для обоих счетов

 

{ return balance; }

 

 

 

 

void withdraw(double amount)

 

 

 

// общая для обоих счетов

 

{ if (balance > amount)

 

 

 

 

 

 

balance -=amount; }

 

 

 

 

 

 

void deposit(double amount)

 

 

 

// для обоих счетов

 

{ balance += amount; }

 

 

 

 

 

 

void paylnterest ()

 

 

 

// только для сберегательных счетов

 

{ balance += balance * rate / 365 / 100; }

 

 

 

 

void applyFeeO

 

 

 

// только для расчетных счетов

 

{ balance -= fee; }

 

 

 

 

} :

 

 

 

 

 

 

 

int mainO

 

 

 

 

 

 

 

{

 

 

 

// a1: расчетный счет, a2: сберегательный счет

Account a1(1000), a2(1000,6.0);

 

cout «

"Начальный баланс: " «

a1.getBal()

 

 

 

 

«

" " « a2.getBal() «

endl;

// нет проблем

 

al.withdraw(IOO); a2.deposit(100);

 

a2.paylnterest(); a1. applyFeeO;

 

 

//нет ошибок

 

cout «

"Конечный баланс: " «

a1.getBal()

 

 

 

 

«

" " « a2.getBal() «

endl;

 

 

 

 

return 0;

}

Сегодня никто не верит в человеческую непогрешимость. Если что-то где-то может быть написано, то когда-нибудь так и случится. Например, пятая строка клиента может выглядеть так:

a1.paylnterest(); а2.арр1уРее();

/ / неудачная попытка...

Конечно, невозможно предотвратить все ошибки программирования (вот почему необходимо тестирование), но следует хотя бы постараться это сделать. Вы може­ те добиться того, чтобы сообш,ения об ошибках выводились заранее и не нужно было анализировать результат. Данная архитектура нуждается в улучшении.

Обратите внимание, что в клиенте при "создании счета даются явные коммен­ тарии о его типе, но ничто не мешает здесь программисту выразить свои идеи в операторах, а не в комментариях. Для этого серверный класс (Account) должен поддерживать потребности клиента, позволяя ему явно различать виды объектов Account.

Перенос ответственности за целостность программы на сервер

Чтобы избежать опасности некорректного использования серверных объектов клиентом, можно добавить в серверный класс дополнительный атрибут — поле тега (признака), описываюш,ее вид счета, к которому относится данный конкрет­ ный объект. Это означает, что в классе вводятся подклассы.

При создании объекта поле тега можно установить таким образом, чтобы указать подкласс объекта при его инициализации. При использовании объекта (например, paylnterestO или applyFeeO) это поле проверяется, чтобы убедиться в допустимости операции для данного вида объекта.

Соседние файлы в предмете Программирование на C++