Опубликован: 02.12.2009 | Уровень: специалист | Доступ: платный | ВУЗ: Тверской государственный университет
Лекция 6:

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

< Лекция 5 || Лекция 6: 12345 || Лекция 7 >

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

Множественным наследованием называется ситуация, когда класс объявляет N классов ( N > 1 ) своими непосредственными родителями. В языке C# есть ограничения на множественное наследование. Ситуация здесь такая. Во-первых, у каждого класса родителем, хотя не всегда непосредственным, является класс object. Во-вторых, каждый класс может явно объявить один класс в качестве непосредственного родителя, а также объявить непосредственными родителями один или несколько интерфейсов. Таким образом, в C# допускается множественное наследование интерфейсов и одиночное (не считая наследования от класса object ) наследование классов.

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

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

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

Но что следует делать классу наследнику в тех случаях, когда сигнатуры методов совпадают? Возможны две стратегии - склеивание методов и переименование.

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

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

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

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

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

Дублирующее наследование

Рис. 5.3. Дублирующее наследование

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

Склеивание и переименование

Приведу пример двух интерфейсов, имеющих методы с одинаковой сигнатурой, и класса - наследника этих интерфейсов, применяющего разные стратегии для конфликтующих методов. У нас уже определен интерфейс IStrings. Предположим, что существует интерфейс ITransform, подобный Istrings:

interface ITransform
  {
   /// <summary>
   /// Преобразование 
   /// </summary>
   /// <returns>результат преобразования</returns>
   string Convert();

   /// <summary>
   /// Шифрование  
   /// </summary>
   /// <param name="code">код </param>
   /// <returns>результат шифрования</returns>
   string Cipher(string[] code);        
  }

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

/// <summary>
    /// Наследник двух интерфейсов,
    /// у методов которых Convert и Cipher
    /// сигнатуры совпадают.
    /// Методы Cipher склеиваются, 
    /// Convert - переименовываются
    /// </summary>
    class TwoInterfaces:IStrings,ITransform
    {
        //Опущена часть класса, общая с классом SimpleText
        //Реализация интерфейсов                
        string IStrings.Convert()
        {            
            string res = "";
            foreach (char sym in text)            
                if (sym != ' ') res += sym.ToString();
            res = res.ToLower();
            return res;
        }
        string ITransform.Convert()
        {            
            string res = "";
            for (int i = text.Length - 1; i >= 0; i--)
                res += text[i];
            return res;
        }
        //Переименование закрытых методов
        public string ConvertOne()
        {
            return ((IStrings)this).Convert();
        }
        public string ConvertTwo()
        {
            return ((ITransform)this).Convert();
        }
        //Склеивание метода Cipher двух интерфейсов
        public string Cipher(string[] code)
        {
            string s = text;
            string res = "";
            foreach (char sym in s)
            {
                int k = code[0].IndexOf(sym);
                if (k >= 0) res += code[1][k];
                else res += sym.ToString();
            }
            return res;
        }
    }

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

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

public void TestTextTwoInterfaces()
  {
      Console.WriteLine("Работа с объектом класса TwoInterfaces! ");
      TwoInterfaces twoInterfaces = new TwoInterfaces(PAL);
      Console.WriteLine("Исходный текст : " + PAL);
      string text;
      text = twoInterfaces.ConvertOne();
      Console.WriteLine("Первое преобразование  : " + text);
      if (twoInterfaces.IsPalindrom())
          Console.WriteLine("Это палиндром!");
      text = twoInterfaces.ConvertTwo();
      Console.WriteLine("Второе преобразование  : " + text);
      text = twoInterfaces.Coding();
      Console.WriteLine("Шифрованный текст : " + text);
      
      text = "Это простой текст!";
      Console.WriteLine("Исходный текст : " + text);
      twoInterfaces = new TwoInterfaces(text);
      IStrings istrings;
      ITransform itransform;
      istrings = (IStrings)twoInterfaces;
      itransform = (ITransform)twoInterfaces;

      Console.WriteLine("Работа с объектом интерфейса IStrings!");
      text = istrings.Convert();
      Console.WriteLine("Преобразованный текст : " + text);
      text = istrings.Cipher(CODE);
      Console.WriteLine("Шифрованный текст : " + text);

      Console.WriteLine("Работа с объектом интерфейса ITransform!");      
      text = itransform.Convert();
      Console.WriteLine("Преобразованный текст : " + text);
      text = itransform.Cipher(CODE);
      Console.WriteLine("Шифрованный текст : " + text);
  }

Результаты работы показаны на рис. 5.4.

Решение проблемы коллизии имен

Рис. 5.4. Решение проблемы коллизии имен
< Лекция 5 || Лекция 6: 12345 || Лекция 7 >
Федор Антонов
Федор Антонов

Здравствуйте!

Записался на ваш курс, но не понимаю как произвести оплату.

Надо ли писать заявление и, если да, то куда отправлять?

как я получу диплом о профессиональной переподготовке?

Илья Ардов
Илья Ардов

Добрый день!

Я записан на программу. Куда высылать договор и диплом?

Дмитрий Штаф
Дмитрий Штаф
Россия
Дмитрий Слапогузов
Дмитрий Слапогузов
Россия, Бийск