Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

// В классе Circle метод Draw НЕ переопределяется.

class Circle : Shape

{

public Circle {}

public Circle(string name) : base(name){}

}

// В классе Hexagon метод Draw переопределяется.

class Hexagon : Shape

{

public Hexagon {}

public Hexagon(string name) : base(name){}

public override void Draw

{

Console.WriteLine("Drawing {0} the Hexagon", PetName);

}

}

Полезность

абстрактных методов становится совершенно ясной, как только вы снова вспомните, что подклассы никогда не обязаны переопределять виртуальные методы (как в случае
Circle
). Следовательно, если создать экземпляры типов
Hexagon
и
Circle
, то обнаружится, что
Hexagon
знает, как правильно "рисовать" себя (или, по крайней мере, выводить на консоль подходящее сообщение). Тем не менее, реакция
Circle
порядком сбивает с толку.

Console.WriteLine("***** Fun with Polymorphism *****\n");

Hexagon hex = new Hexagon("Beth");

hex.Draw;

Circle cir = new Circle("Cindy");

// Вызывает реализацию базового класса!

cir.Draw;

Console.ReadLine;

Взгляните на вывод предыдущего кода:

***** Fun with Polymorphism *****

Drawing Beth the Hexagon

Inside Shape.Draw

Очевидно, что это не самое разумное проектное решение для текущей иерархии. Чтобы вынудить каждый дочерний класс переопределять метод

Draw
, его можно определить как абстрактный метод класса
Shape
, т.е. какая-либо стандартная реализация вообще не предлагается. Для пометки метода как абстрактного в C# используется ключевое слово
abstract
. Обратите внимание, что абстрактные методы не предоставляют никакой реализации:

abstract class Shape

{

// Вынудить все дочерние классы определять способ своей визуализации.

public abstract void Draw;

...

}

На заметку! Абстрактные методы могут быть определены только в абстрактных классах, иначе возникнет ошибка на этапе компиляции.

Методы, помеченные как

abstrac
t, являются чистым протоколом. Они просто определяют имя, возвращаемый тип (если есть) и набор параметров (при необходимости). Здесь абстрактный класс
Shape
информирует производные типы о том, что у него есть метод по имени
Draw
, который не принимает аргументов и ничего не возвращает. О необходимых деталях должен позаботиться производный класс.

С учетом сказанного метод

Draw
в классе
Circle
теперь должен быть обязательно переопределен. В противном случае
Circle
также должен быть абстрактным классом и декорироваться ключевым словом
abstract
(что очевидно не подходит в настоящем примере). Вот изменения в коде:

// Если не реализовать здесь абстрактный метод Draw, то Circle

// также должен считаться абстрактным и быть помечен как abstract!

class Circle : Shape

{

public Circle {}

public Circle(string name) : base(name) {}

public override void Draw

{

Console.WriteLine("Drawing {0} the Circle", PetName);

}

}

Итак,

теперь можно предполагать, что любой класс, производный от
Shape
, действительно имеет уникальную версию метода
Draw
. Для демонстрации полной картины полиморфизма рассмотрим следующий код:

Console.WriteLine("***** Fun with Polymorphism *****\n");

// Создать массив совместимых с Shape объектов.

Shape[] myShapes = {new Hexagon, new Circle, new Hexagon("Mick"),

new Circle("Beth"), new Hexagon("Linda")};

// Пройти в цикле по всем элементам и взаимодействовать

// с полиморфным интерфейсом.

foreach (Shape s in myShapes)

{

s.Draw;

}

Console.ReadLine;

Ниже показан вывод, выдаваемый этим кодом:

***** Fun with Polymorphism *****

Drawing NoName the Hexagon

Drawing NoName the Circle

Drawing Mick the Hexagon

Drawing Beth the Circle

Drawing Linda the Hexagon

Данный код иллюстрирует полиморфизм в чистом виде. Хотя напрямую создавать экземпляры абстрактного базового класса (

Shape
) невозможно, с помощью абстрактной базовой переменной допускается хранить ссылки на объекты любого подкласса. Таким образом, созданный массив объектов
Shape
способен хранить объекты классов, производных от базового класса
Shape
(попытка добавления в массив объектов, несовместимых с
Shape
, приведет к ошибке на этапе компиляции).

С учетом того, что все элементы в массиве

myShapes
на самом деле являются производными от
Shape
, вы знаете, что все они поддерживают один и тот же "полиморфный интерфейс" (или, говоря проще, все они имеют метод
Draw
). Во время итерации по массиву ссылок
Shape
исполняющая система самостоятельно определяет лежащий в основе тип элемента. В этот момент и вызывается корректная версия метода
Draw
.

Такой прием также делает простым безопасное расширение текущей иерархии. Например, пусть вы унаследовали от абстрактного базового класса

Shape
дополнительные классы (
Triangle
,
Square
и т.д.). Благодаря полиморфному интерфейсу код внутри цикла
foreach
не потребует никаких изменений, т.к. компилятор обеспечивает помещение внутрь массива
myShapes
только совместимых с
Shape
типов.

Сокрытие членов

Язык C# предоставляет возможность, которая логически противоположна переопределению методов и называется сокрытием. Выражаясь формально, если производный класс определяет член, который идентичен члену, определенному в базовом классе, то производный класс скрывает версию члена из родительского класса. В реальном мире такая ситуация чаще всего возникает, когда вы создаете подкласс от класса, который разрабатывали не вы (или ваша команда); например, такой класс может входить в состав программного пакета, приобретенного у стороннего поставщика.

Поделиться с друзьями: