Г. Шилдт - Полный справочник по C# (1160789), страница 57
Текст из файла (страница 57)
Наследование307// Переопределяем метод агеа() для класса Rectangle,public override double area() {return width * height;c l a s s DynShapes {p u b l i c s t a t i c void Main() {TwoDShape[] shapes = new TwoDShape[5];shapes[0]shapes[1]shapes[2]shapes[3]shapes[4]=====newnewnewnewnewTriangle("прямоугольный", 8.0, 12.0)Rectangle(10);Rectangle(10, 4 ) ;Triangle(7.0);TwoDShape(10, 20,"заготовка для фигуры");f o r ( i n t i = 0 ; i < shapes.Length; i++) {Console.WriteLine("Объектом является " +shapes[i].name);Console.WriteLine("Площадь равна " +shapes[i].area() ) ;Console.WriteLine();Ййпблй%нии программа Генерирует следующие результаты:Объектом является треугольникПлощадь равна 48Объектом является прямоугольникПлощадь равна 100Объектом является прямоугольникПлощадь равна 4 0Объектом является треугольникПлощадь равна 24.5Объектом является заготовка для фигурыМетод area() необходимо переопределить.Площадь равна 0Рассмотрим программу подробнее.
Во-первых, метод агеа() объявляется в классеTwoDShape с использованием ключевого слова v i r t u a l и переопределяется в классахT r i a n g l e и Rectangle. В классе TwoDShape метод агеа() представляет собой своегорода "заглушку", которая просто информирует пользователя о том, что в производномклассе этот метод необходимо переопределить. Каждое переопределение методаa r e a () реализует вариант вычисления площади, соответствующий типу объекта, инкапсулируемому производным классом.
Таким образом, если бы вы реализовали классэллипсов, то метод area () в этом классе вычислял бы площадь эллипса.В предыдущей программе проиллюстрирован еще один важный момент. Обратитевнимание на то, что в методе М а т ( ) член shapes объявляется как массив объектов308Часть I.
Язык С#типа TwoDShape. Однако элементам этого массива присваиваются ссылки на объектыклассов T r i a n g l e , Rectangle и TwoDShape. Это вполне допустимо, поскольку ссылкана базовый класс может указывать на объект производного класса. Затем программа вцикле опрашивает массив shapes, отображая информацию о каждом объекте.
Несмотря на простоту, этот цикл иллюстрирует силу как наследования, так и переопределения методов. Конкретный тип объекта, хранимый в ссылочной переменной базового класса, определяется во время выполнения программы, что позволяет принятьсоответствующие меры, т.е. выполнить действия, соответствующие объекту данноготипа. Если объект выведен из класса TwoDShape, его площадь можно узнать посредством вызова метода a r e a (). Интерфейс для выполнения этой операции одинаков длявсех производных классов, независимо от типа используемой фигуры.Использование абстрактных классовИногда полезно создать базовый класс, определяющий только своего рода "пустойбланк", который унаследуют все производные классы, причем каждый из них заполнит этот "бланк" собственной информацией.
Такой класс определяет "суть" методов,которые производные классы должны реализовать, но сам при этом не обеспечиваетреализации одного или нескольких методов. Подобная ситуация может возникнуть,когда базовый класс попросту не в состоянии реализовать метод. Этот случай былпроиллюстрирован версией класса TwoDShape (из предыдущей программы), в которойопределение метода агеа() представляло собой "заглушку", поскольку в нем площадь фигуры не вычислялась и, естественно, не отображалась.В будущем, создавая собственные библиотеки классов, вы убедитесь, что отсутствие у метода четкого определения в контексте своего (базового) класса, не являетсячем-то необычным.
Описанную ситуацию можно обработать двумя способами. Одиниз них, который продемонстрирован в предыдущем примере, — вывод предупреждающего сообщения. И хотя такой подход может быть полезным в определенных обстоятельствах (например, при отладке программы), все же он не соответствует уровнюпрофессионального программирования.
Существует и другой способ. Наша цель —заставить производные классы переопределить методы, которые в базовом классе неимеют никакого смысла. Рассмотрим класс T r i a n g l e . Им нельзя пользоваться, еслине определен метод a r e a (). Необходимо иметь средство, благодаря которому производный класс обязательно переопределит все необходимые методы. Этим средством вС# является абстрактный метод.Абстрактный метод создается с помощью модификатора типа a b s t r a c t . Абстрактный метод не содержит тела и, следовательно, не реализуется базовым классом.
Поэтому производный класс должен его переопределить, поскольку он не может использовать версию, предложенную в базовом классе. Нетрудно догадаться, что абстрактный метод автоматически является виртуальным, поэтому и нет необходимости виспользовании модификатора v i r t u a l . Более того, совместное использование модификаторов v i r t u a l и a b s t r a c t считается ошибкой.Для объявления абстрактного метода используйте следующий формат записи.abstract ТИП ИМЯ(список_параметров) ;Как видите, тело абстрактного метода отсутствует. Модификатор a b s t r a c t можноиспользовать только применительно к обычным, а не к static-методам.
Свойстватакже могут быть абстрактными.Класс, содержащий один или несколько абстрактных методов, также должен бытьобъявлен как абстрактный с помощью спецификатора a b s t r a c t , который ставитсяперед объявлением c l a s s . Поскольку абстрактный класс нереализуем в полном объеГлава 11. Наследование309ме, невозможно создать его экземпляры, или объекты. Таким образом, попытка создать объект абстрактного класса с помощью оператора new приведет к возникновениюошибки времени компиляции.Если производный класс выводится из абстрактного, он может реализовать все абстрактные методы базового класса.
В противном случае такой производный класстакже должен быть определен как абстрактный. Таким образом, атрибут a b s t r a c t наследуется до тех пор, пока реализация класса не будет полностью достигнута.Используя абстрактный класс, можно усовершенствовать определение классаTwoDShape. Поскольку для не определенной заранее двумерной фигуры понятиеплощади не имеет смысла, в следующей версии предыдущей программы методa r e a () в классе TwoDShape объявляется как абстрактный, как, впрочем, и сам классTwoDShape.
Безусловно, это означает, что все классы, выведенные из TwoDShape,должны переопределить метод a r e a ().// Создание абстрактного класса.using System;abstractdoubledoublestringclass TwoDShape {pri_width; // Закрытый член,pri_height; // Закрытый член,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; }}public double height {get { return pri_height; }set { pri_height = value; }310Часть I.
Язык С#public string name {get { return pri__name; }set { pri_name = value; }}public void showDim() {Console.WriteLine("Ширина и высота равны " +width + " и " + height);}// Теперь метод агеа() абстрактный,public abstract double area();}// Класс треугольников, производный ачг класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Конструктор по умолчанию,public Triangle() {style = "null";}// Конструктор с параметрами.public Triangle(string s, double w, double h) :base(w, h, "triangle") {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 f double h) :base(w, h, "прямоугольник"){ }// Создаем квадрат.Глава 11.
Наследование311public Rectangle (ckmfcrle x) :base(xf "прямоугольник") { }// Создаем объект из объекта.public Rectangle(Rectangle ob) : base(ob) { }// Метод возвращает значение true, если// прямоугольник является квадратом,public bool isSquareO {if (width ===== height) return true;return false;// Переопределяем метод агеа() для класса Rectangle,public override double area() {return width * height;class AbsShape {public static void Main() {TwoDShape [] shapes == new TwoDShape[4];shapes[0]shapes[1]shapes[2]shapes[3]====newnewnewnewTriangle("прямоугольный", 8.0, 12.0);Rectangle(10);Rectangle(10, 4 ) ;Triangle (7.0);for(int i=0; i < shapes.Length; i++) {Console.WriteLine("Объектом является " +shapes[i].name);Console.WriteLine("Площадь равна " +shapes[i 3.area());Console.WriteLine();Как продемонстрировано этой программой, все производные классы должны илипереопределить метод a r e a ( ) , или также объявить себя абстрактными.
Чтобы убедиться в этом, попробуйте создать производный класс, который не переопределяетметод a r e a (). Вы тут же (т.е. во время компиляции) получите сообщение об ошибке.Конечно, мы можем создать объектную ссылку типа TwoDShape, что и делается впрограмме. Однако теперь нельзя объявить объект типа TwoDShape. Поэтому в методеMain () размер массива shapes сокращен до 4, и больше не создается "заготовка дляфигуры" в виде объекта класса TwoDShape.Обратите также внимание на то, что класс TwoDShape по-прежнему включает метод showDimO, объявления которого не коснулся модификатор a b s t r a c t . Ведь вполне допустимо для абстрактного класса содержать конкретные (а не только абстрактные) методы, которые производный класс может использовать "как есть".
И толькометоды, объявленные с использованием ключевого слова a b s t r a c t , должны переопределяться производными классами.312.Часть I. Язык С#Использование ключевого слова sealedдля предотвращениянаследованияКаким бы мощным и полезным ни был механизм наследования, все же иногда необходимо его отключать. Например, у вас может быть класс, который инкапсулируетпоследовательность действий при инициализации такого специализированного устройства, как медицинский монитор. В этом случае необходимо запретить пользователям изменять характер инициализации этого монитора, чтобы исключить возможнуюнекорректность этой процедуры.