Принципы объектно-ориентированного программирования

         

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



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

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

using System;

public class Control {

public void SerializeO


<

Console.WriteLine("Control.Serialize called");

> >

public interface IDataBound {

void SerializeO;

}

public class EditBox : Control, IDataBound

{

>

class InterfacelnhlApp

{

public static void Main()

{

EditBox edit = new EditBoxQ; edit. SerializeO; } }

Как вы знаете, чтобы реализовать интерфейс, нужно предоставить определение каждого члена в определении интерфейса. Однако в предыдущем примере мы этого не сделали, а код все равно компилируется! Причина в том, что компилятор С# ищет метод Serialize, реализованный в классе EditBox, и находит его. Однако компилятор неверно определяет, что это реализованный метод. Метод Serialize, найденный компилятором, унаследован от класса Control методом Serialize, но не является настоящей реализацией метода IDataBound.Serialize. Поэтому, хоть он и компилируется, этот код не будет функционировать, как задумано, в чем мы убедимся позже.

Теперь внесем дополнительную интригу. Следующий код сначала проверяет оператором as, реализован ли интерфейс, затем пытается вызвать реализованный метод Serialize. Этот код компилируется и работает. Однако, как мы знаем, в классе EditBox метод Serialize на самом деле не реализован из-за наследования IDataBound. В EditBox уже есть метод Serialize (унаследованный) от класса Control. Это значит, что клиент, по всей вероятности, не получит ожидаемых результатов.

using System;

public class Control {

public void SerializeO

{

Console.WriteLine("Control.Serialize called");

} }

public interface IDataBound {

void SerializeO; }

public class EditBox : Control, IDataBound

{

}

class InterfaceInh2App {

public static void MainО

<

EditBox edit = new EditBoxO;

IDataBound bound = edit as IDataBound;

if (bound != null)

{

Console.WriteLine("IDataBound is supported...");

bound. SerializeO; }

else {

Console.WriteLineC'IDataBound is NOT supported...");

>

> >

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

using System;

interface ITest

{

void Foo();

}

// В классе Base реализован интерфейс ITest. class Base : ITest

<

public void Foo()

{

Console.WriteLine("Base.Foo (ITest implementation)");

> }

class HyDerived : Base

{

public new void Foo()

{

Console.WriteLine("MyDerived.Foo");

} }

public class InterfacelnhSApp

{

public static void MainQ

{

MyDe rived myDe rived = new MyDerivedO;

myDerived.Foo();

ITest test = (ITest)myDerived; test.FooQ; }

}

В результате выполнения этот код выводит информацию:

MyDerived.Foo

Base.Foo (ITest implementation)

В этой ситуации в классе Base реализован интерфейс ITest и его метод Foo. Однако производный от Base класс My Derived реализует для этого класса новый метод Foo. Какой из методов Foo будет вызван? Это зависит от имеющейся у вас ссылки. Если есть ссылка на объект My Derived, вызывается его метод Foo. Это так, даже несмотря на то, что у объекта myDerived есть унаследованная реализация ITest.Foo: в период выполнения будет исполнен MyDerived.Foo, так как ключевым словом new задана подмена унаследованного метода.

Однако когда вы явно выполняете приведение объекта myDerived к интерфейсу ITest, компилятор разрешает реализацию интерфейса. У класса MyDerived есть метод с тем же именем, но это не тот метод, что ищет компилятор. Когда вы приводите объект к интерфейсу, компилятор проходит по дереву наследования, пока не найдет класс, содержащий в своем базовом списке интерфейс. Именно поэтому в результате выполнения последних двух строк кода метода Main вызывается метод Foo, реализованный в ITest.

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



Содержание раздела