План:
1. Интерфейсы
2. Две стратегии реализации интерфейса
3. Преобразование к классу интерфейса
4. Проблемы множественного наследования
Интерфейсы
Слово " интерфейс " многозначное и в разных контекстах оно имеет различный смысл. В данной лекции речь идет о понятии интерфейса, стоящем за ключевым словом interface. В таком понимании интерфейс - это частный случай класса. Интерфейспредставляет собой полностью абстрактный класс, все методы которого абстрактны. От абстрактного класса интерфейс отличается некоторыми деталями в синтаксисе и поведении. Синтаксическое отличие состоит в том, что методы интерфейса объявляются без указания модификатора доступа. Отличие в поведении заключается в более жестких требованиях к потомкам. Класс, наследующий интерфейс, обязан полностью реализовать все методы интерфейса. В этом - отличие от класса, наследующего абстрактный класс, где потомок может реализовать лишь некоторые методы родительского абстрактного класса, оставаясь абстрактным классом. Но, конечно, не ради этих отличий были введены интерфейсы в язык C#. У них значительно более важная роль.
Введение в язык частных случаев усложняет его и свидетельствует о некоторых изъянах, для преодоления которых и вводятся частные случаи. Например, введение структур в язык C# позволило определять классы как развернутые типы. Конечно, проще было бы ввести в объявление класса соответствующий модификатор, позволяющий любой класс объявлять развернутым. Но этого сделано не было, а, следуя традиции языка С++, были введены структуры как частный случай классов.
Подробнее о развернутых и ссылочных типах см. лекцию 17.
Интерфейсы позволяют частично справиться с таким существенным недостатком языка, как отсутствие множественного наследованияклассов. Хотя реализация множественного наследования встречается с рядом проблем, его отсутствие существенно снижает выразительную мощь языка. В языке C# полного множественного наследования классов нет. Чтобы частично сгладить этот пробел, допускается множественное наследование интерфейсов. Обеспечить возможность классу иметь несколько родителей - один полноценный класс, а остальные в виде интерфейсов, - в этом и состоит основное назначение интерфейсов.
Отметим одно важное назначение интерфейсов. Интерфейс позволяет описывать некоторые желательные свойства, которыми могут обладать объекты разных классов. В библиотеке FCL имеется большое число подобных интерфейсов, с некоторыми из них мы познакомимся в этой лекции. Все классы, допускающие сравнение своих объектов, обычно наследуют интерфейс IComparable, реализация которого позволяет сравнивать объекты не только на равенство, но и на "больше", "меньше".
Две стратегии реализации интерфейса
Давайте опишем некоторый интерфейс, задающий дополнительные свойства объектов класса:
public interface IProps
{
void Prop1(string s);
void Prop2 (string name, int val);
}
У этого интерфейса два метода, которые и должны будут реализовать все классы - наследники интерфейса. Заметьте, у методов нет модификаторов доступа.
Класс, наследующий интерфейс и реализующий его методы, может реализовать их явно, объявляя соответствующие методы класса открытыми. Вот пример:
public class Clain:IProps
{
public Clain() {}
public void Prop1(string s)
{
Console.WriteLine(s);
}
public void Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//Clain
Класс реализует методы интерфейса, делая их открытыми для клиентов класса и наследников. Другая стратегия реализации состоит в том, чтобы все или некоторые методы интерфейса сделать закрытыми. Для реализации этой стратегии класс, наследующий интерфейс, объявляет методы без модификатора доступа, что по умолчанию соответствует модификатору private, и уточняет имя метода именем интерфейса. Вот соответствующий пример:
public class ClainP:IProps
{
public ClainP(){ }
void IProps.Prop1(string s)
{
Console.WriteLine(s);
}
void IProps.Prop2(string name, int val)
{
Console.WriteLine("name = {0}, val ={1}", name, val);
}
}//class ClainP
Класс ClainP реализовал методы интерфейса IProps, но сделал их закрытыми и недоступными для вызова клиентами и наследниками класса. Как же получить доступ к закрытым методам? Есть два способа решения этой проблемы:
· Обертывание. Создается открытый метод, являющийся оберткой закрытого метода.
· Кастинг. Создается объект интерфейсного класса IProps, полученный преобразованием ( кастингом ) объекта исходного класса ClainP. Этому объекту доступны закрытые методы интерфейса.
В чем главное достоинство обертывания? Оно позволяет переименовывать методы интерфейса. Метод интерфейса со своим именем закрывается, а потом открывается под тем именем, которое класс выбрал для него. Вот пример обертывания закрытых методов в классе ClainP:
public void MyProp1(string s)
{
((IProps)this).Prop1(s);
}
public void MyProp2(string s, int x)
{
((IProps)this).Prop2(s, x);
}
Как видите, методы переименованы и получили другие имена, под которыми они и будут известны клиентам класса. В обертке для вызова закрытого метода пришлось использовать кастинг, приведя объект this к интерфейсному классу IProps.
Преобразование к классу интерфейса
Создать объект класса интерфейса обычным путем с использованием конструктора и операции 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.