1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 70
Текст из файла (страница 70)
Вот исходный текст метода D e q u e u e ( ) ://Dequeue - и з в л е ч е н и е// п р и о р и т е т о мpublic Т D e q u e u e ()Т изочереди снаивысшим// П р о с м а т р и в а е м о ч е р е д ь с наивысшим п р и о р и т е т о мQueue<T> q u e u e T o p = T o p Q u e u e ( ) ;// Очередь не п у с т аif(queueTop!= n u l l&& q u e u e T o p . C o u n t>0){return queueTop.Dequeue();////Возвращаемэлементпервый/ / Если в с е о ч е р е д и п у с т ы , в о з в р а щ а е м n u l l// можно с г е н е р и р о в а т ь и с к л ю ч е н и е )return d e f a u l t ( Т ) ;(здесь}}Единственная сложность состоит в том, как поступить, если все внутренние очередипусты, т.е.
по сути пуста очередь P r i o r i t y Q u e u e в целом? Что следует вернуть в этомслучае? Представленный метод D e q u e u e () в этом случае возвращает значение n u l l .Таким образом, клиент — код, вызывающий P r i o r i t y Q u e u e . D e q u e u e () — долженпроверять, не вернул ли метод D e q u e u e О значение n u l l . Где именно возвращаетсязначение n u l l ? В d e f a u l t ( Т ) , в конце исходного текста метода. О выражении d e fault (Т) речь пойдет чуть позже.Вспомогательный м е т о д TopQueue()Метод D e q u e u e () использует вспомогательный метод T o p Q u e u e () для того,чтобы найти непустую внутреннюю очередь с наивысшим приоритетом.
МетодTopQueue () начинает с очереди q u e u e H i g h и проверяет ее свойство C o u n t . Если онобольше 0, очередь содержит элементы, так что метод T o p Q u e u e () возвращает ссылкуна эту внутреннюю очередь (тип возвращаемого значения метода T o p Q u e u e () —Queue<T>). Если же очередь q u e u e H i g h пуста, метод T o p Q u e u e () повторяет своидействия с очередями q u e u e M e d i u m и q u e u e L o w .Что происходит, если все внутренние очереди пусты? В этом случае методTopQueue () мог бы вернуть значение n u l l , но более полезным будет возврат одной из пустых очередей.
Когда после этого метод D e q u e u e () вызовет метод Dequeue () возвращенной очереди, тот вернет значение n u l l . Вот как выглядит исходный текст метода T o p Q u e u e ( ) :Глава 15. Обобщенное программирование357/ / T o p Q u e u e - н е п у с т а я о ч е р е д ь с наивысшим п р и о р и т е т о мp r i v a t e Queue<T> T o p Q u e u e ( ){// О ч е р е д ь с в ы с о к и мif ( q u e u e H i g h .
C o u n t > 0)// п р и о р и т е т о м п у с т а ?r e t u r n queueHigh;i f ( q u e u e M e d i u m . C o u n t > 0 ) // О ч е р е д ь с о с р е д н и м// п р и о р и т е т о м п у с т а ?r e t u r n queueMedium;// О ч е р е д ь с н и з к и мif ( q u e u e L o w . C o u n t > 0)// п р и о р и т е т о м п у с т а ?r e t u r n queueLow;// В с е о ч е р е д и п у с т ыr e t u r n queueLow;}Остальные члены PriorityQueueПолезно знать, пуста ли очередь P r i o r i t y Q u e u e или нет, и если нет, то сколькоэлементов в ней содержится (каждый объект отвечает сам за себя!). Вернитесь к листингудемонстрационной программы и рассмотрите исходный текст метода Is E m p t y () и свойства C o u n t класса P r i o r i t y Q u e u e .Незавершенные делаP r i o r i t y Q u e u e все еще нуждается в небольшой доработке.Сам по себе класс P r i o r i t y Q u e u e не защищен от попыток инстанцирования для типов, например, i n t , s t r i n g или S t u d e n t , т.е.
типов, неимеющих приоритетов, Вы должны наложить ограничения на класс с тем,чтобы он мог быть инстанцирован только для типов, реализующих интерфейс I P r i o r i t i z a b l e . Попытки инстанцировать P r i o r i t y Q u e u e даклассов, не реализующих I P r i o r i t i z a b l e , должны приводить к ошибквремени компиляции.Метод D e q u e u e () класса P r i o r i t y Q u e u e возвращает значение nullвместо реального объекта. Однако обобщенные типы наподобие <Т> неимеют естественного значения n u l l по умолчанию, как, например, intили s t r i n g .
Эта часть метода D e q u e u e () также требует обобщения.Добавление ограниченийКласс P r i o r i t y Q u e u e должен быть способен запросить у помещаемого в очередьобъекта о его приоритете. Для этого все классы, объекты которых могут быть размещены в P r i o r i t y Q u e u e , должны реализовывать интерфейс I P r i o r i t i z a b l e , как этоделает класс P a c k a g e .
Класс P a c k a g e указывает интерфейс I P r i o r i t i z a b l e в заголовке своего объявления:classPackage:IPrioritizableпосле чего реализует свойство P r i o r i t y интерфейса I P r i o r i t i z a b l e .Компилятор в любом случае сообщит об ошибке, если один из методов обобщенного класса вызовет метод, отсутствующий у типа, для которого инстанцируетсяобобщенный класс. Однако лучше использовать явные ограничения.
Поскольку выможете инстанцировать обобщенный класс буквально для любого типа, долженбыть способ указать компилятору, какие типы допустимы, а какие — нет.358Часть V. За базовыми классамиВы добавляете ограничение путем указания интерфейса I P r i o r i t i z a b l eв заголовке P r i o r i t y Q u e u e :c l a s s P r i o r i t y Q u e u e < T > where T :IPrioritizableОбратите внимание на выделенную полужирным шрифтом конструкцию, начинающуюся со слова w h e r e . Это принудителъ (enforcer), который указывает, что тип Т обязан реализовывать интерфейс I P r i o r i t i z a b l e , т.е.
как бы говорит компилятору:"Убедись, подставляя конкретный тип вместо Т, что он реализует интерфейс I P r i o r i t i z a b l e , а иначе просто сообщи об ошибке".Вы указываете ограничения, перечисляя в конструкции w h e r e одно или несколько имен:имя базового класса, от которого должен быть порожден класс Т (илидолжен быть этим классом);имя интерфейса, который должен быть реализован классом Т, как былопоказано в предыдущем примере.Дополнительные варианты ограничений включают ключевые слова s t r u c t , c l a s sinew(). С new () вы встретитесь чуть позже в этой главе, а об ограничениях s t r u c tи class можно прочесть в разделе "Generics, constraints" справочной системы.Эти варианты ограничений повышают гибкость в описании поведения обобщенногоиасса.
Вот пример гипотетического обобщенного класса, объявленного с несколькимиограничениями на Т:class MyClass<T>}:whereТ:class,IPrioritizable,new ()Здесь тип T должен быть классом, а не типом-значением; он должен реализоватьинтерфейс I P r i o r i t i z a b l e и содержать конструктор без параметров. ДостаточноА если у вас есть два обобщенных параметра и оба должны иметь ограничения? (Да, да — вы можете использовать несколько обобщенных параметроводновременно!) Вот как можно записать две конструкции w h e r e :c l a s s MyClass<T,U>: where T:IPrioritizable,where U:new()Определение значения null д л я т и п а TКак уже упоминалось ранее, у каждого типа есть свое значение по умолчанию, означающее "ничто" для данного типа. Для i n t и других типов чисел это 0 (или 0.0).
Дляs t r i n g — пустая строка " " . Для b o o l это f a l s e , а для всех ссылочных типов, такихкак Package, это n u l l .Однако поскольку обобщенный класс наподобие P r i o r i t y Q u e u e может быть инпанцирован практически для любого типа данных, С# не в состоянии предсказать, какимдолжно быть правильное значение n u l l в исходном тексте обобщенного класса. Например, в методе D e q u e u e () класса P r i o r i t y Q u e u e вы можете оказаться именно в такойситуации: вы вызываете D e q u e u e ( ) , но очередь пуста и пакетов нет. Что вы должны вернуть, что бы могло означать "ничего"? Поскольку P a c k a g e — класс, следует вернуть значение n u l l .Это сообщит вызывающей функции, что ничего вернуть не удалось(вызывающая функция, само собой, должна проверять, не вернулось ли значение n u l l ) .Глава 15.
Обобщенное программирование359Компилятор не может придать смысл ключевому слову null в исходном тексте обобщенного класса, поскольку обобщенный класс может быть инстанцирован для любых типов данных. Вот почему в исходном тексте метода Deq u e u e () используется следующая конструкция:returndefault(Т);//ЗначениеnullдлятипаТЭта строка указывает компилятору, что нужно посмотреть, что собой представляеттип Т и вернуть верное значение n u l l для этого типа. В случае P a c k a g e , который в качестве класса представляет собой ссылочный тип, верным возвращаемым значением удет n u l l . Однако для некоторых других Т это значение может быть иным, и компиляторсможет верно определить, что именно следует вернуть.Если вы думаете, что обобщенный класс P r i o r i t y Q u e u e достаточно гибок, то на прилагаемом компакт-диске вы можете найти еще более гибкуюверсию обобщенного класса P r i o r i t y Q u e u e и познакомиться с некоторыми принципами объектно-ориентированного дизайна, обратившись к де-|монстрационной программе P r o g r a m m i n g T o A n l n t e r f а с е .Часто методы в обобщенных классах также должны быть обобщенными.