Тверской государственный университет
Опубликован: 22.11.2005 | Доступ: свободный | Студентов: 30405 / 1848 | Оценка: 4.31 / 3.69 | Длительность: 28:26:00
ISBN: 978-5-9556-0050-5
Лекция 19:

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

Преобразование к классу интерфейса

Создать объект класса интерфейса обычным путем с использованием конструктора и операции new нельзя. Тем не менее, можно объявить объект интерфейсного класса и связать его с настоящим объектом путем приведения ( кастинга ) объекта наследника к классу интерфейса. Это преобразование задается явно. Имея объект, можно вызывать методы интерфейса - даже если они закрыты в классе, для интерфейсных объектов они являются открытыми. Приведу соответствующий пример, в котором идет работа как с объектами классов Clain, ClainP, так и с объектами интерфейсного класса IProps:

public void TestClainIProps()
{
	Console.WriteLine("Объект класса Clain вызывает 
		открытые методы!");
	Clain clain = new Clain();
	clain.Prop1(" свойство 1 объекта");
	clain.Prop2("Владимир", 44);
	Console.WriteLine("Объект класса IProps вызывает 
		открытые методы!");
	IProps ip = (IProps)clain;
	ip.Prop1("интерфейс: свойство");
	ip.Prop2 ("интерфейс: свойство",77);
	Console.WriteLine("Объект класса ClainP вызывает 
		открытые методы!");
	ClainP clainp = new ClainP();
	clainp.MyProp1(" свойство 1 объекта");
	clainp.MyProp2("Владимир", 44);
	Console.WriteLine("Объект класса IProps вызывает 
		закрытые методы!");
	IProps ipp = (IProps)clainp;
	ipp.Prop1("интерфейс: свойство");
	ipp.Prop2 ("интерфейс: свойство",77);
}

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

Наследование интерфейса. Две стратегии

Рис. 19.1. Наследование интерфейса. Две стратегии

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

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

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

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

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

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

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

public interface IProps
{
	void Prop1(string s);
	void Prop2 (string name, int val);
	void Prop3();
}
public interface IPropsOne
{
	void Prop1(string s);
	void Prop2 (int val);
	void Prop3();
}

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

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.Prop3()
	{
		Console.WriteLine("Свойство 3 интерфейса 1");
	}
	void IPropsOne.Prop3()
	{
		Console.WriteLine("Свойство 3 интерфейса 2");
	}
	public void Prop3FromInterface1()
	{
		((IProps)this).Prop3();
	}
	public void Prop3FromInterface2()
	{
		((IPropsOne)this).Prop3();
	}
}

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

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

public void TestCliTwoInterfaces()
{
	Console.WriteLine("Объект ClainTwo вызывает методы двух интерфейсов!");
	Cli.ClainTwo claintwo = new Cli.ClainTwo();
	claintwo.Prop1("Склейка свойства двух интерфейсов");
	claintwo.Prop2("перегрузка ::: ",99);
	claintwo.Prop2(9999);
	claintwo.Prop3FromInterface1();
	claintwo.Prop3FromInterface2();
	Console.WriteLine("Интерфейсный объект вызывает методы 1-го 
		интерфейса!");
	Cli.IProps ip1 = (Cli.IProps)claintwo;
	ip1.Prop1("интерфейс IProps: свойство 1");
	ip1.Prop2("интерфейс 1 ", 88);
	ip1.Prop3();
	Console.WriteLine("Интерфейсный объект вызывает методы 2-го
		интерфейса!");
	Cli.IPropsOne ip2 = (Cli.IPropsOne)claintwo;
	ip2.Prop1("интерфейс IPropsOne: свойство1");
	ip2.Prop2(7777);
	ip2.Prop3();
}

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

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

Рис. 19.2. Решение проблемы коллизии имен
Александр Галабудник
Александр Галабудник

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

Александра Гусева
Александра Гусева