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

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

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

Часть V

З а б а з о в ы м и к л а с с а м и

В а ши объекты до сих пор были простыми вещами наподобие целых чисел или строк, в крайнем случае — счетов B a n k A c c o u n t . Но в С# имеются и другие объекты. Из этой части вы узнаете, как писать соб­ ственные объекгы типов-значений (работающие подобно типам i n t или f l o a t ) , и познакомитесь с интерфейсами, которые позволяют сделать ваши объекты более обобщенными и гибкими. Вместе с аб­ страктными классами, рассмотренными в главе 13, "Полиморфизм", интерфейсы предоставляют ключ к передовым методам проектиро­ вания программ. Так что читайте внимательно!

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

Глава 14

Интерфейсы и структуры

Отношение М О Ж Е Т _ И С П О Л Ь З О В А Т Ь С Я _ К А К

Определение интерфейса

Использование интерфейса для выполнения распространенных операций

Определение структуры

Использование структуры для объединения классов, интерфейсов и встроенных типов в одну иерархию классов

ласе может содержать ссылку на другой класс. Это — простое отношение СО­ ДЕРЖИТ. Один класс может расширять другой класс с п о м о щ ь ю наследования.

Ь—отношение Я В Л Я Е Т С Я . Интерфейсы С# реализуют еще одно, не менее важное яношение — М О Ж Е Т _ И С П О Л Ь З О В А Т Ь С Я _ К А К .

Если вы хотите написать памятку, вы можете взять ручку и обрывок бумаги, можете Епользоваться органайзером или сделать это посредством своего компьютера. Все эти ккты реализуют операцию "написать памятку" — T a k e A N o t e . Используя магию нашедования, на языке С# это можно реализовать следующим образом:

::stract class ThingsThatRecord

{

abstract p u b l i c v o i d T a k e A N o t e ( s t r i n g s N o t e ) ;

}

public class Pen : ThingsThatRecord

{

override public void TakeANote (string sNote)

{

II. . . Написание заметки ручкой . . .

}

}

public class PDA : ThingsThatRecord

override

p u b l i c v o i d T a k e A N o t e ( s t r i n g s N o t e )

I I . .

. п р и п о м о щ и о р г а н а й з е р а . . . "

)

 

}

p u b l i c c l a s s L a p t o p

:

T h i n g s T h a t R e c o r d

{

 

 

 

o v e r r i d e

p u b l i c v o i d

T a k e A N o t e ( s t r i n g s N o t e )

{

 

 

 

II... еще каким-то образом . . .

 

}

 

 

 

}

 

 

 

Если

ключевое

слово a b s t r a c t вас с м у щ а е т — обратитесь к главе

" П о л и м о р ф и з м " , за пояснениями . Если вам непонятно, что такое наследова ние — перечитайте главу 12, "Наследование" .

Решение с использованием наследования выглядит неплохо до тех пор, пока интерес вызывает только операция T a k e A N o t e ( ) . Функция наподобие показанной далее Re c o r d T a s k () может использовать метод T a k e A N o t e () для того, чтобы записать спи сок необходимых покупок в зависимости от того, какое средство есть у вас под рукой: v o i d R e c o r d T a s k ( T h i n g s T h a t R e c o r d t h i n g s )

{

/ / Э т о т а б с т р а к т н ы й м е т о д р е а л и з о в а н в о в с е х к л а с с а х , / / к о т о р ы е н а с л е д у ю т T h i n g s T h a t R e c o r d

t h i n g s . T a k e A N o t e ( " С п и с о к п о к у п о к " ) , - // . . . и т а к д а л е е . . .

}

Однако это решение сталкивается с двумя большими проблемами.

Первая проблема — фундаментальная. Дело в том, что реально связать ручку

органайзер и компьютер соотношением Я В Л Я Е Т С Я невозможно . Знание того, как работает ручка, не дает никаких сведений о том, как записывают информацию компьютер или органайзер.

Вторая проблема чисто техническая. Гораздо лучше описать L a p t o p как под

класс класса C o m p u t e r . Хотя PDA также можно наследовать от того же класса C o m p u t e r , этого нельзя сказать о классе Р е п . Вы можете охарактеризовать pyчку как некоторый тип M e c h a n i c a l W r i t e D e v i c e (механическое пишущее y c т р о й ство) или D e v i c e T h a t S t a i n s Y o u r S h i r t (устройство, пачкающее ваши шта­ ны). Однако в С# класс не может быть наследован от двух разных классов одно временно — класс С# может быть вещью только одного сорта.

В е р н е м с я

к т р е м и с х о д н ы м к л а с с а м .

Е д и н с т в е н н о е о б щ е е , что

у них есть —

то, что все

о н и м о г у т и с п о л ь з о в а т ь с я

для

з а п и с и чего - либо .

Отношение МО

Ж Е Т _ И С П О Л Ь З О В А Т Ь С Я _ К А К R e c o r d a b l e

позволяет связать

и х

пригодность

некоторой цели без применения наследования.

 

 

 

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

312

Часть V. За базовыми классам

interface IRecordable

 

 

{

 

 

v o i d T a k e A N o t e ( s t r i n g

s N o t e )

 

{

 

 

Обратите внимание на ключевое

слово i n t e r f a c e там, где обычно стоит ключевое

слово c l a s s . В фигурных скобках

интерфейса приведен список абстрактных

методов.

Интерфейсы не содержат определения никаких членов-данных.

 

Метод T a k e A N o t e () записан без реализации. Ключевые слова p u b l i c и

v i r t u a l

ни a b s t r a c t не являются необходимыми . Все методы интерфейса открыты,

а сам он

не включается ни в какое обычное наследование — это интерфейс, а не класс.

 

Класс, который реализует интерфейс, должен предоставить реализацию для каждого злемента интерфейса. Метод, реализующий метод интерфейса, не использует ключевое слово o v e r r i d e — это не похоже на перекрытие виртуальной функции.

По соглашению имена интерфейсов начинаются с буквы I, Кроме того, для них, как правило, используются прилагательные (в то время как для имен клас­ сов — существительные). Как обычно, это только соглашение — С# совершен­ но все равно, как именно вы назовете ваш интерфейс.

Далее приведено объявление, указывающее, что класс PDA реализует интерфейс

IRecordable.

 

public c l a s s

PDA : I R e c o r d a b l e

{

 

public v o i d

T a k e A N o t e ( s t r i n g s N o t e )

{

 

II . . .

Н а п и с а н и е п а м я т к и . . .

Как видите, не существует отличий между синтаксисом объявления наследования базового класса T h i n g s T h a t R e c o r d и объявлением о реализации интерфейса I R e c o r d a b l e .

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

Вывод из всего сказанного — интерфейс описывает возможности и свойства. Кроме иго, он представляет собой контракт. Если вы согласны реализовать все методы, опре­ деленные в интерфейсе, вы получите все его возможности .

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

public class Pen : IRecordable

{

public

v o i d

T a k e A N o t e ( s t r i n g

s N o t e )

{

 

 

 

I I .

. .

З а п и с ь р у ч к о й . . .

}

 

 

 

Глава 14. Интерфейсы и структуры

313

}

p u b l i c c l a s s

PDA . -

E l e c t r o n i c D e v i c e , I R e c o r d a b l e

{

 

 

 

 

p u b l i c

v o i d T a k e A N o t e ( s t r i n g

s N o t e )

{

 

 

 

 

// .

.

. И с п о л ь з о в а н и е о р г а н а й з е р а . . .

}

 

 

 

 

}

 

 

 

 

p u b l i c c l a s s L a p t o p

: C o m p u t e r ,

I R e c o r d a b l e

{

 

 

 

 

p u b l i c

/ /

{}

}

v o i d

T a k e A N o t e ( s t r i n g

s N o t e )

 

З а п и с ь п р и п о м о щ и

к о м п ь ю т е р а

Каждый из этих трех классов наследует свой базовый класс, но реализует один и тот) интерфейс I R e c o r d a b l e , указьшающий, что каждый из трех классов может использован для написания памятки с применением метода T a k e A N o t e ( ) . Чтобы понять, почему может оказаться полезным, рассмотрим следующую функцию R e c o r d S h o p p i n g L i s t ( ]

p u b l i c c l a s s

P r o g r a m

 

{

 

 

 

 

 

s t a t i c

p u b l i c

v o i d R e c o r d S h o p p i n g L i s t ( I R e c o r d a b l e

 

 

 

 

 

r e c o r d i n g O b j e c t )

{

 

 

 

 

 

/ / С о з д а н и е с п и с к а п о к у п о к

 

s t r i n g

s L i s t

= G e n e r a t e S h o p p i n g L i s t ( ) ;

/ / З а п и с ь с п и с к а

 

r e c o r d i n g O b j e c t . T a k e A N o t e ( s L i s 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 )

{

 

 

 

 

 

PDA

p d a

=

n e w P D A ( ) ;

 

R e c o r d S h o p p i n g L i s t ( p d a ) ;

}

}

Д а н н ы й фрагмент кода гласит, что функция R e c o r d S h o p p i n g L i s t () может при нимать в качестве аргумента любой объект, реализующий метод T a k e A N o t e () —гово ря человеческим языком, любой объект, который в состоянии записать памятку. Функ ция R e c o r d S h o p p i n g L i s t () не делает никаких предположений о том, какой в точка сти тип имеет r e c o r d i n g O b j e c t . Тот факт, что объект в действительности имеет PDA или E l e c t r o n i c D e v i c e , совершенно не важен, поскольку он в состоянии запи

сать памятку.

 

Это чрезвычайно важное

свойство, так как о н о обеспечивает функции Record

S h o p p i n g L i s t () в ы с о к у ю

степень обобщенности, а следовательно, и повторно

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

314

Часть V. За базовыми класса» Глава 1

Рассматриваемая далее программа S o r t I n t e r f a c e демонстрирует применение описанных сведений на практике.

Для лучшего понимания я должен разбить программу на несколько частей. Так я смо- гу более четко продемонстрировать применение отдельных принципов. Если у меня во­ обще есть принципы ... :) Сейчас главная цель — обеспечить понимание того, как рабо-

тает данная программа.

Создание собственного интерфейса

Интерфейс I D i s p l a y a b l e удовлетворяется л ю б ы м классом, который содержит ме- т о д G e t S t r i n g ( ) (и, само собой, объявляет, что о н реализует I D i s p l a y a b l e ) . G e t -

String ()

возвращает объект

типа s t r i n g , который может быть выведен на экран

(использованием W r i t e L i n e

( ) :

/ / I D i s p l a y a b l e - о б ъ е к т , р е а л и з у ю щ и й м е т о д G e t S t r i n g O

interface

I D i s p l a y a b l e

 

{

/ / В о з в р а щ а е т с о б с т в е н н о е о п и с а н и е s t r i n g G e t S t r i n g O ;

}

Приведенный далее класс S t u d e n t реализует интерфейс I D i s p l a y a b l e :

class S t u d e n t :

I D i s p l a y a b l e

p r i v a t e

s t r i n g

s N a m e ;

p r i v a t e

d o u b l e d G r a d e = 0 . 0 ;

/ / Методы д о с т у п а т о л ь к о д л я ч т е н и я p u b l i c s t r i n g N a m e

{

g e t { r e t u r n s N a m e ; }

}

p u b l i c d o u b l e G r a d e

{

g e t { r e t u r n d G r a d e ; }

}

/ / G e t S t r i n g // и н ф о р м а ц и и

- в о з в р а щ а е т с т р о к о в о е п р е д с т а в л е н и е

ос т у д е н т е

p u b l i c s t r i n g G e t S t r i n g ( ) / / i m p l e m e n t s t h e i n t e r f a c e

{

s t r i n g

s P a d N a m e = N a m e . P a d R i g h t ( 9 ) ;

s t r i n g

s = S t r i n g . F o r m a t (11 {0} : { l : N 0 } " ,

 

s P a d N a m e , G r a d e ) ;

r e t u r n

s ;

[да 14. Интерфейсы и структуры

315

В ы з ов P a d R i g h t () гарантирует, что поле имени будет иметь ширину не менее 9 символов (справа от имени при необходимости будет добавлено необходимое коллче ство пробелов). Это делает вывод на экран более привлекательным (данный вопрос рассматривался в г л а в е 9 , "Работа со строками в С # " ) . { l : N 0 } гласит: выводит час с запятыми (или точками — в зависимости от региональных настроек) через каждые

3цифры . О означает — округлить дробную часть.

Сиспользованием приведенного объявления можно написать следующий фрагмента исходного текста (полностью программа будет приведена позже):

/ / D i s p l a y A r r a y - в ы в о д м а с с и в а о б ъ е к т о в , к о т о р ы е р е а л и з у ю т )

/ / и н т е р ф е й с

I D i s p l a y a b l e

 

p u b l i c s t a t i c

v o i d D i s p l a y A r r a y ( I D i s p l a y a b l e [ ]

d i s p l a y a b l e s ) :

i n t l e n g t h = f o r ( i n t i n d e x

d i s p l a y a b l e s . L e n g t h ;

= 0 ; i n d e x < l e n g t h ; i n d e x + + )

{

I D i s p l a y a b l e

d i s p l a y a b l e =

d i s p l a y a b l e s [ i n d e x ] ;

C o n s o l e . W r i t e L i n e ( " { 0 } " ,

d i s p l a y a b l e . G e t S t r i n g ( )

}

}

Приведенный метод D i s p l a y A r r a y () может вывести и н ф о р м а ц и ю о массиве лю бого типа, л и ш ь б ы его элементы определяли метод G e t S t r i n g ( ) . Вот примере в ы в о д а описанной функции:

H o m e r

:

0

M a r g e

:

8 5

B a r t

:

5 0

L i s a

:

1 0 0

M a g g i e

:

3 0

Предопределенные интерфейсы

Аналогично можно использовать интерфейсы из стандартной библиотеки С#. Напр мер, С # определяет интерфейс I C o m p a r a b l e следующим образом:

interface IComparable

{

/ /

С р а в н и в а е т т е к у щ и й о б ъ е к т с о б ъ е к т о м ' о ' ; в о з в р а щ а е т 1 ,

/ /

е с л и т е к у щ и й о б ъ е к т б о л ь ш е , - 1 , е с л и м е н ь ш е , и 0 в

/ / п р о т и в н о м с л у ч а е

i n t C o m p a r e T o ( o b j e c t о ) ;

}

Класс реализует интерфейс I C o m p a r a b l e путем реализации метода СотрагеТои Например, S t r i n g реализует этот метод путем сравнения двух строк. Если строки им тичны, метод возвращает 0. Если строки различны, метод возвращает либо 1, либо а в зависимости от того, какая из строк "больше" .

Как ни странно, но отношение сравнения можно задать и для объектов та S t u d e n t -— например, по их успеваемости.

Реализация метода C o m p a r e T o () приводит к тому, что объекты могут быть oral тированы . Если один студент " б о л ь ш е " другого, их м о ж н о упорядочить от "меньше! к "большему" . На самом деле в классе A r r a y уже реализован соответствующий метод:!

A r r a y . S o r t ( I C o m p a r a b l e [ ]

o b j e c t s ) ;

316

Часть V. За базовыми класса*

Этот метод сортирует массив объектов,

которые реализуют интерфейс I C o m p a r a -

ble. Не имеет значения,

к какому классу в действительности принадлежат объекты —

пример, это могут быть объекты

S t u d e n t . Класс A r r a y

может сортировать следую-

щую версию S t u d e n t :

 

 

 

 

 

 

 

 

//Student

-

о п и с а н и е

с т у д е н т а

с

и с п о л ь з о в а н и е м и м е н и

и

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

 

 

 

 

 

 

 

 

 

class S t u d e n t :

I C o m p a r a b l e

 

 

 

 

private

d o u b l e

d G r a d e ;

 

 

 

 

 

 

/ / Методы

д о с т у п а

т о л ь к о

д л я ч т е н и я

 

 

public double Grade

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

get { return dGrade; }

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

// CompareTo

- с р а в н е н и е д в у х

с т у д е н т о в ;

с т у д е н т с

л у ч ш е й

// у с п е в а е м о с т ь ю " б о л ь ш е "

 

 

 

 

public

i n t

C o m p a r e T o

( o b j e c t r i g h t O b j e c t )

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

Student

 

l e f t S t u d e n t

 

=

t h i s ;

 

 

 

 

Student

 

r i g h t S t u d e n t

=

( S t u d e n t ) r i g h t O b j e c t ;

 

/ / В о з в р а щ а е м 1 6 - 1 и л и 0 в з а в и с и м о с т и о т в ы п о л н е н и я

// к р и т е р и я с о р т и р о в к и

 

 

 

 

 

i f ( r i g h t S t u d e n t . G r a d e < l e f t S t u d e n t . G r a d e )

 

{

 

 

 

 

 

 

 

 

 

 

 

 

r e t u r n

- 1 ;

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

i f ( r i g h t S t u d e n t . G r a d e > l e f t S t u d e n t . G r a d e )

 

{

 

 

 

 

 

 

 

 

 

 

 

 

r e t u r n

1 ;

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

r e t u r n

0 ;

 

 

 

 

 

 

 

 

 

Сортировка массива объектов

S t u d e n t

сводится к единственному вызову:

i d M y F u n c t i o n ( S t u d e n t [ ] s t u d e n t s )

 

 

/ / С о р т и р о в к а м а с с и в а о б ъ е к т о в I C o m p a r a b l e A r r a y . S o r t ( s t u d e n t s ) ;

Ваше дело — обеспечить компаратор; A r r a y сделает все остальное сам.

Сборка воедино

И вот наступил долгожданный момент: полная программа S o r t I n t e r f a c e , использующая описанные ранее возможности.

[ S o r t l n t e r f а с е и л ю с т р и р у е т using S y s t e m ;

- д е м о н с т р а ц и о н н а я п р о г р а м м а S o r t l n t e r f а с е к о н ц е п ц и ю и н т е р ф е й с а

ш 14. Интерфейсы и структуры

317

n a m e s p a c e

S o r t l n t e r f а с е

{

 

/ / I D i s p l a y a b l e - О б ъ е к т , к о т о р ы й

/ / и н ф о р м а ц и ю о с е б е в

с т р о к о в о м

i n t e r f a c e

I D i s p l a y a b l e

 

{

 

 

мо ж е т п р е д с т а в и т ь

фо р м а т е

/ / G e t S t r i n g

-

в о з в р а т с т р о к и ,

п р е д с т а в л я ю щ е й

информации

/ / о б о б ъ е к т е

 

 

 

 

s t r i n g G e t S t r i n g O ;

 

 

}

 

 

 

 

 

 

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 i n ( s t r i n g [ ] a r g s )

 

{

 

 

 

 

 

 

/ / С о р т и р о в к а с т у д е н т о в п о у с п е в а е м о с т и . . .

 

C o n s o l e . W r i t e L i n e ( " С о р т и р о в к а

с п и с к а с т у д е н т о в " ) ;

/ / П о л у ч а е м н е с о р т и р о в а н н ы й с п и с о к с т у д е н т о в

 

S t u d e n t [ ]

s t u d e n t s = S t u d e n t . C r e a t e S t u d e n t L i s t ( ) ;

/ / И с п о л ь з у е м и н т е р ф е й с I C o m p a r a b l e д л я с о р т и р о в к и

/ / м а с с и в а

 

 

 

 

 

I C o m p a r a b l e [ ]

c o m p a r a b l e O b j e c t s =

 

 

 

 

 

 

( I C o m p a r a b l e [ ] ) s t u d e n t s ;

A r r a y . S o r t ( c o m p a r a b l e O b j e c t s ) ;

 

/ / Т е п е р ь и н т е р ф е й с I D i s p l a y a b l e в ы в о д и т р е з у л ь т а т

I D i s p l a y a b l e [ ]

d i s p l a y a b l e O b j e c t s =

 

 

 

 

 

 

( I D i s p l a y a b l e [ ] ) s t u d e n t s ;

D i s p l a y A r r a y ( d i s p l a y a b l e O b j e c t s ) ;

 

/ / Т е п е р ь о т с о р т и р у е м м а с с и в п т и ц п о и м е н и

с

/ / и с п о л ь з о в а н и е м т о й ж е п р о ц е д у р ы , х о т я к л а с с ы B i r d

/ / и S t u d e n t н е и м е ю т о б щ е г о б а з о в о г о к л а с с а

 

C o n s o l e . W r i t e L i n e ( " \ п С о р т и р о в к а с п и с к а п т и ц " ) ;

B i r d [ ]

b i r d s

= B i r d . C r e a t e B i r d L i s t ( ) ;

 

/ / О б р а т и т е в н и м а н и е н а о т с у т с т в и е н е о б х о д и м о с т и

/ / я в н о г о п р е о б р а з о в а н и я т и п а о б ъ е к т о в . . .

 

A r r a y . S o r t ( b i r d s ) ;

 

 

D i s p l a y A r r a y ( b i r d s ) ;

 

 

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

 

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 ( ) ;

 

 

}

 

 

 

 

 

 

/ / D i s p l a y A r r a y - в ы в о д м а с с и в а о б ъ е к т о в , р е а л и з у ю щ и х

/ / и н т е р ф е й с

 

I D i s p l a y a b l e

 

 

p u b l i c

s t a t i c

v o i d

 

 

318

Часть V. За базовыми класса

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