Здравствуйте! Записался на ваш курс, но не понимаю как произвести оплату. Надо ли писать заявление и, если да, то куда отправлять? как я получу диплом о профессиональной переподготовке? |
Интерфейсы. Множественное наследование
Множественное наследование и его проблемы
Множественным наследованием называется ситуация, когда класс объявляет N классов ( N > 1 ) своими непосредственными родителями. В языке C# есть ограничения на множественное наследование. Ситуация здесь такая. Во-первых, у каждого класса родителем, хотя не всегда непосредственным, является класс object. Во-вторых, каждый класс может явно объявить один класс в качестве непосредственного родителя, а также объявить непосредственными родителями один или несколько интерфейсов. Таким образом, в C# допускается множественное наследование интерфейсов и одиночное (не считая наследования от класса object ) наследование классов.
Во многом ограничение множественного наследования классов связано с тем, что оно создает ряд проблем. Они остаются и при множественном наследовании интерфейсов, хотя становятся проще. Рассмотрим две основные проблемы - коллизию имен и наследование от общего предка.
Коллизия имен
Проблема коллизии имен возникает, когда два или более интерфейса имеют методы с одинаковыми именами и сигнатурой. Если сигнатуры разные, то это не приводит к конфликтам. Класс реализует методы обоих интерфейсов, и у него просто появляются перегруженные методы.
Но что следует делать классу наследнику в тех случаях, когда сигнатуры методов совпадают? Возможны две стратегии - склеивание методов и переименование.
Стратегия склеивания применяется тогда, когда класс - наследник интерфейсов - полагает, что методы разных интерфейсов, имеющие одинаковое имя и сигнатуру, задают один и тот же метод, единая реализация которого и должна быть обеспечена наследником. В этом случае наследник строит единственную общедоступную ( public ) реализацию, соответствующую методам всех интерфейсов c единой сигнатурой.
Стратегия переименования исходит из того, что, несмотря на единую сигнатуру, методы разных интерфейсов должны быть реализованы по-разному. В этом случае необходимо переименовать конфликтующие методы. Конечно, переименование можно сделать в самих интерфейсах, но это неправильный путь: наследники не должны требовать изменений своих родителей - они сами должны меняться. Переименование методов интерфейсов иногда невозможно чисто технически, если интерфейсы являются встроенными или поставляются сторонними фирмами. К счастью, мы уже знаем, как производить переименование метода интерфейса в самом классе наследника. Для этого достаточно реализовать в классе методы разных интерфейсов как закрытые, а затем открыть их в классе с переименованием.
Итак, коллизия имен при множественном наследовании интерфейсов хотя и возможна, но разрешима. Разработчик класса может выбрать одну из двух возможных стратегий, наиболее подходящую для данного конкретного случая.
Наследование от общего предка
Проблема наследования от общего предка характерна, в первую очередь, для множественного наследования классов. Если класс C является наследником классов A и B, а те, в свой черед, являются наследниками класса P, то класс наследует свойства и методы своего предка P дважды: один раз получая их от класса A, другой - от B. Это явление называется еще дублирующим наследованием. Для классов ситуация осложняется тем, что классы A и B могли по-разному переопределить методы родителя и для потомков предстоит сложный выбор реализации. Ситуация дублирующего наследования показана на рис. 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.