Часть 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 |
{ |
|
|
|
|
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 |