Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 66
Текст из файла (страница 66)
Как пояснялось выше, такое вполне допустимо, поскольку по ссылке на объект базового класса можно обращаться к объекту производного класса. Следовательно, конструктору Тыо()зпаре ( ) можно на совершенно законных основаниях передать ссылку на объект класса, производного от класса Тыорзпаре. А поскольку конструктор тыопзпаре () инициализирует только те части объекта производного класса, которые являются членами класса Тыо()зпаре, то для него не имеет никакого значения, содержит ли этот объект другие члены, добавленные в производном классе. Виртуальные методы и их переопределение Виртуальным называется такой метод, который объявляется как ч1гспа1 в базовом классе.
Виртуальный метод отличается тем, что он может быть переопределен в одном или более производном классе. Следовательно, у каждого производного класса может быть свой вариант виртуального метода. Кроме того, виртуальные методы интересны тем, что именно происходит при их вызове по ссылке на базовый класс.
В этом случае средствами языка С№ определяется именно тот вариант виртуального метода, который следует вызывать, исходя из типа объекта, к которому происходит обращение по ссылке, причем это делается во время выполнения. Поэтому при ссылке на разные типы объектов выполняются разные варианты виртуального метода. Иными словами, вариант выполняемого виртуального метода выбирается по типу объекта, а не по типу ссылки на этот объект.
Так, если базовый класс содержит виртуальный метод и от него получены производные классы, то при обращении к разным типам объектов по ссылке на базовый класс выполняются разные варианты этого виртуального метода. Метод объявляется как виртуальный в базовом классе с помощью ключевого слова уъгспа1, указываемого перед его именем.
Когда же виртуальный метод переопределяется в производном классе, то для этого используется модификатор оуегг1г(е. А сам процесс повторного определения виртуального метода в производном классе называется переопределением метода. При переопределении имя, возвращаемый тип и сигнатура переопределяющего метода должны быть точно такими же, как и у того виртуального метода, который переопределяется. Кроме того, виртуальный метод не может быть объявлен как эсас1с или аЬэггасг (подробнее данный вопрос рассматривается далее в этой главе).
Переопределение метода служит основанием для воплощения одного из самых эффективных в С№ принципов: динамической диспвтчвризак(ии методов, которая представляет собой механизм разрешения вызова во время выполнения, а не компиляции. Значение динамической диспетчеризации методов состоит в том, что именно благодаря ей в С№ реализуется динамический полиморфизм. Ниже приведен пример, демонстрирующий виртуальные методы и нх переопределение. // Продемонстрировать виртуальный метод. О51ПЧ зувсепг Глава 11, Наследование 343 с1аяя Вазе ( // Создать виртуальный метод в базовом классе.
рпЫас ч1гяпа1 чоьб ИЬо() Сопяо1е.нг1Сеъапе("Метод ХЬо() в классе Вазе" ) ) ) с1аяя Оег1чеб1 : Вазе ( О Переопределить метод ИЬо() в производном классе. рпЬ11с очеггабе чопб Ипо() ( Сопзо1е.хгьсе).ьпе("Метод Ипо() е классе Оег1чеб1") ) ) с1аяя Оег1чеб2 : Вазе ( // Вновь переопределить метод Хпо() з еще одном // производном классе. риЫас очегг1бе чоаб Ипо() ( Сопяо1е.игасеь1пе("Метод Ипо() з классе Оег1чеб2") ) с1аяз Очегг1бепещо '( язадас чо1б Маьп В ( Вазе ЬаяеОЬ = пен Вазе()г Оегьчед1 бОЫ = пен Оетачебт() Оегачеб2 бОЬ2 = пен Оегачеб2() Вазе Ьаяеает; // ссылка на базовый класс Ьаяепет = ЬазеОЬ; Ьаяеает.нпо(); Ьаяекет = бОЫ; Ьаяекег.иьо(); Ьаяекет = сЮЬ2; ьаяекет.иьо()) ) Вот к какому результату приводит выполнение этого кода: Метод ИЬа() з классе Вазе.
Метод ХЬо() в классе Оегачеб1 Метод ИЬо() в классе Оегачеб2 В коде из приведенного выше примера создаются базовый класс Вазе и два производных от него класса — Оет1чес)1 и Оет1чеб2. В классе Вазе объявляется виртуальный метод ХЬО (), который переопределяется в обоих производных классах. Затем в методе Маап () объявляются объекты типа Ваяе, Оег1чеб1 и Оег1чес(2. Кроме того, объявляется переменная Ьаяеие1 ссылочного типа Вазе. Далее ссылка иа каждый тип объекта присваивается переменной Ьаяенег и затем используется для вызова метода ИЬО () .
Как 344 часть (. язык Сз следует из результата выполнения приведенного выше кода, вариант выполняемого метода иьо () определяется по типу объекта, к которому происходит обращение по ссылке во время вызова этого метода, а не по типу класса переменной Ьавепег. Но переопределять виртуальный метод совсем не обязательно. Ведь если в производном классе не предоставляется собственный вариант виртуального метода, то используется его вариант из базового класса, как в приведенном ниже примере. /* Если виртуальный метод не переопределяется, то используется его вариант из базового класса. */ пвтпд Зувсеи) с1авв Вазе ( // Создать виртуальный метод в базовом классе.
риЫгс чггсса1 чо16 Ино() ( Сопво1е.иггсеьнпе (" Метод Ипо() в классе Вазе") ) с1авв Оегкче61: Вазе ( // Переопределить метод ИЬо() в производном классе. риЪ11с очеггьбе чо16 ИЬоы ( Сопво1е.нг1сеъ1пе("Метод Ипо() в классе Оегтче61")1 с1авв Оег1че62: Вазе ( // В этом классе метод Ипо() не переопределяется.
с1авв Ноочегггнеоеио ( всастс чо16 Магп() ( Вазе ЬавеОЬ = пеы Вазе()' Оеггче61 6ОЫ = пеы Оег1чебт(); Оеггче62 6ОЬ2 = пеы Оеггче62(); Вазе Ьавенет) // ссылка на базовый класс Ьавепег = ЬавеОЬ; Ьавенет.ипо()) Ьавенет = 6ОЫ; Ьавекет.ипо(); Ьавепег = 6ОЬ2; Ьавенег.ипо(); // вызывается метод Ипо() // из класса Вазе Выполнение этого кода приводит к следующему результату: Глава )), Наследование 345 Метод ИЬо() в классе Вазе. Метод Ипо() в классе Оегъчеб1 Метод Ипо() в классе Ваяе В данном примере метод ИЬС () не переопределяется в классе Оегвчеб2. Поэтому для объекта класса Оеглчеб2 вызывается метод иьо () из класса Вазе.
Если при наличии многоуровневой иерархии виртуальный метод не переопределяется в производном классе, то выполняется ближайший его вариант, обнаруживаемый вверх по иерархии, как в приведенном ниже примере. /* В многоуровневой иерархии классов выполняется тот переопределенный вариант виртуального метода, который обнаруживается первым при продвижении вверх по иерархии. */ ия1пд Яуягели с1аяя Ваяе ( // Создать виртуальный метод в базовом классе.
рпв11с чагспа1 чо1б ИЬо() ( Сопяо1е.Игтсеввпе("Метод ИЬо() в классе Вазе"]; ) с1аяя Оеггчеб1: Вазе ( // Переопределить метод ИЬо() в производном классе. рпЬ11с очегггбе чо1б ИЬо() [ сопяо1е.игагеьвпе("метод иво() в классе Оеггчеб1") ) ) с1аяв Оегячеб2: Оег1чеб1 ( // В этом классе метод ИЬо() не переопределяется. ) с1аяз Оеггчебз: Оег1че62 ( // И в этом классе метод ИЬо() не переопределяется.
с1аяя Ноочеггтбеоезо2 ( яса11с чогб Ма1п() ( Оег1чебз бОЬ = пен Оег1чебз()) Вазе Ьаяеаег> // ссылка на базовый класс Ьаяенег = бОЬ; Ьазеаег.нпо(); // вызов метода ИЬо() из класса Оегачеб1 ) Вот к какому результату приводит выполнение этого кода; Метод Ипо() в классе Оегтчеб1 346 Часть!. Язык С№ В данном примере класс Рег1чек(З наследует класс РегфчеП2, который наследует класс Рег1чег)1, а тот, в свою очередь, — класс Вазе. Как показывает приведенный выше результат, выполняется метод ИЬо (), переопределяемый в классе Рег1чек)1, поскольку зто первый вариант виртуального метода, обнаруживаемый при продвижении вверх по иерархии от классов Рег1чег(З и Рег1чег(2, где метод ИЬо () не переопределяется, к классу Рег1чег(1. И еше одно замечание; свойства также подлежат модификации ключевым словом чфггпа1 и переопределению ключевым словом очегг14е.
Это же относится и к индексаторам. Что дает переопределение методов Благодаря переопределению методов в С№ поддерживается динамический полиморфизм. В объектно-ориентированном программировании полиморфизм играет очень важную роль, потому что он позволяет определить в общем классе методы, которые становятся общими для всех производных от него классов, а в производных классах — определить конкретную реализацию некоторых или же всех этих методов. Переопределение методов — это еще один способ воплотить в С№ главный принцип полиморфизма: один интерфейс — множество методов. Удачное применение полиморфизма отчасти зависит от правильного понимания той особенности, что базовые и производные классы образуют иерархию, которая продвигается от меньшей к большей специализации. При надлежащем применении базовый класс предоставляет все необходимые элементы, которые могут использоваться в производном классе непосредственно.
А с помощью виртуальных методов в базовом классе определяются те методы, которые могут быть самостоятельно реализованы в производном классе. Таким образом, сочетая наследование с виртуальными методами, можно определить в базовом классе общую форму методов, которые будут использоваться во всех его производных классах. Применение виртуальных методов Для того чтобы стали понятнее сильные стороны виртуальных методов, применим их в классе ТноРЗЬаре. В предыдущих примерах в каждом классе, производном от класса ТноРЗЬаре, определялся метод )(геа () . Но, по-видимому, метод )(геа () лучше было бы сделать виртуальным в классе тнопоьаре и тем самым предоставить возможность переопределить его в каждом производном классе с учетом особенностей расчета площади той двумерной формы, которую инкапсулирует этот класс. Именно это и сделано в приведенном ниже примере программы. Ради удобства демонстрации классов в этой программе введено также свойство паве в классе ТноРЗЬаре.