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

14.3. Реализация интерфейсов

Прежде чем рассмотреть реализацию интерфейса, уточним отличия интерфейсов от абстрактных классов. Наиболее важное отличие состоит в том, что при наследовании классов у каждого класса может быть только один базовый класс - множественное наследование классов в языке С# невозможно. При построении класса на основе интерфейсов их может быть любое количество. Другими словами класс может реализовать сколько угодно интерфейсов, но при этом может иметь только один базовый класс. В интерфейсе не определяются поля и не может быть конструкторов. В интерфейсе нельзя объявлять статические методы.

Как и для абстрактных классов невозможно определить объект с помощью интерфейса.

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

class имя_класса спецификация_базы { объявления_ членов_класса

}

Спецификация базы класса в этом случае имеет вид:

:имя_базового_классаоpt список_интерфейсовоpt

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

class имя_класса: имя_интерфейса { объявления_ членов_класса

}

Реализацией члена интерфейса является его полное определение в реализующем классе, снабженное модификатором доступа public.

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

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

В следующей программе (14_01.cs) интерфейс IPublication, реализуется классом Item – "заметка в газете".

// 14_01.cs - Интерфейс и его реализация

interface IPublication { // интерфейс публикаций

void write(); // готовить публикацию

void read(); // читать публикацию

string Title { set; get; } // название публикации

}

class Item : IPublication

{ // заметка в газете

string newspaper = "Известия"; // название газеты

string headline; // заголовок статьи

public string Title

{ // реализация свойства

set { headline = value; }

get { return headline; }

}

public void write()

{ // реализация метода

/* операторы, имитирующие подготовку статьи */

}

public void read()

{ // реализация метода

/*cоператоры, имитирующие чтение статьи */

Console.WriteLine(@"Прочел в газете ""{0}"", статью ""{1}"".", newspaper, Title);

}

}

public static void Main()

{

Console.WriteLine("Publication!");

Item article = new Item();

article.Title = "О кооперации";

article.read();

}

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

Publication!

Прочел в газете "Известия", статью "О кооперации".

Класс Item кроме реализаций членов интерфейса включает объявления закрытых полей: newspaper (название газеты) и headline (заголовок статьи). Для простоты в класс не включён конструктор и только условно обозначены операторы методов write() и read(). Реализация свойства Title приведена полностью - аксессеры get и set позволяют получить и задать название статьи, представляемой конкретным объектом класса Item. В методе Main() нет ничего незнакомого читателю - определён объект класса Item и ссылка articte на него. С помощью обращения arcticle.Title задано название статьи.

В UML для изображения интерфейсов применяется та же символика, что и для классов. Конечно, имеется отличие - в верхней части, после имени интерфейса помещается служебное слово Interface. Тот факт, что класс реализует интерфейс, отображается с помощью специального символа и имени интерфейса над прямоугольником, представляющим класс.

В качестве второго примера рассмотрим интерфейс, реализация членов которого позволит получать целочисленные значения членов числовых рядов (Шилдт[14]):

interface ISeries {

void setBegin(); // восстановить начальное состояние

int GetNext {get; } // вернуть очередной член ряда

int this[int k] {get;} // вернуть k-й член ряда

}

Договоримся о ролях прототипов, входящих в этот интерфейс. В приведённом объявлении: setBegin() – прототип метода; GetNext – имя свойства, позволяющего получить значение очередного члена ряда и настроить объект на следующий член. Индексатор в этом примере позволяет получить значение не очередного, а произвольного k-го члена ряда и перевести объект в состояние, при котором свойство GetNext вернёт значение (k+1)-го члена.

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

Интерфейс ISeries можно реализовать для представления разных числовых рядов. Вот, например, регулярные числовые последовательности, которые можно представить классами, реализующими интерфейс ISeries: 1, 1, 2, 3, 5, 8, 13 ... – ряд Фибоначчи: аii-2i-1, где i>2; a1=1, а2=1; 1, 3, 4, 7, 11, 18, 29 ... – ряд Лукаса: аii-2i-1, где i>2; a1=1, a2=3; 1, 2, 5, 12, 29, 70, 169 ... – ряд Пелла: аii-2+2*аi-1, где i>2; a1=1, a2=2.

В следующей программе на основе интерфейса ISeries определен класс, представляющий в виде объектов фрагменты ряда Пелла (см. также рис 14.2).

// 14_02.cs - Интерфейс и его реализация

interface ISeries

{

void setBegin(); // восстановить начальное состояние

int GetNext { get; } // вернуть очередной член ряда

int this[int k] { get;} // вернуть к-й член ряда

}

class Pell : ISeries // Ряд Пелла: 1, 2, 5, 12,...

{

int old, last; // два предыдущих члена ряда

public Pell() // конструктор

{ setBegin(); }

public void setBegin() // задать начальное состояние

{

old = 1;

last = 0;

}

public int GetNext // вернуть следующий после last

{

get

{

int now = old + 2 * last;

old = last;

last = now;

return now;

}

}

public int this[int k] // вернуть к-й член ряда

{

get

{

int now = 0;

setBegin();

if (k <= 0)

return -1;

for (int j = 0; j < k; j++)

now = GetNext;

return now;

}

}

public void seriesPrint(int n)

{ // вывести n членов, начиная со следующего

for (int i = 0; i < n; i++)

Console.Write(GetNext + "\t");

Console.WriteLine();

}

}

public static void Main()

{

Pell pell = new Pell();

pell.seriesPrint(9);

Console.WriteLine("pell[3] = " + pell[3]);

pell.seriesPrint(4);

pell.seriesPrint(3);

}

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

1 2 5 12 29 70 169 408 985

pell[3] = 5

12 29 70 169

408 985 2378

169 408 985

Кроме реализации членов интерфейса ISeries в классе Pell объявлен метод seriesPrint(). Он выводит значения нескольких членов ряда, следующих за текущим. Количество членов определяет аргумент метода seriesPrint(). После выполнения метода состояние ряда изменится – текущим членом станет последний выведенный член ряда. Обратите внимание, что при реализации индексатора нумерация членов ряда начинается с 1.

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

В наших примерах интерфейсы и реализующие их классы размещены в одном файле. Если интерфейс объявлен не в том файле, где выполняется его реализация, то объявление интерфейса необходимо снабдить соответствующим модификатором доступа.

В приведённых выше программах классов Item и Pell была использована неявная реализация членов интерфейсов. Термин "неявная" употребляется для обозначения того, что в объявлении класса, реализующего интерфейс, не применяются квалифицированные имена членов интерфейса и их реализации снабжаются обязательным модификатором public.

Когда один класс реализует несколько интерфейсов, возможны совпадения имён членов из разных интерфейсов. Для разрешения такой конфликтной ситуации в классе, реализующем интерфейс, используется квалифицированное имя члена интерфейса. Здесь существует ограничение – такая реализация члена называется явной иона не может быть открытой, то есть для неё нельзя использовать модификатор public. Подробнее об особенностях явной реализации интерфейсов можно узнать из работ [1, 2, 8].

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