Скачиваний:
84
Добавлен:
24.03.2015
Размер:
212.48 Кб
Скачать

14.4. Интерфейс как тип

Несмотря на то, что нельзя создавать экземпляры (объекты) интерфейсов, но интерфейс как и класс в языке С# является ссылочным типом. Подобно тому, как можно объявлять ссылку, имеющую тип абстрактного класса, разрешено объявлять ссылку с типом интерфейса. Такая ссылка может быть равноправно связана с объектом любого класса, который реализовал данный интерфейс. С помощью такой ссылки возможно получить доступ ко всем членам класса, реализующим соответствующие члены интерфейса. Однако ссылка с типом интерфейса не позволяет получить доступ к членам класса, которые отсутствовали в интерфейсе, но были добавлены в класс, реализующий интерфейс.

Интерфейс как тип может специфицировать параметр метода, может определять тип возвращаемого методом значения, может определять тип элементов массива. Рассмотрим эти возможности на иллюстративных примерах. Определим интерфейс, специфицирующий средства обработки геометрических фигур:

interface IGeo { // интерфейс геометрической фигуры

void transform(double coef); // преобразовать размеры

void display(); // вывести характеристики

В приведённом интерфейсе нет сведений о конкретных особенностях тех геометрических фигур, классы которых могут реализовать IGeo. Например, ничего не ограничивает размерность пространства, в котором определены фигуры. Возможности преобразований фигур с помощью реализаций метода transform() могут быть достаточно произвольными. Самыми разными могут быть сведения о фигурах, выводимые реализациями метода display().

Определим два класса, реализующих интерфейс IGeo: Circle - круг и Cube - куб. Поле double rad в классе Circle - это радиус круга. Поле double rib в классе Cube - ребро куба. Реализация метода transform() в обоих классах изменяют линейные размеры (rad и rib) в заданное параметром число раз.

Чтобы продемонстрировать применимость интерфейсной ссылки в качестве параметра, определим статическую функцию report(), из тела которой выполняется обращение к реализации метода display(). Указанным соглашениям соответствует следующая программа:

// 14_03.cs - интерфейсные ссылки

interface IGeo

{ // интерфейс геометрической фигуры

void transform(double coef); // преобразовать размеры

void display(); // вывести характеристики

}

class Circle : IGeo

{ // круг

double rad = 1; // радиус круга

public void transform(double coef)

{ rad *= coef; }

public void display()

{

Console.WriteLine("Площадь круга: {0:G4}", Math.PI * rad * rad);

}

}

class Cube : IGeo

{ // куб

double rib = 1; // ребро куба

public void transform(double coef)

{ rib *= coef; }

public void display()

{

Console.WriteLine("Объем куба: {0:G4}", rib * rib * rib);

}

}

class Program

{

static void report(IGeo g)

{

Console.WriteLine("Данные объекта класса {0}:", g.GetType());

g.display();

}

public static void Main()

{

Circle cir = new Circle();

report(cir);

Cube cub = new Cube();

report(cub);

IGeo ira = cir;

report(ira);

}

}

Результаты выполнения программы: Данные объекта класса Circle: Площадь круга: 3,142 Данные объекта класса Cube: Объем куба: 1 Данные объекта класса Circle: Площадь круга: 3,142

Обратим внимание на статистический метод report(). Его параметр g -ссылка с типом интерфейса IGeo. Выражение g.GetType() в теле метода report() позволяет получить имя класса, ссылка на который используется в качестве аргумента при обращении к методу report(). Оператор g.display() обеспечивает вызов той реализации метода display(), которая соответствует типу аргумента. Использование интерфейсной ссылки в качестве параметра позволяет применять метод report() для обработки объектов любых классов, которые реализовали интерфейс IGeo. Код метода Main() иллюстрирует сказанное. В нём определены два объекта классов Circle и Cube и ассоциированные с ними ссылки cir, cub. Их использование в качестве аргументов метода report() приводит к вызовам методов display() из соответствующих классов. Это подтверждают результаты выполнения программ.

Обратить внимание на последние две строки тела функции Main(). Определена ссылка ira с типом интерфейса IGeo, которой затем присвоено значение ссылки на объект класса Circle. Это очень важная особенность -интерфейсной ссылке можно присвоить ссылку на объект любого класса, который реализовал данный интерфейс. В теле метода report() аргумент в этом случае воспринимается как имеющий тип класса Circle, реализовавшего интерфейс IGeo. Остановимся на этом подробнее. Ссылка IGeo ira при объявлении имеет тип интерфейса, а после присваивания ira=cir ссылка ira получает тип класса Circle. Таким образом, наша программа иллюстрирует различие между объявленным типом ссылки и её типом времени исполнения. (О таком различии говорят как о статическом и динамическом типах одной и той же переменной.)

Несмотря на то, что параметр метода report() специфицирован как ссылка типа IGeo и ссылка ira типа IGeo использована в качестве аргумента, вызов базируется на типе времени исполнения и выполняется обращение к объекту класса Circle. Такая возможность называется поздним иначе динамическим связыванием. Решение о том, какой метод вызывать, принимается при позднем связывании на основе типа времени исполнения. В противоположность этому стратегия использования объявленного типа называется ранним или статическим связыванием.

Применение в качестве параметров и аргументов интерфейсных ссылок и ссылок с типом базового класса (при наличии виртуальных членов) обеспечивает позднее (динамическое) связывание. Позднее связывание -одно из проявлений полиморфизма в языке С#.

Чтобы продемонстрировать другие возможности применения интерфейсных ссылок, используем без изменений ещё раз интерфейс IGeo и реализующие его классы Circle и Cube. В класс Program добавим статический метод:

static IGeo mapping(IGeo g, double d)

{

g.transform(d);

return g;

}

Первый параметр - ссылка с типом интерфейса. Метод, получив в качестве первого аргумента ссылку на конкретный объект, изменяет его линейные размеры и возвращает в качестве результата ссылку на изменённый объект. Второй параметр - коэффициент изменения линейных размеров. Используем методы mapping() и report() следующим образом (программа 14_04.cs):

public static void Main()

{

IGeo ira = new Circle(); // единичный радиус

report(ira);

ira.transform(3);

report(ira);

ira = mapping(new Cube(), 2);

report(ira);

}

Результаты выполнения программы: Данные объекта класса Circle: Площадь круга: 3,142 Данные объекта класса Circle: Площадь круга: 28,27 Данные объекта класса Cube: Объем куба: 8

В методе Main() ссылка ira с объявленным типом IGeo связана с объектом класса Circle, который она "представляет" в обращениях к статическому методу report() и в вызове нестатического метода transform(). Первый аргумент метода mapping() - вновь созданный объект класса Cube. По умолчанию у этого объекта-куба ребро равно 1. Результат выполнения статического метода mapping() присваивается ссылке ira, после чего она ассоциирована с изменённым объектом класса Cube. Во всех использованиях интерфейсной ссылки ira проявляется её динамический тип, и этот тип не остаётся постоянным во время исполнения программы. Результаты выполнения программы дополняют сказанное.

Так как интерфейс является типом, то можно определять массивы с элементами, имеющими тип интерфейса. Элементам такого массива можно присваивать как значения интерфейсных ссылок, так и значения ссылок на объекты любых классов, реализующих данный интерфейс. В следующей программе (14_05.cs) определён массив типа IGeo[ ] и именующая его ссылка iarray. Элементам массива присваиваются значения ссылок на объекты классов Circle и Cube. Код без объявлений интерфейса, классов и статических методов report() и mapping():

public static void Main()

{

IGeo[] iarray = new IGeo[4];

IGeo ira = new Circle();

iarray[0] = ira; ira.transform(3);

iarray[1] = ira;

ira = mapping(new Cube(), 2);

iarray[2] = ira;

iarray[3] = new Circle();

foreach (IGeo obj in iarray)

report(obj);

}

Результаты выполнения программы:

Данные объекта класса Circle: Площадь круга: 28,27 Данные объекта класса Circle: Площадь круга: 28,27 Данные объекта класса Cube: Объем куба: 8 Данные объекта класса Circle: Площадь круга: 3,142

В методе Main() элементам массива присвоены ссылки на объекты разных классов. Затем в операторе foreach с помощью ссылки типа IGeo перебираются значения всех элементов массива и для каждого из них вызывается статический метод report(). Обратим внимание на выводимые результаты и последовательность присваивания значений элементам массива. Ссылка IGeo ira при объявлении адресует объект класса Circle, и её значение присвоено элементу iarray[0]. Затем оператор ira.transform(3) изменяет объект, связанный со ссылкой ira, и её значение присваивается элементу iarray[1]. Таким образом значения элементов iarray[0] и iarray[1] равны и оба элемента адресуют уже изменённый объект класса Circle. Оператор ira=mapping(new Cube(),2); присваивает интерфейсной ссылке ira адрес модифицированного объекта класса Cube. После этого присваивается значение элементу iarray[2]. Наконец элементу iarray[3] присваивается ссылка на новый объект класса Circle. Таким образом, в программе определены 3 объекта, адресуемые четырьмя элементами массива.

Соседние файлы в папке Lekc_C#