На рис. 6.5 обратите внимание на то, что типы Hexagon и Circle расширяют базовый класс Shape. Как и любой базовый класс. Shape определяет набор членов (в данном случае свойство PetName и метод Draw), общих для всех наследников.
Во многом подобно иерархии классов для сотрудников вы должны иметь возможность запретить создание экземпляров класса Shape напрямую, потому что он представляет слишком абстрактную концепцию. Чтобы предотвратить непосредственное создание экземпляров класса Shape, его можно определить как абстрактный класс. К тому же с учетом того, что производные типы должны уникальным образом реагировать на вызов метода Draw, пометьте его как virtual и определите стандартную реализацию. Важно отметить, что конструктор помечен как protected, поэтому его можно вызывать только в производных классах.
// Абстрактный базовый класс иерархии.
abstract class Shape
{
protected Shape(string name = "NoName")
{ PetName = name; }
public string PetName { get; set; }
// Единственный виртуальный метод.
public virtual void Draw
{
Console.WriteLine("Inside Shape.Draw");
}
}
Обратите внимание, что виртуальный метод Draw предоставляет стандартную реализацию, которая просто выводит на консоль сообщение, информирующее о факте вызова метода Draw из базового класса Shape. Теперь вспомните, что когда метод помечен ключевым словом virtual, он поддерживает стандартную реализацию, которую автоматически наследуют все производные типы. Если дочерний класс так решит, то он может переопределить такой метод, но он не обязан это делать. Рассмотрим показанную ниже реализацию типов Circle и Hexagon:
// В классе 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;
...
}
На заметку! Абстрактные методы могут быть определены только в абстрактных классах, иначе возникнет ошибка на этапе компиляции.
Методы, помеченные как abstract, являются чистым протоколом. Они просто определяют имя, возвращаемый тип (если есть) и набор параметров (при необходимости). Здесь абстрактный класс Shape информирует производные типы о том, что у него есть метод по имени Draw, который не принимает аргументов и ничего не возвращает. О необходимых деталях должен позаботиться производный класс.