Добавить в цитаты Настройки чтения

Страница 317 из 371



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

public interface IProps

{

   void Prop1(string s);

   void Prop2 (string name, int val);

   void Ргор3 ();

}

public interface IPropsOne

{

   void Prop1(string s);

   void Prop2 (int val);

   void Ргор3 ();

}

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

public class ClainTwo: IProps,IPropsOne

{

   /// <summary>

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

   /// </summary>

   /// <param name="s"></param>

   public void Prop1 (string s)

   {

       Console.WriteLine (s);

   }

   /// <summary>

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

   /// </summary>

   /// <param name="s"></param>

   /// <param name="x"></param>

   public void Prop2(string s, int x)

   {

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

   }

   public void Prop2 (int x)

   {

        Console.WriteLine(x);

   }

   /// <summary>

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

   /// </summary> void IProps.Ргор3()

   {

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

   }

   void IPropsOne.Ргор3()

   {

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

   }

   public void Prop3FromInterface 1()

   {

        ((IProps)this). Ргор3();

   }

   public void Prop3FromInterface2()

   {

        ((IPropsOne)this). Ргор3();

   }

}

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

Приведу пример работы с объектами класса и интерфейсными объектами:

public void TestCliTwoInterfaces()

{

   Console.WriteLine("Объект ClainTwo вызывает методы двух интерфейсов!");

   Cli.ClainTwo claintwo = new Cli.ClainTwo ();

   claintwo.Prop1("Склейка свойства двух интерфейсов");

   claintwo.Ргор2("перегрузка::: ",99);

   claintwo.Prop2(9999);

   claintwo.Prop3FromInterface1 ();

   claintwo.Prop3FromInterface2 ();

   Console.WriteLine("Интерфейсный объект вызывает методы 1-го интерфейса!");

   Cli.IProps ip1 = (Cli.IProps)claintwo;





   ip1.Prop1("интерфейс IProps: свойство 1");

   ip2.Prop2("интерфейс 1", 88);

   ip1.Ргор3 ();

   Console.WriteLine("Интерфейсный объект вызывает методы 2-го интерфейса!");

   Cli.IPropsOne ip2 = (Cli.IPropsOne)claintwo;

   ip2.Propl("интерфейс IPropsOne: свойство1");

   ip2.Prop2 (7777);

   ip2.Ргор3 ();

}

Результаты работы тестирующей процедуры показаны на рис. 19.2.

Рис. 19.2. Решение проблемы коллизии имен Наследование от общего предка

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

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

Начнем наш пример с наследования интерфейсов:

public interface IParent

{

   void ParentMethod();

}

public interface ISonl: IParent {

   void SonlMethod();

}

public interface ISon2:IParent {

   void Son2Method();

}

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

public class Pars: ISon1, ISon2

{

    public void ParentMethod()

    {

       Console.WriteLine("Это метод родителя!");

    }

    public void Son1Method()

    {

       Console.WriteLine("Это метод старшего сына!");

    }

    public void Son2Method()

    {

        Console.WriteLine("Это метод младшего сына!");

    }

}//class Pars

Класс обязан реализовать метод ParentMethod, приходящий от обоих интерфейсов. Понимая, что речь идет о дублировании метода общего родителя — интерфейса IParent, лучшей стратегией реализации является склеивание методов в одной реализации, что и было сделано. Приведу тестирующую процедуру, где создается объект класса и три объекта интерфейсных классов, каждый из которых может вызывать только методы своего интерфейса:

public void TestlParsons ()

{

    Console.WriteLine("Объект класса вызывает методы трех интерфейсов!");

    Cli. Pars ct = new Cli.Pars();

    ct.ParentMethod();

    ct.Son1Method();

    ct.Son2Method();

    Console.WriteLine("Интерфейсный объект 1 вызывает свои методы!");

    Cli.IParent ip = (IParent)ct;

    ip.ParentMethod();

    Console.WriteLine("Интерфейсный объект 2 вызывает свои методы!");

    Cli.ISonl ipl = (ISon1)ct;

    ip1.ParentMethod ();

    ip1.SonlMethod();

    Console.WriteLine("Интерфейсный объект 3 вызывает свои методы!");

    Cli.ISon2 ip2 = (ISon2)ct;

    ip2.ParentMethod ();

    ip2.Son2Method();

}

Результаты работы тестирующей процедуры показаны на рис. 19.3.

Рис. 19.3. Дублирующее наследование интерфейсов

Встроенные интерфейсы

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

Упорядоченность объектов и интерфейс IComparable

Часто, когда создается класс, желательно задать отношение порядка на его объектах. Такой класс следует объявить наследником интерфейса IComparable. Этот интерфейс имеет всего один метод CompareTo (object obj), возвращающий целочисленное значение, положительное, отрицательное или равное нулю, в зависимости от выполнения отношения "больше", "меньше" или "равно".