Console.WriteLine("3D-отображение окружности!");
}
}
// Hexagon поддерживает IPointy и IDraw3D.
public class Hexagon: Shape, IPointy, IDraw3D{
…
public void Draw3D() { Console.WriteLine ("3D-отображение шестиугольника!"); }
}
На рис. 7.3 показана соответствующая обновленная диаграмма классов, полученная в Visual Studio 2005.
Рис. 7.3. Обновленная иерархия форм
Если определить метод, использующий интерфейс IDraw3D в виде параметра, вы получите возможность передать любой объект, реализующий IDraw3D (но если вы попытаетесь передать тип, не поддерживающий нужный интерфейс, будет сгенерирована ошибка компиляции). Рассмотрим следующий фрагмент программного кода.
// Создание нескольких форм.
// Если это возможно, их отображение в трехмерном виде.
public class Program {
// Отображение форм, поддерживающих IDraw3D.
public static void DrawIn3D(IDraw3D itf3d) {
Console.WriteLine("-› Отображение IDraw3D-совместимого типа");
itf3d.Draw3D();
}
static void Main() {
Shape [] s = {new Hexagon(), new Circle(), new Triangle("Joe"), new Circle("JoJo")};
for (int i = 0; i ‹ s.Length; i++) {
…
// Можно ли отобразить в 3D-виде?
if (s[i] is IDraw3D) DrawIn3D((IDraw3D)s[i]);
}
}
}
Обратите внимание на то, "что треугольник не отображается, поскольку он не является IDraw3D-совместимым (рис. 7.4).
Рис.7.4. Интерфейсы в качестве параметров
Интерфейсы в качестве возвращаемых значений
Интерфейсы можно использовать и в качестве возвращаемых значений методов. Например, можно создать метод, который берет любой System.Object, проверяет на совместимость с IPointy и возвращает ссылку на извлеченный интерфейс.
// Этот метод проверяет соответствие IPointy и, если это возможно,
// возвращает ссылку на интерфейс.
static IPointy ExtractPointyness(object o) {
if (o is IPointy) return (IPointy ) o;
else return null;
}
С этим методом можно взаимодействовать так, как предлагается ниже.
static void Main(string[] args) {
// Попытка извлечь IPointy из объекта Car.
Car myCar = new Car();
IPointy itfPt = ExtractPointyness(myCar);
if (itfPt!= null) Console.WriteLine("Объект имеет {0} вершин.", itfPt.Points);
else Console.WriteLine("Этот объект не реализует IPointy");
};
Массивы интерфейсных типов
Следует понимать, что один и тот же интерфейс может реализовываться многими типами, даже если эти типы не находятся в рамках одной иерархии классов. В результате можно получать очень мощные программные конструкции, Предположим, например, что мы построили одну иерархию классов для описания кухонной посуды, а другую – для описания садового инвентаря.
Эти иерархии абсолютно не связаны между собой с точки зрения классического наследования, но с ними можно обращаться полиморфно, используя программирование на основе интерфейсов. Для иллюстрации предположим, что у нас есть массив объектов, совместимых с IPointy. При условии, что все объекты этого массива поддерживают один интерфейс, вы можете обходиться с каждым объектом, как с IPointy-совместимым объектом, несмотря на абсолютную несовместимость иерархий классов.
static void Main(string[] args) {
// Этот массив может содержать только типы,
// реализующие интерфейс IPointy.
IPointy[] myPointyObjects = {new Hexagon(), new Knife(), new Triangle(), new Fork(), new PitchFork()};
for (int i = 0; i ‹ myPointyObjects.Length; i++) Console.WriteLine("Объект имеет {0} вершин", myPointyObjects[i].Points);
}
Замечание.С учетом общеязыковой природы .NET важно подчеркнуть, что можно определить интерфейс на одном языке (C#), а реализовать его на другом (VB .NET). Но чтобы выяснить, как это сделать, нам потребуется понимание структуры компоновочных блоков .NET, что является темой обсуждения главы 11.
Явная реализация интерфейса
В определении IDraw3D мы были вынуждены назвать наш единственный метод Draw3D(), чтобы избежать конфликта с абстрактным методом Draw(), определенным в базовом классе Shape. Такое определение интерфейса вполне допустимо, но более естественным именем для метода было бы Draw().
// Изменение имени с "Draw3D" на "Draw".
public interface IDraw3D {
Читать дальше