Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ShPOR.docx
Скачиваний:
38
Добавлен:
23.04.2019
Размер:
93.52 Кб
Скачать

18.Интерфейсы. Функциональное назначение. Иерархия интерфейсов. Множественное наследование: проблемы и способы их разрешения.

В объектно-ориентированном программировании иногда требуется определить, что класс должен делать, а не как он будет это делать. Интерфейс определяет набор методов, которые будут реализованы классом. Сам интерфейс не реализует методы. Таким образом, интерфейс — это логическая конструкция, которая описывает методы, не устанавливая жестко способ их реализации. Интерфейсы синтаксически подобны абстрактным классам. Однако в интерфейсе ни один метод не может включать тело, т.е. интерфейс в принципе не предусматривает какой бы то ни было реализации. Он определяет, что должно быть сделано, но не уточняет, как. Если интерфейс определен, его может реализовать любое количество классов. При этом один класс может реализовать любое число интерфейсов. Для реализации интерфейса класс должен обеспечить тела (способы реализации) методов, описанных в интерфейсе. Каждый класс может определить собственную реализацию. Таким образом, два класса могут реализовать один и тот же интерфейс различными способами, но все классы поддерживают одинаковый набор методов. Следовательно, код, "осведомленный" о наличии интерфейса, может использовать объекты

(18)

любого класса, поскольку интерфейс для всех объектов одинаков. Предоставляя программистам возможность применения такого средства программирования, как интерфейс, С# позволяет в полной мере использовать аспект полиморфизма, выражаемый как "один интерфейс — много методов".

Интерфейсы объявляются с помощью ключевого слова interface:

interface имя{ тип_возврата имя_метода1 {список_параметров) ;

тип_возврата имя_метода2 {список_параметров) ; }

Методы, по сути, — абстрактные. Как упоминалось выше, для методов в интерфейсе не предусмотрены способы реализации. Следовательно, каждый класс, который включает интерфейс, должен реализовать все его методы. В интерфейсе методы неявно являются открытыми (public-методами), при этом не разрешается явным образом указывать спецификатор доступа. Пример интерфейса для класса, который генерирует ряд чисел.

public interface ISeries { int getNext(); // Возвращает следующее число ряда,

void reset (); // Выполняет перезапуск,

void setStart(int x) ; // Устанавливает начальное }

Интерфейсы не могут иметь членов данных. Они не могут определять конструкторы, деструкторы или операторные методы. Кроме того, ни один член интерфейса не может быть объявлен статическим.

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

Итак, если интерфейс определен, один или несколько классов могут его реализовать. Чтобы реализовать интерфейс, нужно указать его имя после имени класса подобно тому, как при создании производного указывается базовый класс. Формат записи класса, который реализует интерфейс, таков: class имя_класса : имя__интерфейса { // тело класса }

Если класс реализует интерфейс, он должен это сделать в полном объеме, т.е. реализация интерфейса не может быть выполнена частично.

Классы могут реализовать несколько интерфейсов. В этом случае имена интерфейсов отделяются запятыми. Класс может наследовать базовый класс и реализовать один или несколько интерфейсов. В этом случае список интерфейсов должно возглавлять имя базового класса. Методы, которые реализуют интерфейс, должны быть объявлены открытыми. Дело в том, что методы внутри интерфейса неявно объявляются открытыми, поэтому их реализации также должны быть открытыми. Рассмотрим пример, где создается класс с именем ByTwos, генерирующий ряд чисел, в котором каждое следующее число больше предыдущего на два.

class ByTwos : ISeries {int start; int val;

public ByTwos() { start = 0; val = 0;}

public int getNext() { val += 2; return val; }

public void reset() { val = start;}

public void setStart(int x) { start = x; val = start; }

Класс ByTwos реализует все три метода, определенные интерфейсом ISeries. Иначе и быть не может, поскольку классу не разрешается создавать частичную реализацию интерфейса. В классах, которые реализуют интерфейсы, можно определять дополнительные члены.

Как и методы, свойства определяются в интерфейсе без тела. Ниже приведен формат спецификации свойства.

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

Проблемы множественного наследования

При множественном наследовании классов возникает две основные проблемы — коллизия имен и наследование от общего предка.

Коллизия имен

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

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

Другая стратегия исходит из того, что, несмотря на единую сигнатуру, методы разных интерфейсов должны быть реализованы по-разному. В этом случае необходимо переименовать конфликтующие методы. Конечно, переименование можно сделать в самих интерфейсах, но это неправильный путь: наследники не должны требовать изменений своих родителей — они сами должны меняться. Переименование методов интерфейсов иногда невозможно чисто технически, если интерфейсы являются встроенными или поставляются сторонними фирмами. К счастью, мы знаем, как производить переименование метода интерфейса в самом классе наследника, для этого достаточно реализовать методы разных интерфейсов как закрытые, а затем открыть их с переименованием.

public interface IProps

(18)

{

void Prop1(string s);

void Prop2 (string name, int val);

void Prop3();

}

public interface IPropsOne

{

void Prop1(string s);

void Prop2 (int val);

void Prop3();

}

У двух интерфейсов — по три метода с совпадающими именами, сигнатуры двух методов совпадают, а в одном случае различаются. Вот класс, наследующий оба интерфейса:

public class ClainTwo:IProps,IPropsOne

{

/// склеивание методов двух интерфейсов

public void Prop1 (string s)

{

Console.WriteLine(s);

}

/// перегрузка методов двух интерфейсов

public void Prop2(string s, int x)

{

Console.WriteLine(s + "; " + x);

}

public void Prop2 (int x)

{

Console.WriteLine(x);

}

/// переименование методов двух интерфейсов

void IProps.Prop3()

{

Console.WriteLine("Свойство 3 интерфейса 1");

}

void IPropsOne.Prop3()

{

Console.WriteLine("Свойство 3 интерфейса 2");

}

public void Prop3FromInterface1()

{

((IProps)this).Prop3();

}

public void Prop3FromInterface2()

{

((IPropsOne)this).Prop3();

}

}

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

Наследование от общего предка

Проблема наследования от общего предка характерна, в первую очередь, для множественного наследования классов. Если класс C является наследником классов A и B, а те, в свой черед, являются наследниками класса Parent, то класс наследует свойства и методы своего предка Parent дважды, один раз получая их от класса A, другой от — B. Это явление называется еще дублирующим наследованием. Для классов ситуация осложняется тем, что классы A и B могли по-разному переопределить методы родителя и для потомков предстоит сложный выбор реализации.

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

Синтаксис языка

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]