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

C# для чайников

.pdf
Скачиваний:
194
Добавлен:
27.03.2015
Размер:
15.52 Mб
Скачать

С# генерирует зловещего вида предупреждение при компиляции рассматривавшейся ране демонстрационной программы H i d i n g W i t h d r a w a l . Из всего длинного текста

предупреждения интерес представляет только небольшая его часть, а именно:

1 .. . S a v i n g s A c c o u n t . W i t h d r a w ( d e c i m a l ) ' h i d e s i n h e r i t e d m e m b e r ' . . . B a n k A c c o u n t . W i t h d r a w ( d e c i m a l ) ' . U s e t h e n e w k e y w o r d i f h i d i n g w a s i n t e n d e d .

C# пытается сообщить, что вы написали метод подкласса, который имеет то же имя, что и метод базового класса. Действительно ли вы хотите именно этого?

Это всего лишь предупреждение. Вы можете и не реагировать на него, но все же крайне желательно ознакомиться со всеми предупреждениями, выводимыми компилятором, и избавиться от них. Предупреждение почти всегда говорит о какой-то мелочи, которая может перерасти в крупные неприятности, если во­ время о ней не позаботиться.

Неплохо дать указание компилятору С # рассматривать все предупреждения как,

ошибки, по крайней мере на этапе отладки. Дл я этого следует воспользоваться командой мен ю Projects Properties и прокрутить панель Build страницы свойств проекта до раздела Errors and Warnings. Установите значение пара­ метра Warning Level равным 4, наивысшей возможной величине. Кроме того, в подразделе Treat Warnings as Errors выберите флаг АLL. П р и этом при рабо­

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

Описатель n e w , упомянутый в предупреждении и показанный в приведенном далее фрагменте исходного текста, говорит компилятору С# о том, что сокрытие метода пред­ намеренное (тем самым предупреждение устраняется).

//

Теперь

с W i t h d r a w () н и к а к и х п р о б л е м

new

p u b l i c

d e c i m a l W i t h d r a w ( d e c i m a l m W i t h d r a w a l )

{

 

 

// .

. . Никаких иных изменений не требуется . . .

}

Такое использование ключевого слова n e w не имеет ничего общего с его при­ менением для создания объекта.

Вызов методов базового класса

Вернемся

к методу

S a v i n g s A c c o u n t . W i t h d r a w () из демонстрационной про­

граммы H i d i n g W i t h d r a w a l ,

рассматривавшейся

ранее в этой главе. Вызов

B a n k A c ­

count . W i t h d r a w () из

этого

нового метода осуществляется при п о м о щ и

ключевого

слова b a s e .

 

 

 

 

 

Приведенная далее версия функции без ключевого слова b a s e работать не будет:

new p u b l i c

d e c i m a l

W i t h d r a w ( d e c i m a l

m W i t h d r a w a l )

 

{

 

 

 

 

 

d e c i m a l m A m o u n t W i t h d r a w n = W i t h d r a w ( m W i t h d r a w a l ) ;

Глава 13. Полиморфизм

289

m A m o u n t W i t h d r a w n + = W i t h d r a w ( 1 . 5 ) ;

 

 

 

 

r e t u r n

m A m o u n t W i t h d r a w n ;

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

В этом случае возникает та же проблема, что и в следующем фрагменте:

 

v o i d

f n ( )

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

f n () ;

// В ы з о в ф у н к ц и е й с а м о й с е б я

 

 

}

 

 

 

 

 

 

 

 

 

Вызов f п () из

f п ()

приводит к рекурсивному

вызову функцией самой себя. Анапа

гично,

такой

вызов

W i t h d r a w ( ) , как показано в

фрагменте выше, приводит к вызов;

функцией самой себя, пока программа в конечном счете не завершится аварийно.

Требуется

указать С#,

что в методе S a v i n g s A c c o u n t . W i t h d r a w ( )

следуетщ

звать

метод

B a n k A c c o u n t . W i t h d r a w ( ) .

Один

и з

вариантов

решения

поставлена!

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

t h i s

в

указатель на

объект ВапкАссоия

перед выполнением вызова:

 

 

 

 

 

/ / W i t h d r a w - э т а в е р с и я о б р а щ а е т с я к с о к р ы т о м у м е т о д у

/ / б а з о в о г о к л а с с а п о с р е д с т в о м я в н о г о п р е о б р а з о в а н и я t h i s n e w p u b l i c d e c i m a l W i t h d r a w ( d e c i m a l m W i t h d r a w a l )

{

/ / П р е о б р а з о в а н и е у к а з а т е л я t h i s в о б ъ е к т к л а с с а

/ / B a n k A c c o u n t

 

 

 

B a n k A c c o u n t b a =

( B a n k A c c o u n t ) t h i s ;

 

/ / В ы з о в W i t h d r a w ( )

с и с п о л ь з о в а н и е м о б ъ е к т а B a n k A c c o u n t

d e c i m a l m A m o u n t W i t h d r a w n = b a . W i t h d r a w ( m W i t h d r a w a l ) ;

m A m o u n t W i t h d r a w n + = b a . W i t h d r a w ( 1 . 5 ) ;

 

r e t u r n m A m o u n t W i t h d r a w n ;

 

}

 

 

 

Данное решение вполне работоспособно: вызов

ba . W i t h d r a w () вызывает мети

класса B a n k A c c o u n t . Однако

в будущем изменение

программы может привести кл

кому изменению иерархии

классов, что S a v i n g s A c c o u n t не будет непосредственны)

потомком B a n k A c c o u n t . Подобная модификация приведет к неверной работе функцщ найти причину которой будет нелегко.

Необходим способ пояснить С#, что требуется вызвать функцию W i t h d r a w () из клас­ са, являющегося непосредственным предшественником т е к у щ е г о — причем без явной именования этого класса. Для этой цели в С# служит ключевое слово b a s e .

Это то же ключевое слово b a s e , которое конструктор использует для переда! аргумента конструктору базового класса.

Ключевое слово С# b a s e в показанном далее фрагменте кода представляет собой то же, что и t h i s , но приведение к базовому классу выполняется независимо от того, кап именно класс является таковым .

/ / W i t h d r a w - м о ж н о с н и м а т ь л ю б у ю с у м м у в п р е д е л а х б а л а н с а ; / / в о з в р а щ а е т с н я т у ю с о с ч е т а с у м м у

n e w p u b l i c d e c i m a l W i t h d r a w ( d e c i m a l m W i t h d r a w a l )

{

/ / С н я т и е д о п о л н и т е л ь н о й с у м м ы 1 . 5 0 b a s e . W i t h d r a w ( 1 . 5 M ) ;

290

Часть IV. Объектно-ориентированное программирова

// Снятие со с ч е т а с

о с т а в ш е й с я с у м м о й

return base . Withdraw (mWithdrawal) ;

}

 

Вызов b a s e . W i t h d r a w ()

приводит к вызову метода B a n k A c c o u n t . W i t h d r a w ( ) ;

и самым проблема, связанная с рекурсией, снимается. Кроме того, данное решение работает и при изменении иерархии наследования.

Можно перегрузить метод базового класса методом в подклассе. Это и замечательно, одновременно очень опасно.

Проведем мысленный эксперимент: когда должно приниматься решение о том, какой из м е т о д о в — B a n k A c c o u n t . W i t h d r a w () или S a v i n g s A c c o u n t . W i t h d r a w () — будет вызван: во время компиляции или во время выполнения программы?

Для того чтобы понять, в чем здесь отличие, будет немного изменена рас­ сматривавшаяся ранее программа H i d i n g W i t h d r a w a l (здесь приведена только та часть, в которую внесены изменения).

// H i d i n g W i t h d r a w a l P o l y m o r p h i c a l l y - с о к р ы т и е м е т о д а

// W i t h d r a w () б а з о в о г о к л а с с а м е т о д о м с т е м же и м е н е м в

// п о д к л а с с е

 

 

 

 

 

 

public

c l a s s P r o g r a m

 

 

 

 

(

 

 

 

 

 

 

 

p u b l i c

s t a t i c

v o i d M a k e A W i t h d r a w a l ( B a n k A c c o u n t

b a ,

 

 

 

 

 

 

d e c i m a l m A m o u n t )

{

 

 

 

 

 

 

 

b a . W i t h d r a w ( m A m o u n t ) ;

 

 

 

}

 

 

 

 

 

 

 

p u b l i c

s t a t i c

v o i d M a i n ( s t r i n g [ ]

a r g s )

 

{

 

 

 

 

 

 

 

B a n k A c c o u n t

b a ;

 

 

 

 

 

S a v i n g s A c c o u n t

s a ;

 

 

 

 

b a = n e w B a n k A c c o u n t ( 2 0 0 M ) ;

 

 

M a k e A W i t h d r a w a l ( b a ,

1 0 0 M ) ;

 

 

 

s a = n e w S a v i n g s A c c o u n t ( 2 0 0 M ,

1 2 ) ;

 

M a k e A W i t h d r a w a l ( s a ,

1 0 0 M ) ;

 

 

 

/ / В ы в о д и м с о с т о я н и я с ч е т о в

 

 

C o n s o l e . W r i t e L i n e ( " Б а л а н с

B a n k A c c o u n t р а в е н

{ 0 : C } " ,

 

 

 

 

b a . B a l a n c e ) ;

 

 

C o n s o l e . W r i t e L i n e ( " Б а л а н с

S a v i n g s A c c o u n t р а в е н { 0 : C } " ,

 

 

 

 

s a . B a l a n c e ) ;

 

 

/ / О ж и д а е м п о д т в е р ж д е н и я п о л ь з о в а т е л я

 

C o n s o l e . W r i t e L i n e ( " Н а ж м и т е < E n t e r > д л я " +

 

 

 

 

 

" з а в е р ш е н и я п р о г р а м м ы . . . " ) ;

 

C o n s o l e . R e a d ( ) ;

 

 

 

 

 

Вывод этой демонстрационной программы на экран может вас удивить (а может и не удивить — в зависимости от того, чего именно вы ожидали):

Глава 13. Полиморфизм

291

Б а л а н с B a n k A c c o u n t р а в е н

$ 1 0 0 . 0 0

 

 

 

 

 

 

Б а л а н с S a v i n g s A c c o u n t

р а в е н

$ 1 0 0 . 0 0

 

 

 

 

 

Н а ж м и т е < E n t e r > д л я з а в е р ш е н и я п р о г р а м м ы . . .

 

 

 

 

В этот раз, вместо снятия со

счета

в

функции

M a i n ( ) , программа передает объект

счета функции M a k e A W i t h d r a w a l ( ) .

 

 

 

 

 

 

 

 

Первый вопрос очевиден: как функция

M a k e A W i t h d r a w a l ()

может

принимать

объект

S a v i n g s A c c o u n t ,

 

если

она ожидает в качестве аргумента объект BankAc

c o u n t ? Ответ не

менее

ясен:

потому

что

S a v i n g s A c c o u n t

Я В Л Я Е Т С Я

BankAc

c o u n t

(см. главу 12, "Наследование") .

 

 

 

 

 

 

 

 

Второй вопрос

не

так

очевиден. Когда функции M a k e A W i t h d r a w a l

()

передает!

объект

B a n k A c c o u n t ,

она

вызывает B a n k A c c o u n t . W i t h d r a w ()

— это

понятно. Не

когда передается объект типа S a v i n g s A c c o u n t ,

вызывается тот

же метод. Должен ли

в этом

случае вызываться метод W i t h d r a w ()

подкласса?

 

 

 

 

С

одной стороны,

поскольку

объект

ba

принадлежит типу

B a n k A c c o u n t , вызов

b a . W i t h d r a w ()

должен вызывать метод B a n k A c c o u n t . W i t h d r a w ( ) .

С другой сто

роны,

хотя объект ba и объявлен

как B a n k A c c o u n t , фактически

он

представляет собой

объект

S a v i n g s A c c o u n t ,

так

что

 

должен

быть вызван

метод

S a v i n g s A c

c o u n t

. W i t h d r a w ( ) . Оба

аргумента достаточно

логичны .

 

 

 

 

В данном случае С# принимает как более весомый первый аргумент. Это более безопасный выбор — работать с объявленным типом — поскольку он устраняет все недора зумения. Объект объявлен как B a n k A c c o u n t , и так тому и быть.

Что неверно в стратегии использования объявленного типа

В ряде случаев вам не требуется работа с объявленным типом. На самом деле необходимо, чтобы вызов базировался на реальном типе, т.е. на типе времени исполнения, а не на объявленном типе. Например, вам нужно, чтобы выполнялись действия со счетом типа S a v i n g s A c c o u n t , который хранится в переменной типа B a n k A c c o u n t . Такая

возможность принятия

решения во время выполнения программы называется полимор­

физмом, или поздним связыванием (late binding). Стратегия использования

объявленного

типа называется ранним

связыванием (early binding), в противоположность

позднему.

Термин полиморфизм происходит из греческого языка: поли означает много,

аморф — форма, или действие.

По л и м о р ф и з м и позднее связывание вообще-то не одно и то же. Однако их отличие весьма тонкое. Полиморфизм означает возможность принятия решения о том, какой ме­

тод должен быть вызван в процессе выполнения программы . Позднее связывание — спо­ соб реализации полиморфизма языком программирования .

П о л и м о р ф и з м является ключевой составляющей частью объектно-ориентированного программирования . Он настолько важен, что языки, его не поддерживающие, не имеют права называться объектно-ориентированными.

Я з ы к и программирования, поддерживающие классы, но не поддерживающие полиморфизм, называются объектно-основанными языками (object-based lan­ guages). Примером такого языка может служить язык Ada.

292

Часть IV. Объектно-ориентированное программирование

Без полиморфизма в наследовании мало толку. Позвольте привести наглядный при­ мер, иллюстрирующий данный тезис. Предположим, вы написали м о щ н у ю программу, использующую класс... ну, скажем, S t u d e n t . После нескольких месяцев проектирова­ ния, кодирования и тестирования вы наконец-то вынесли ее на суд восхищенных пользо­ вателей (начинающих даже поговаривать, что совершенно напрасно не существует Но - бевской премии в области программирования) .

Проходит время, и ваш шеф требует, чтобы вы добавили в программу возможность рабо­ ты с аспирантами, которые, конечно, похожи на студентов, но все же немного отличаются от них (сами аспиранты считают, что они отличаются во всем). Предположим, что формулы для вычисления оплаты за обучение для студентов и аспирантов совершенно различны. Вашему боссу это безразлично, но в программе имеется масса вызовов функции C a l c T u i t i o n ( ) , яредаазначенной для таких расчетов. Вот пример одного из таких вызовов:

v o i d S o m e F u n c t i o n ( S t u d e n t s )

{

// . . . К а к и е - т о д е й с т в и я . . .

s . C a l c T u i t i o n ( ) ; II... продолжение . . .

}

Если бы С# не поддерживал позднее связывание, то вам бы пришлось редактировать функцию S o m e F u n c t i o n ( ) , чтобы проверять в ней, является ли переданный объект s пе­

ременной типа S t u d e n t или

G r a d u a t e S t u d e n t . Программа должна была

бы вызывать

Student. C a l c T u i t i o n ()

в случае, когда переменная s принадлежала бы

классу S t u -

|dent, и G r a d u a t e S t u d e n t . C a l c T u i t i o n () в случае класса G r a d u a t e S t u d e n t . Это было бы не так страшно, если бы не две вещи.

Это только одна функция. А теперь представьте, что C a l c T u i t i o n () вызывает­

ся в сотнях мест...

Предположим, что C a l c T u i t i o n () — не единственное отличие между двумя классами. Ш а н с ы , что вы найдете все места в программе, требующие изменений, резко снижаются ...

При поддержке полиморфизма вы просто позволяете С# самостоятельно решить, ка­ кой метод должен быть вызван.

Использование is для полиморфного доступа к скрытому методу

Каким образом сделать программу полиморфной? Один из подходов для решения

этой задачи в

С# состоит в использовании ключевого слова i s (о котором

рассказыва­

юсь в

главе

12, "Наследование") . Выражение ba

is

S a v i n g s A c c o u n t

возвращает

иачение

t r u e

или f a l s e в зависимости от класса

объекта во время выполнения

про­

граммы. Объявленный тип может быть B a n k A c c o u n t ,

но с какого типа объектом

при­

ходится иметь дело в реальности? В приведенном далее фрагменте исходного текста is

используется для

обращения

к функции W i t h d r a w () класса

S a v i n g s A c c o u n t .

public c l a s s

P r o g r a m

 

 

p u b l i c

s t a t i c v o i d

M a k e A W i t h d r a w a l ( B a n k A c c o u n t b a ,

 

 

 

d e c i m a l

m A m o u n t )

Глава 13.

Полиморфизм

 

293

{

i f b a i s S a v i n g s A c c o u n t

{

S a v i n g s A c c o u n t s a = ( S a v i n g s A c c o u n t ) b a , • s a . W i t h d r a w ( m A m o u n t ) ;

}e l s e

{

b a . W i t h d r a w ( m A m o u n t ) ;

}

}

Теперь, когда M a i n () передает функции объект типа S a v i n g s A c c o u n t , функции M a k e A W i t h d r a w a l () проверяет тип времени выполнения объекта ba и вызывает функцию S a v i n g s A c c o u n t . W i t h d r a w ( ) .

Программист может выполнить вызов одной строкой: ( ( S a v i n g s A c c o u n t ) b a ) . W i t h d r a w ( m A m o u n t ) ;

Об этом упоминается только потому, что имеется масса программ, в который вызовы осуществляются именно таким образом. Однако данный подход приво дит к менее удобочитаемому исходному тексту, соответственно — к большему количеству ошибок в программе.

Подход с использованием i s вполне работоспособен, но это далеко не лучшая идея Применение is требует от функции M a k e A W i t h d r a w a l () осведомленности о всех возможных типах счетов, которые имеются (и могут появиться в дальнейшем) в банк Это накладывает на функцию M a k e A W i t h d r a w a l () слишком большую ответстве ность. Да, сейчас ваше приложение обходится двумя классами, но завтра от вас могу потребовать реализовать новый вид счета, например, C h e c k i n g A c c o u n t , и вы будете вынуждены перерыть всю программу в поисках мест, в которые надо внести добавления связанные с проверкой типа аргумента функции в процессе выполнения программы.

Объявление метода виртуальным

В качестве автора функции M a k e A W i t h d r a w a l () вы бы, конечно, не хотели делав ее осведомленной о всех возможных типах счетов. Хотелось бы предоставить это про­ граммисту, использующему функцию M a k e A W i t h d r a w a l ( ) , т.е. заставить С# самому; принимать решение о том, какой метод должен быть вызван,

основываясь на информации о типе объекта времени выполнения программы.

М о ж н о заставить С# самостоятельно принимать решение о версии W i t h d r a w () торую следует вызвать. Для этого необходимо пометить функцию базового класса при п о м о щ и ключевого слова v i r t u a l , а каждую версию функции в подклассах — ключе вым словом o v e r r i d e .

Предыдущая демонстрационная программа переписана с использован полиморфизма, при этом в каждый из методов W i t h d r a w () добавлен вод строки, чтобы было точно видно, какого класса метод вызывается в или ином случае.

/ / P o l y m o r p h i c l n h e r i t a n c e - п о л и м о р ф н о е с о к р ы т и е м е т о д а / / б а з о в о г о к л а с с а

294

Часть IV. Объектно-ориентированное программиров

using S y s t e m ;

 

 

 

namespace

P o l y m o r p h i c l n h e r i t a n c e

 

{

 

 

 

 

/ / B a n k A c c o u n t - п р о с т е й ш и й б а н к о в с к и й с ч е т

 

p u b l i c

c l a s s B a n k A c c o u n t

 

 

 

{

 

 

 

 

p r o t e c t e d d e c i m a l m B a l a n c e ;

 

 

p u b l i c

B a n k A c c o u n t ( d e c i m a l

m l n i t i a l B a l a n c e )

 

m B a l a n c e = m l n i t i a l B a l a n c e ;

 

p u b l i c

d e c i m a l B a l a n c e

 

 

 

g e t { r e t u r n m B a l a n c e ;

}

 

 

p u b l i c v i r t u a l d e c i m a l W i t h d r a w ( d e c i m a l m A m o u n t )

C o n s o l e . W r i t e L i n e ( " B a n k A c c o u n t . W i t h d r a w ( ) с

$ { o } . . . " ,

 

m A m o u n t ) ;

 

d e c i m a l m A m o u n t T o W i t h d r a w = m A m o u n t ;

 

i f ( m A m o u n t T o W i t h d r a w > B a l a n c e )

 

{

 

 

 

 

m A m o u n t T o W i t h d r a w = B a l a n c e ;

 

}

 

 

 

 

m B a l a n c e - = m A m o u n t T o W i t h d r a w ;

 

r e t u r n m A m o u n t T o W i t h d r a w ;

 

 

/ / S a v i n g s A c c o u n t - б а н к о в с к и й с ч е т с н а ч и с л е н и е м

 

// п р о ц е н т о в

 

 

 

p u b l i c c l a s s S a v i n g s A c c o u n t

:

B a n k A c c o u n t

 

{

 

 

 

 

p u b l i c d e c i m a l m l n t e r e s t R a t e ;

 

/ / S a v i n g s A c c o u n t - п р о ц е н т н а я с т а в к а у к а з ы в а е т с я к а к

/ / ч и с л о о т 0 д о 1 0 0

 

 

 

p u b l i c

S a v i n g s A c c o u n t ( d e c i m a l m l n i t i a l B a l a n c e ,

 

 

d e c i m a l m l n t e r e s t R a t e )

 

 

: b a s e ( m l n i t i a l B a l a n c e )

 

{

t h i s . m l n t e r e s t R a t e = m l n t e r e s t R a t e / 1 0 0 ;

/ / A c c u m u l a t e l n t e r e s t - н а ч и с л е н и е п р о ц е н т о в p u b l i c v o i d A c c u m u l a t e l n t e r e s t ( )

{

m B a l a n c e = B a l a n c e + ( B a l a n c e * m l n t e r e s t R a t e ) , -

/ / W i t h d r a w . - с н я т и е с о с ч е т а п р о и з в о л ь н о й с у м м ы , н е

Глава 13. Полиморфизм

295

/ /

п р е в ы ш а ю щ е й и м е ю щ е й с я н а с ч е т у ; в о з в р а щ а е т с н я т у ю

/ /

с у м м у

o v e r r i d e p u b l i c d e c i m a l W i t h d r a w ( d e c i m a l m W i t h d r a w a l )

{

C o n s o l e . W r i t e L i n e ( " S a v i n g s A c c o u n t . W i t h d r a w ( ) . . . " ) ;

C o n s o l e . W r i t e L i n e ( " В ы з о в ф у н к ц и и W i t h d r a w б а з о в о г о " +

 

 

 

 

" к л а с с а

д в а ж д ы . . . ' " ) ;

 

 

/ / С н я т и е 1 . 5 0

 

 

 

 

 

 

b a s e . W i t h d r a w ( 1 . 5 М ) ;

 

 

 

 

/ / С н я т и е в п р е д е л а х о с т а в ш е й с я с у м м ы

 

 

r e t u r n b a s e . W i t h d r a w ( m W i t h d r a w a l ) ;

 

 

}

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

p u b l i c c l a s s P r o g r a m

 

 

 

 

 

{

 

 

 

 

 

 

 

 

p u b l i c

s t a t i c

v o i d M a k e A W i t h d r a w a l ( B a n k A c c o u n t

b a ,

 

 

 

 

 

 

d e c i m a l m A m o u n t )

{

 

 

 

 

 

 

 

 

b a . W i t h d r a w ( m A m o u n t ) ;

 

 

 

 

}

 

 

 

 

 

 

 

 

p u b l i c

s t a t i c

v o i d M a i n ( s t r i n g [ ]

a r g s )

 

 

B a n k A c c o u n t

b a ;

 

 

 

 

 

 

S a v i n g s A c c o u n t

s a ;

 

 

 

 

 

/ / В ы в о д б а л а н с а

 

 

 

 

 

C o n s o l e . W r i t e L i n e ( " M a k e A W i t h d r a w a l ( b a ,

. . . ) " ) ;

ba = n e w B a n k A c c o u n t ( 2 0 0 M ) ;;

 

 

 

M a k e A W i t h d r a w a l ( b a ,

1 0 0 M ) ;

 

 

 

 

C o n s o l e . W r i t e L i n e ( " Б а л а н с B a n k A c c o u n t р а в е н

{ 0 : C } " ,

 

 

 

 

b a . B a l a n c e ) ;

 

 

 

C o n s o l e . W r i t e L i n e ( " M a k e A W i t h d r a w a l ( s a ,

. . . ) " ) ;

s a = n e w S a v i n g s A c c o u n t ( 2 0 0 M ,

1 2 ) ;

 

 

M a k e A W i t h d r a w a l ( s a ,

1 0 0 M ) ;

 

 

 

 

C o n s o l e . W r i t e L i n e ( " Б а л а н с

S a v i n g s A c c o u n t

р а в е н { 0 : C } " ,

 

 

 

 

s a . B a l a n c e ) ;

 

 

 

/ / О ж и д а е м п о д т в е р ж д е н и я п о л ь з о в а т е л я

 

 

C o n s o l e . W r i t e L i n e ( " Н а ж м и т е < E n t e r > д л я " +

 

 

 

 

 

" з а в е р ш е н и я п р о г р а м м ы . . . " ) ;

 

C o n s o l e . R e a d ( ) ;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

} }

 

 

 

 

 

 

 

 

Вывод программы имеет следующий вид:

 

 

 

 

M a k e A W i t h d r a w a l ( b a ,

. . . )

 

 

 

 

 

B a n k A c c o u n t . W i t h d r a w ( )

с

$ 1 0 0 . . .

 

 

 

 

Б а л а н с B a n k A c c o u n t р а в е н $ 1 0 0 . 0 0

 

 

 

 

M a k e A W i t h d r a w a l ( s a ,

. . . )

 

 

 

 

 

S a v i n g s A c c o u n t . W i t h d r a w ( ) . . .

 

 

 

 

296

 

Часть IV. Объектно-ориентированное программирование

Вызов ф у н к ц и и W i t h d r a w

б а з о в о г о

к л а с с а д в а ж д ы . . .

B a n k A c c o u n t . W i t h d r a w ( )

с

$ 1 . 5 . . .

B a n k A c c o u n t . W i t h d r a w ( )

с $ 1 0 0 . . .

Баланс

S a v i n g s A c c o u n t

р а в е н $ 9 8 . 5 0

Нажмите

< E n t e r >

д л я з а в е р ш е н и я

п р о г р а м м ы . . .

Метод W i t h d r a w ()

помечен в

базовом

классе B a n k A c c o u n t как v i r t u a l , в то вре­

мя как в подклассе он помечен как o v e r r i d e . Метод M a k e A W i t h d r a w a l () остается без изменений, и вывод при его вызове различен из-за того, что разрешение вызова ba. W i t h d r a w () осуществляется на основании типа ba во время выполнения программы.

Для полного понимания того, как это работает, желательно пошагово пройти программу в отладчике Visual Studio 2005. Для этого соберите программу как обычно, а затем нажимайте клавишу <F11> для пошагового ее выполнения. Это достаточно впечатляющее зрелище, когда один и тот же вызов приводит в раз­ ные моменты к двум разным методам.

Будьте экономны при объявлении методов виртуальными. Все имеет свою це­ ну, так что используйте ключевое слово v i r t u a l только при необходимости.

Утка — вид птицы. Так же как воробей или колибри. Любая птица представляет ка­ кой-то подвид птиц. Но обратная сторона медали в том, что нет птицы, которая была бы птицей вообще. С точки зрения программирования это означает, что все объекты B i r d являются экземплярами каких-то подклассов B i r d , но не имеется ни одного экземпляра класса B i r d . Так что же такое птица? Это всегда какой-то конкретный вид — пингвин, курица или, к примеру, страус.

Различные типы птиц имеют множество общих свойств (в противном случае они бы не были птицами), но нет двух типов, у которых бы общими были все свойства. Если бы такие типы были, они были бы одинаковыми типами, ничем не отличающимися друг от друга.

Разложение классов

Люди систематизируют объекты, выделяя их общие черты. Ч т о б ы увидеть, как это работает, рассмотрим два к л а с с а — H i g h S c h o o l и U n i v e r s i t y , показанные на рис. 13.1. Здесь для описания классов использован Унифицированный Я з ы к Моделиро­ вания (Unified Modeling Language, U M L ) , графический язык, описывающий классы и их взаимоотношения друг с другом.

Помните — машина ЯВЛЯЕТСЯ транспортным средством, но С О Д Е Р Ж И Т мотор.

t Как видно на рис. 13.1, у ш к о л ы и университета много общих свойств. И у школы, и у университета имеется открытый метод E n r o l l () для добавления объекта S t u d e n t (зачисления в учебное заведение). Оба класса имеют закрытый член n u m S t u d e n t s , в котором хранится число учащихся. Еще одно общее свойство — взаимоотношения уча­ щихся и учебных заведений: в учебном заведении может быть много учащихся, в то время

Глава 13. Полиморфизм

297

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

Рис. 13.1. UML-описание классов HighSchool и University

В дополнение к свойствам ш к о л ы университет содержит метод G e t G r a n t () и членданные nAvgSAT .

Рис. 13.1 корректно отображает ситуацию, но большая часть информации дублирует­ ся. Уменьшить дублирование можно, если позволить классу U n i v e r s i t y унаследовать более простой класс H i g h S c h o o l , как показано на рис. 13.2.

 

Рис.

13.2.

Наследование

класса

 

 

 

HighSchool

упрощает

класс

Uni -

 

 

 

versity,

но

привносит

определен­

 

 

 

ные проблемы

 

 

 

 

 

 

Класс

H i g h S c h o o l остается

неизменным,

но

класс

U n i v e r s i t y

при этом

проще

описать.

М о ж н о сказать, что U n i v e r s i t y — это

класс

H i g h S c h o o l

с членом

nAvg-

SAT и методом G e t G r a n t ( ) . Однако такое решение имеет одну фундаментальную про­ блему — университет вовсе не школа со специальными свойствами.

Вы можете сказать: " Н у и что? Главное, что наследование работает и экономит наши усилия". Да, конечно, это так, но сказанное выше — не просто стилистическая тривиаль­ ность. Такое неверное представление может ввести в заблуждение программиста как сейчас, так и в будущем. В один прекрасный день ему, незнакомому с вашими фокусами, придется читать и разбираться в ваших исходных текстах, и такое неверное представле­

ние может привести к неправильному пониманию программы .

 

Кром е того, неверное представление м о ж е т привести к

р е а л ь н ы м проблемам.

П р е д п о л о ж и м , что в ш к о л е р е ш и л и выбирать лучшего ученика,

и для этого програм-

298

Часть IV. Объектно-ориентированное программирование

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]