Неявные преобразования и полиморфизм в C#

Представлять наследование и то, что оно делает, можно несколькими способами. Первый и наиболее очевидный — наследование позволяет позаимствовать реализацию. Другими словами, можно унаследовать класс D от класса А и повторно использовать реализацию класса А в классе D.

Потенциально это позволит сэкономить некоторую часть работы при определении класса D. Другое применение наследования — специализация когда класс D становится специализированной формой класса А. Например, рассмотрим иерархию классов, показанную на рисунке.

Как видите, классы Rectangle и Circle наследуются от класса GeometricShape. Другими словами, они являются специализациями класса GeometricShape. Специализация бессмысленна без полиморфизма и виртуальных методов. На данный момент достаточно лишь в общих чертах разобраться, что означает это понятие.

Полиморфизм описывает ситуацию, когда тип, на который ссылается определенная переменная, может вести себя как (и в действительности быть) экземпляр другого (более специализированного) типа. На рисунке показан метод класса GeometricShape по имени Draw. Этот же метод присутствует и в Rectangle, и в Circle. Такую модель можно реализовать следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class GeometricShape {
public virtual void Draw() {
// Выполнить некоторое рисование по умолчанию
}
}
public class Rectangle : GeometricShape {
public override void Draw() {
// Нарисовать прямоугольник
}
public class Circle : GeometricShape {
public override void Draw() {
// Нарисовать круг
}
public class EntryPoint {
private static void DrawShape( GeometricShape shape ) {
shape.Draw();
}
static void Main() {
Circle circle = new Circle (); GeometricShape shape = circle;
DrawShape ( shape );
DrawShape ( circle );
}

В методе Main создается новый экземпляр Circle. Сразу после этого получается ссылка типа GeometricShape на тот же объект. Это важный момент. Компилятор здесь неявно преобразует эту ссылку в ссылку на тип GeometricShape, позволяя использовать простое выражение присваивания. На самом деле, однако, она продолжает ссылаться на тот же объект Circle. В этом суть специализации типа и автоматического преобразования, сопровождающего ее.

Теперь рассмотрим оставшуюся часть кода метода Main. После получения ссылки GeometricShape на экземпляр Circle можно передать ее методу DrawShape, который не делает ничего кроме вызова метода Draw переданной ему фигуры. Однако ссылка на объект фигуры на самом деле указывает на Circle, метод Draw определен как виртуальный, а класс Circle переопределяет виртуальный метод, так что вызов Draw на ссылке GeometricShape на самом деле приводит к вызову Circle. Draw.

Это и есть полиморфизм в действии. Метод DrawShape не интересует, какой конкретный тип фигуры представляет переданный ему объект. То, с чем он имеет дело — это GeometricShape, a Circle является типом GeometricShape. Вот почему наследование иногда называют отношением “is-a” (”является”). В данном примере Rectangle является GeometricShape и Circle является GeometricShape.

Ключ к ответу на вопрос, когда наследование имеет смысл, а когда нет, лежит в применении отношения “is-a” к существующему дизайну. Если класс D наследуется от класса В, и класс D семантически не является классом В, то для данного отношения наследование является неподходящим инструментом.

Следует дать еще одно важное замечание по поводу наследования и возможности преобразования. Выше упоминалось, что компилятор неявно преобразует ссылку на экземпляр Circle в ссылку на экземпляр GeometricShape. Неявно в данном случае означает, что код не должен делать ничего специального для выполнения такого преобразования, а под “чем-то специальным” обычно имеется в виду операция приведения.

Поскольку компилятор обладает способностью делать это на основе знания иерархии наследования, то может показаться, что можно и обойтись без получения ссылки на GeometricShape перед вызовом DrawShape с экземпляром Circle. На самом деле так оно и есть! Это доказывает последняя строка метода Main.

Ссылку на экземпляр Circle можно просто передать непосредственно методу DrawShape, и поскольку компилятор может неявно преобразовать ее в ссылку на тип GeometricShape исходя из отношений наследования, он выполнит всю работу за вас. Здесь снова проявляется вся мощь этого механизма.

Теперь можно передавать любой экземпляр объекта, производного от GeometricShape. После того, как построенное программное обеспечение будет упаковано в коробку и помечено наклейкой “Версия 1″, некто может потом заняться версией 2 и определить новые фигуры, унаследованные от GeometricShape, причем код DrawShape не потребует никаких изменений.

Ему даже не понадобится ничего знать от новой специализации. Это могут быть Trapezoid, Square (специализации Rectangle) или же Ellipse. Это не имеет значения до тех пор, пока классы фигур наследуются от GeometricShape.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100