Г. Шилдт - Полный справочник по C# (1160789), страница 56
Текст из файла (страница 56)
При переопределении виртуального метода впроизводном классе используется модификатор o v e r r i d e . Итак, процесс переопределения виртуального метода в производном классе иногда называется замещением метода (method overriding). При переопределении метода сигнатуры типа у виртуальногои метода-заменителя должны совпадать. Кроме того, виртуальный метод нельзя определять как статический (с использованием слова s t a t i c ) или абстрактный (с использованием слова a b s t r a c t , о котором пойдет речь ниже в этой главе).Переопределение виртуального метода формирует базу для одной из самых мощных концепций С#: динамической диспетчеризации методов. Динамическая диспетчеризация методов — это механизм вызова переопределенного метода во время выполнения программы, а не в период компиляции. Именно благодаря механизму диспетчеризации методов в С# реализуется динамический полиморфизм.Рассмотрим пример, который иллюстрирует виртуальные методы и их переопределение.// Демонстрация виртуального метода.using System;iclass Base {// Создаем виртуальный метод в базовом классе,public virtual void who () {Console.WriteLine("Метод who() в классе Base.");class Derivedl : Base {// Переопределяем метод who() в производном классе,public override void who() {Console.WriteLine("Метод who() в классе Derivedl")class Derived2 : Base {// Снова переопределяем метод who()// в другом производном классе,public override void who() {Console.WriteLine("Метод who() в классе Derived2class OverrideDemo {public static void Main() {Base baseOb = new Base();Derivedl dObl = new Derivedl()Derived2 dOb2 = new Derived2()302Часть I.
Язык С #Base baseRef; // Ссылка на базовый класс.baseRef = baseOb;baseRef.who();baseRef = dObl;baseRef.who() ;чbaseRef = dOb2;baseRef.who() ;IВот результатыМетод who() вМетод who() вМетод who() ввыполнения этой программы:классе Base.классе Derivedlклассе Derived2В программе создается базовый класс Base и два производных класса — Derivedlи Derived2. В классе Base объявляется метод с именем who (), а производные классыего переопределяют. В методе Main() объявляются объекты типа Base, Derivedl иDerived2, а также ссылка baseRef типа Base.
Затем программа поочередно присваивает ссылку на объект каждого типа ссылке baseRef и использует эту ссылку для вызова метода who (). Как показывают результаты выполнения этой программы, нужнаядля выполнения версия определяется типом объекта, адресуемого в момент вызова, ане "классовым" типом ссылки baseRef.Виртуальный метод переопределять необязательно. Если производный класс непредоставляет собственную версию виртуального метода, используется версия, определенная в базовом классе. Вот пример:/* Если виртуальный метод не переопределенв производном классе, используется методбазового класса. Л /using System;class Base {// Создаем виртуальный метод в базовом классе,public virtual void who() {Console.WriteLine("Метод who() в классе Base");class Derivedl : Base {// Переопределяем метод who() в производном классе,public override void who() {Console.WriteLine("Метод who() в классе Derivedl");class Derived2 : Base {// Этот класс не переопределяет метод who().}class NoOverrideDemo {public static void Main() {Base baseOb = new Base();Derivedl dObl = new Derivedl();Глава 11.
Наследование303Derived2 dOb2 = new Derived2();Base baseRef; // Ссылка на базовый класс.baseRef = baseOb;baseRef.who();,baseRef = dObl;baseRef.who();baseRef = dOb2;baseRef.who(); // Вызывает метод who() класса Base.Вот результатыМетод who() вМетод who() вМетод who() ввыполнения этой программы:классе Baseклассе Derivedlклассе BaseЗдесь класс Derived2 не переопределяет метод who ().
Поэтому при вызове методаwho () для объекта класса Derived2 выполняется метод who (), определенный в классе Base.Если производный класс не переопределяет виртуальный метод в случае многоуровневой иерархии, то будет выполнен первый переопределенный метод, которыйобнаружится при просмотре иерархической лестницы в направлении снизу вверх.Рассмотрим пример./* Если производный класс не переопределяет виртуальныйметод в случае многоуровневой иерархии, будет выполненпервый переопределенный метод, который обнаружитсяпри просмотре иерархической лестницы в направленииснизу вверх. */using System;class Base {// Создаем виртуальный метод в базовом классе,public virtual void who() {Console.WriteLine("Метод who() в классе Base");class Derivedl : Base {// Переопределяем метод who() в производном классе,public override void who() {Console.WriteLine("Метод who() в классе Derivedl");class Derived2 : Derivedl {// Этот класс не переопределяет метод who().}class Derived3 : Derived2 {// Этот класс также не переопределяет метод who().}class No0verrideDemo2 {304Часть I.
Язык С#public static void Main{) {Derived3 dOb = new Derived3();Base baseRef; // Ссылка на базовый класс.baseRef = dOb;baseRef.who(); // Вызывает метод who()// из класса Derivedl.Результаты выполнения этой программы таковы:I Метод who() в классе DerivedlЗдесь класс Derived3 наследует класс Derived2, который наследует классDerivedl, который в свою очередь наследует класс Base. Как подтверждают результаты выполнения этой программы, поскольку метод who () не переопределяется ни вклассе Derived3, ни в классе Derived2, но переопределяется в классе Derivedl, тоименно эта версия метода who () (из класса Derivedl) и выполняется, так как онаявляется первой обнаруженной в иерархии классов.Еще одно замечание.
Свойства также можно модифицировать с помощью ключевого слова v i r t u a l , а затем переопределять с помощью ключевого слова o v e r r i d e .Зачем переопределять методыПереопределение методов позволяет С# поддерживать динамический полиморфизм. Без полиморфизма объектно-ориентированное программирование невозможно,поскольку он позволяет исходному классу определять общие методы, которыми будутпользоваться все производные классы, и в которых $ри этом можно будет задать собственную реализацию некоторых или всех этих методов. Переопределенные методыпредставляют собой еще один способ реализации в С# аспекта полиморфизма, который можно выразить как "один интерфейс — много методов".Ключ (вернее, его первый "поворот") к успешному применению полиморфизмалежит в понимании того, что базовые и производные классы образуют иерархию, которая развивается в сторону более узкой специализации.
При корректном использовании базовый класс предоставляет производному классу все элементы "пригоднымик употреблению", т.е. для прямого их использования. Кроме того, он определяет методы, которые производный класс должен реализовать самостоятельно. Это делает определение производными классами собственных методов более гибким, по-прежнемуоставляя в силе требование согласующегося интерфейса. Таким образом, сочетая наследование с возможностью переопределения (замещения) методов, в базовом классеможно определить общую форму методов, которые будут использованы производными классами.Применение виртуальных методовЧтобы лучше почувствовать силу виртуальных методов, применим их к классуTwoDShape. В предыдущих примерах каждый класс, выведенный из классаTwoDShape, определяет метод с именем a r e a ().
Это наводит нас на мысль о том, нелучше ли сделать метод вычисления площади фигуры а г е а ( ) виртуальным в классеTwoDShape, получив возможность переопределить его в производных классах такимобразом, чтобы он вычислял площадь согласно типу конкретной геометрической фигуры, которую инкапсулирует класс. Эта мысль и реализована в следующей программе.
Для удобства в класс TwoDShape вводится свойство name, которое упрощает демонстрацию этих классов.Глава 11. Наследование305// Использование виртуальных методов и полиморфизма,using System;class TwoDShape {double pri_width; // Закрытый член,double pri_height; // Закрытый член,string pri_name;// Закрытый член.// Конструктор по умолчанию,public TwoDShape() {width = height = 0.0;name = "null";\// Конструктор с параметрами.public TwoDShape(double w, double h, string n)width = w;height = h;name = n;// Создаем объект, у которого ширина равна высоте,public TwoDShape(double x, string n) {width = height = x;name = n;// Создаем объект из объекта,public TwoDShape(TwoDShape ob) {width = ob.width;height = ob.height;name = ob.name;}// Свойства width, height и name.public double width {get { return pri_width; }set { pri_width = value; }}spublic double height {get { return pri_height; }set { pri_height = value; }}public string name {get { return pri__name; }set { pri__name = value; }}public void showDim() {\Console.WriteLine("Ширина и высота равны " +width + " и " + height);}public virtual double area() {Console.WriteLine(306Часть I.
Язык С#return"Метод a r e a ( ) необходимо переопределить. " ) ;0.0;// Класс треугольников, производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Конструктор по умолчанию,public Triangle() {style = "null";}// Конструктор с параметрами.public Triangle(string s, double w, double h) :base(w, h, "треугольник") {style = s;}// Создаем равнобедренный треугольник.public Triangle(double x) : base(x, "треугольник") {'style - "равнобедренный";}// Создаем объект из объекта.public Triangle(Triangle ob) : base(ob) {style = ob.style;}// Переопределяем метод агеа() для класса Triangle,public override double area() {return width * height / 2 ;}// Метод отображает тип треугольника,public void showStyle() {Console.WriteLine("Треугольник " + style);}}// Класс прямоугольников, производный от класса TwoDShape.class Rectangle : TwoDShape {// Конструктор с параметрами.public Rectangle(double w, double h) :base(w, h, "прямоугольник"){ }// Создаем квадрат,public Rectangle(double x) :base(x, "прямоугольник") { }// Создаем объект из объекта.public Rectangle(Rectangle ob) : base(ob) { }// Метод возвращает true, если прямоугольник - квадрат,public bool isSquare() {if(width — height) return true;return false;Глава 11.