1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 56
Текст из файла (страница 56)
Это делает определение производными классамисобственных методов более гибким, по-прежнему оставляя в силе требованиесогласующегося интерфейса. Таким образом, сочетая наследование с возможностьюпереопределения (замещения) методов, в базовом классе можно определить общую формуметодов, которые будут использованы производными классами.Применение виртуальных методовЧтобы лучше почувствовать силу виртуальных методов, применим их к классуTwoDShape.
В предыдущих примерах каждый класс, выведенный из класса TwoDShape,определяет метод с именем area(). Это наводит нас на мысль о том, не лучше ли сделатьметод вычисления площади фигуры area() виртуальным в классе 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; }}public 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.
Язык C#}}"Метод area() необходимо переопределить.");return 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;}// Переопределяем метод area() для класса 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.
Наследование307}}// Переопределяем метод area() для класса Rectangle.public override double area() {return width * height;}class DynShapes {public static void Main() {TwoDShape[] shapes = new TwoDShape[5];shapes[0] = new Triangle("прямоугольный", 8.0, 12.0);shapes[1] = new Rectangle(10);shapes[2] = new Rectangle(10, 4);shapes[3] = new Triangle(7.0);shapes[4] = new TwoDShape(10, 20,"заготовка для фигуры");for(int i=0; i < shapes.Length; i++) {Console.WriteLine("Объектом является " +shapes[i].name);Console.WriteLine("Площадь равна " +shapes[i].area());Console.WriteLine();}}}При выполнении программа генерирует следующие результаты:Объектом является треугольникПлощадь равна 48Объектов является прямоугольникПлощадь равна 100Объектом является прямоугольникПлощадь равна 40Объектом является треугольникПлощадь равна 24,5Объектом является заготовка для фигурыМетод area() необходимо переопределить.Площадь равна 0Рассмотрим программу подробнее.
Во-первых, метод area() объявляется в классеTwoDShape с использованием ключевого слова virtual и переопределяется в классахTriangle и Rectangle. В классе TwoDShape метод area() представляет собой своегорода “заглушку”, которая просто информирует пользователя о том, что в производномклассе этот метод необходимо переопределить. Каждое переопределение метода area()реализуетвариантвычисленияплощади,соответствующийтипуобъекта,инкапсулируемому производным классом. Таким образом, если бы вы реализовали классэллипсов, то метод area() в этом классе вычислял бы площадь эллипса.В предыдущей программе проиллюстрирован еще один важный момент. Обратитевнимание на то, что в методе Main() член shapes объявляется как массив объектов308Часть I.
Язык C#типа TwoDShape. Однако элементам этого массива присваиваются ссылки на объектыклассов Triangle, Rectangle и TwoDShape. Это вполне допустимо, поскольку ссылкана базовый класс может указывать на объект производного класса. Затем программа в циклеопрашивает массив shapes, отображая информацию о каждом объекте.
Несмотря напростоту, этот цикл иллюстрирует силу как наследования, так и переопределения методов.Конкретный тип объекта, хранимый в ссылочной переменной базового класса, определяетсяво время выполнения программы, что позволяет принять соответствующие меры, т.е.выполнить действия, соответствующие объекту данного типа. Если объект выведен изкласса TwoDShape, его площадь можно узнать посредством вызова метода area().Интерфейс для выполнения этой операции одинаков для всех производных классов,независимо от типа используемой фигуры.Использование абстрактных классовИногда полезно создать базовый класс, определяющий только своего рода “пустойбланк”, который унаследуют все производные классы, причем каждый из них заполнит этот“бланк” собственной информацией.
Такой класс определяет “суть” методов, которыепроизводные классы должны реализовать, но сам при этом не обеспечивает реализацииодного или нескольких методов. Подобная ситуация может возникнуть, когда базовыйкласс попросту не в состоянии реализовать метод.
Этот случай был проиллюстрированверсией класса TwoDShape (из предыдущей программы), в которой определение методаarea() представляло собой “заглушку”, поскольку в нем площадь фигуры не вычисляласьи, естественно, не отображалась.В будущем, создавая собственные библиотеки классов, вы убедитесь, что отсутствиеу метода четкого определения в контексте своего (базового) класса, не является чем-тонеобычным.
Описанную ситуацию можно обработать двумя способами. Один из них,который продемонстрирован в предыдущем примере, — вывод предупреждающегосообщения. И хотя такой подход может быть полезным в определенных обстоятельствах(например, при отладке программы), все же он не соответствует уровнюпрофессионального программирования. Существует и другой способ. Наша цель —заставить производные классы переопределить методы, которые в базовом классе не имеютникакого смысла. Рассмотрим класс Triangle. Им нельзя пользоваться, если не определенметод area(). Необходимо иметь средство, благодаря которому производный классобязательно переопределит все необходимые методы. Этим средством в C# являетсяабстрактный метод.Абстрактный метод создается с помощью модификатора типа abstract.Абстрактный метод не содержит тела и, следовательно, не реализуется базовым классом.Поэтому производный класс должен его переопределить, поскольку он не можетиспользовать версию, предложенную в базовом классе.
Нетрудно догадаться, чтоабстрактный метод автоматически является виртуальным, поэтому и нет необходимости виспользовании модификатора virtual. Более того, совместное использованиемодификаторов virtual и abstract считается ошибкой.Для объявления абстрактного метода используйте следующий формат записи.abstract тип имя(список_параметров);Как видите, тело абстрактного метода отсутствует.
Модификатор abstract можноиспользовать только применительно к обычным, а не к static-методам. Свойства такжемогут быть абстрактными.Класс, содержащий один или несколько абстрактных методов, также должен бытьобъявлен как абстрактный с помощью спецификатора abstract, который ставится передобъявлением class. Поскольку абстрактный класс нереализуем в полном объеме,Глава 11. Наследование309невозможно создать его экземпляры, или объекты.
Таким образом, попытка создать объектабстрактного класса с помощью оператора new приведет к возникновению ошибки временикомпиляции.Если производный класс выводится из абстрактного, он может реализовать всеабстрактные методы базового класса. В противном случае такой производный класс такжедолжен быть определен как абстрактный.
Таким образом, атрибут abstract наследуетсядо тех пор, пока реализация класса не будет полностью достигнута.Используя абстрактный класс, можно усовершенствовать определение классаTwoDShape. Поскольку для не определенной заранее двумерной фигуры понятие площадине имеет смысла, в следующей версии предыдущей программы метод area() в классеTwoDShape объявляется как абстрактный, как, впрочем, и сам класс TwoDShape.Безусловно, это означает, что все классы, выведенные из TwoDShape, должныпереопределить метод area().// Создание абстрактного класса.using System;abstract 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; }}public double height {get { return pri_height; }set { pri_height = value; }310Часть I.