Интерфейсы и наследование

Интерфейс может не иметь или иметь сколько угодно интерфейсов-предков, в по­следнем случае он наследует все элементы всех своих базовых интерфейсов, начиная с самого верхнего уровня. Базовые интерфейсы должны быть доступны в не меньшей степени, чем их потомки. Например, нельзя использовать интер­фейс, описанный со спецификатором private или internal, в качестве базового для открытого (public) интерфейса1.

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

interface IBase

{

void F(int i);

}

interface ILeft: IBase

{

new void F(int i); // переопределение метода F

}

interface Iright: IBase

{

void GO;

}

interface Iderived: ILeft, IRight { }

class A

{

void Test(IDerived d)

{

d.F(1); // Вызывается ILeft.F

((IBase)d).F(1); // Вызывается IBase.F

((ILeft)d).F(1); // Вызывается ILeft.F

((IRight)d).F(1); // Вызывается IBase.F

}

}

Метод F из интерфейса IBase скрыт интерфейсом ILeft, несмотря на то что в це­почке IDerived — IRight — IBase он не переопределялся.

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

Естественно, интерфейс не может быть наследником самого себя

должно ссылаться на тот интерфейс, в котором был описан соответствующий элемент, например:

class A: IRight

{

IRight.G() {... }

IBase.F(int i){...} // IRight.F(int i) - нельзя

}

Интерфейс, на собственные или унаследованные элементы которого имеется яв­ная ссылка, должен быть указан в списке предков класса, например:

Class В: А

{

// IRight.GO {... } нельзя!

}

class С: A, IRight

{

IRight.G() {... } // можно

IBase.F(int i){...} // можно

}

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

interface IBase

{

void А();

}

class Base: IBase

{

public void А() {... }

}

class Derived: Base

{

new public void А() {... }

}

//...

Derived d = new Derived ();

d.A(); // вызывается Derived.А();

IBase id = d;

id.А(); // вызывается Base.А();

Однако если интерфейс реализуется с помощью виртуального метода класса, по­сле его переопределения в потомке любой вариант обращения (через класс или через интерфейс) приведет к одному и тому же результату:

interface IBase

{

void А();

}

class Base: IBase

{

public virtual void А() {... }

}

class Derived: Base

{

public override void А() {... }

}

Derived d = new Derived();

d.A(); // вызывается Derived.А();

IBase id = d;

id.А(); // вызывается Derived.А();

Метод интерфейса, реализованный явным указанием имени, объявлять виртуаль­ным запрещается. При необходимости переопределить в потомках его поведение пользуются следующим приемом: из этого метода вызывается другой, защищен­ный метод, который объявляется виртуальным. В приведенном далее примере метод А интерфейса IBase реализуется посредством защищенного виртуального метода А_, который можно переопределять в потомках класса Base:

interface IBase

{

void А();

}

class Base: IBase

{

void IBase.A() { A_(); }

protected virtual void А() {... }

}

class Derived: Base

{

protected override void А О {... }

}

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

interface IBase

{

void А();

}

class Base: IBase

{

void IBase.А() {... } //не используется в Derived

}

class Derived: Base, IBase

{

public void А() {... }

}

Если класс наследует от класса и интерфейса, которые содержат методы с одина­ковыми сигнатурами, унаследованный метод класса воспринимается как реали­зация интерфейса, например:

interface Interface1

{

void F();

}

class Class1

{

public void F() {.. }

public void G() {... }

}

class Class2: Class1, Interface1

{

new public void G() {... }

}

Здесь класс Class2 наследует от класса Classl метод F. Интерфейс Interface1 так­же содержит метод F. Компилятор не выдает ошибку, потому что класс Class2 со­держит метод, подходящий для реализации интерфейса.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: