Высокоуровневые методы информатики и программирования Лекция 14 Интерфейсы План работы • • • • • • Абстрактные классы Понятие интерфейса – interface Реализация интерфейсов Использование интерфейсов Стандартные интерфейсы. Поддержка работы с интерфейсами в Visual Studio. Абстрактный класс • Если мы ни хотим, чтобы можно было создавать экземпляры класса, то его нужно объявить абстрактным – abstract public abstract class MyClass { ... } … MyClass a = new MyClass(); // ошибка! • Для абстрактного класса не могут быть созданы экземпляры; он используется для создания производных классов; • Единственная реальная польза от абстрактного класса состоит в том, что он описывает общие элементы (поля, методы) для всех производных от него классов. public class ClassA : MyClass { . . . } // нет абстрактных методов … ClassA a = new ClassA(); // правильно! Абстрактные методы • Абстрактный класс может иметь абстрактные методы – методы только с заголовком, без описания public abstract class MyClass { ... public abstract void MethodA( ); ... } • • • абстрактный метод не может иметь модификатор virtual (он всегда виртуальный) класс, наследующего абстрактный класс, может реализовать лишь некоторые его абстрактные методы, оставаясь абстрактным классом; класс, наследующего абстрактный класс, может наследоваться только от одного класса. public abstract class ClassA : MyClass { ... public void MethodA( ) { Console.Write(“ClassA”); } ... } Интерфейс • Интерфейсы это еще один пользовательский тип. • Интерфейсы позволяют описывать некоторые желательные свойства и методы, которыми должны обладать разные классы. • Особенности интерфейсов: – можно описывать переменные такого типа, ссылочный тип; – нельзя создавать объекты (экземпляры) такого типа; – включает набор абстрактных открытых (public) методов. • То, что интерфейс не может иметь реализации, означает, что все его методы и свойства являются абстрактными. • Интерфейс описывает поведение, которое конкретный класс или структура может выбрать для реализации. • Класс (или структура) может при необходимости поддерживать много интерфейсов, тем самым поддерживая множество стилей поведения. • Интерфейс (interface) во много аналогичен именованному набору абстрактных методов. Интерфейс • Интерфейс – это открытый контракт между поставщиком услуг (методов) и потребителем этих услуг. • Интерфейс позволяет организовать свободную связь между клиентом и объектом (т.е. можно указать, что требуемый объект поддерживает некоторый интерфейс). • Интерфейс представляет собой полностью абстрактный класс, который может включать только открытые абстрактные – – – – методы, свойства, индексаторы события. Описание интерфейса <режим доступа> interface <имя_интерфейса> { <тип_результата> имя_метода1(<параметры>); // < тип_результата> имя_методаN(<параметры>); } Реализация интерфейса • Интерфейс может быть реализован в описании класса. • Для этого нужно указать, что класс является наследником интерфейса: public class MyClass : IMyInterface { public void Method1( ) {...} public void Method2( ) {...} public void Method3( ) {...} //другие элементы класса } • Если класс наследует интерфейс или интерфейсы, то в нем должны быть реализованы (неявно или явно) все методы входящие в данный интерфейс. • При описании методов реализующих методы интерфейса, не нужно использовать такие модификаторы, как new или override. • Для работы с объектами с помощью интерфейса нужно создать экземпляр класса, реализующий этот интерфейс, и присвоить его переменной, типом которой является название интерфейса. • Например: IMyInterface obj; obj = new MyClass( ); obj.Method1( ); или можно вызывать методы напрямую MyClass obj; obj = new MyClass( ); obj.Method1( ); Работа с интерфейсом • Описание интерфейса - Методы интерфейса объявляются без указания модификаторов доступа. interface IScalable { void ScaleX(float factor); void ScaleY(float factor); } • Реализация интерфейса (класс Map реализует – поддерживает интерфейс IScalable) – override не указывается class Map : IScalable { public void ScaleX(float factor) {. . .}; public void ScaleY(float factor) {. . .}; } • Использование интерфейса Map map1 = new Map( ); map1.ScaleX(100f); map1.ScaleY(200f); IScale intrf; intrf = (IScale) map1; // преобразование к интерфейсу intrf.ScaleX(200f); Соответствие интерфейса и абстрактного класса • интерфейс public interface IMyInterface { int Method1( ); bool Method2( ); void Method3( ); } • почти эквивалентен абстрактному классу public abstract class MyInterface { int abstract void Method1( ); bool abstract void Method2( ); void abstract void Method3( ); } Различие абстрактного класса и интерфейса 1. Абстрактный класс может иметь данные и не абстрактные методы. Интерфейс может иметь только описания свойств и методов, без их реализации. 2. Класс С# может наследоваться только от одного базового класса, даже если он абстрактный. Но класс C#, может реализовать (иметь наследование) много интерфейсов. 3. Абстрактный класс может быть производным от любого другого класса или от одного и более интерфейсов. Интерфейс может наследоваться только от других интерфейсов. 4. Абстрактный класс может иметь не открытые элементы (private или protected) методы и свойства. Интерфейс имеет только открытые методы. 5. Абстрактный класс может иметь статические методы и поля. Интерфейс может иметь только методы. 6. Абстрактный класс может иметь конструктор. Интерфейс не может его иметь. Ошибки описания интерфейса! public interface IPointy { // Ошибка! Интерфейс не может иметь полей! public int numbOfPoints; // Ошибка! Интерфейс не может иметь конструкторы! public IPointy() { numbOfPoints = 0;}; // Ошибка! Интерфейс не содержит реализации методов! byte GetNumberOfPoints() { return numbOfPoints; } } Работа с интерфейсом • Класс, наследующий интерфейс, обязан полностью реализовать все методы интерфейса. • Объекты данного класса могут использоваться везде, где требуется данный интерфейс. • Может быть множественное наследование от интерфейсов. • В классе производном от интерфейса можно выполнить неявную и явную реализацию интерфейса. Неявная реализация интерфейса // определение интерфейса public interface IMyInterface { void Method1( ); void Method2( ); } // реализация интерфейса public class MyClass : IMyInterface { public void Method1( ) {...} // не указывается, что это методы интерфейса public void Method2( ) {...} // неявное определение } … //другие элементы класса // можно вызывать и через // интерфейс IMyInterface obj; obj = new MyClass( ); obj.Method1( ); // а можно и как методы // класса MyClass obj; obj = new MyClass( ); obj.Method1( ); Явная реализация интерфейса public interface IMyInterface { void Method1( ); void Method2( ); } public class MyClass : IMyInterface { void IMyInterface.Method1( ) {...} // указывается, что это методы интерфейса void IMyInterface.Method2( ) {...} // тип доступа public или private, не задается //другие элементы класса } // в этом случае метод можно вызвать // только через интерфейс IMyInterface obj; obj = new MyClass( ); obj.Method1( ); // у экземпляра класс // вызывать эти методы уже нельзя MyClass obj; obj = new MyClass( ); obj.Method1( ); Определение и использование нескольких интерфейсов public interface IMyInterface { void Method1( ); void Method2( ); } public interface IMyOtherInterface { void Method3( ); } public class MyClass : IMyInterface,IMyOtherInterface { public void Method1( ) {...} public void Method2( ) {...} public void Method3( ) {...} } //Client-side code: IMyInterface obj1; IMyOtherInterface obj2; obj1 = new MyClass( ); obj1.Method1( ); obj2 = (IMyOtherInterface)obj1; obj2.Method3( ); Приведение к типу интерфейса • • • Для применения интерфейса нужно выполнить преобразование объекта, который его реализует, в ссылочную переменную интерфейсного типа. Имеются два типа преобразования типа: неявное и явное. При простом присвоении интерфейсной переменной экземпляра класса выполняется неявное преобразование типов: IMyInterface obj; obj = new MyClass( ); obj.Method1( ); • • Если класс MyClass не реализует интерфейс IMyInterface, то при компиляции будет выдаваться сообщение об ошибке, так как компилятор может читать метаданные класса и поэтому может заранее определить, реализует ли класс заданный интерфейс. Существуют ситуации, когда нельзя использовать неявное преобразование. В этих случаях нужно использовать явное преобразование (кастинг): IMyInterface obj; // ... obj = (IMyInterface)new MyClass( ); obj.Method1( ); • Если объект, к которому применяется явное преобразование, не поддерживает требуемый интерфейс, то во время выполнения будет формироваться исключение (exception) и если его не обработать, то работа программы завершаться аварийно. Проверка на поддержку интерфейса • • Для того, чтобы избежать таких ситуаций нужно использовать операцию as. Например: IMyInterface obj; MyClass с = new MyClass( ); obj = с as IMyInterface; • Операция as выполняет преобразование к требуемому интерфейсу, если объект поддерживает этот интерфейс, и присваивает полученное значение переменной. • Если преобразование невозможно, то вместо генерации исключения (как это происходит при кастинге), данная операция присваивает интерфейсной переменной значение null. Например: SomeType obj1; IMyIntee obj2; // некоторый код для инициализации obj1 obj2 = obj1 as IMyInterface; if(obj2 != null) { obj.Method1( ); //переменная имеет верное значение } else { // объект obj1 не ддерживает интерфейс IMyIntee //обработка ошибки } Операторы проверки интерфейсов • оператор is (true, если в классе реализован интерфейс, иначе false) <объект> is <интерфейс> – Например if (d is IScalable) {…} • оператор as (получение ссылки на заданный интерфейс, если интерфейса нет, то null) <объект> as <интерфейс> – Например IScalable s = d as IScalable; if (s != null) { … } Пример использования SomeType obj1; IMyInterface obj2; /* Some code to initialize obj1 */ obj2 = obj1 as IMyInterface; if(obj2 != null) { obj.Method1( ); } else { // обработка ошибки – нет интерфейса } Работа с интерфейсами в Visual Studio 2008 1. Реализация интерфейса 2. Создание интерфейса для описанного класса • в контекстном меню Refactor выбрать команду Extract Interface... • диалоговом окне Extract Interface выделить те методы, которые войдут в интерфейс Сортировка элементов массива • Имеется метод сортировки элементов массива: Array.Sort (<массив>); • Для использования данного метода необходимо, чтобы класс элементов массива поддерживал интерфейс IComparable. Интерфейс IComparable Включает только один метод: int CompareTo ( Object obj ) • Если – <0, данный объект меньше, чем obj – =0, объекты равны – >0, данный объект больше, чем obj Упорядочение объектов класса Point с использованием интерфейса IComparable public class Point : IComparable { int IComparable.CompareTo(object obj) { Point p = (Point)obj; if(this.x == p.x) return 0; if(this.x > p.x) return 1; else return -1; } ... } Упорядочение объектов класса Person с использованием интерфейса IComparable public class Person : IComparable { string firstName, lastName; public int CompareTo(object obj) { Person otherPerson = (Person)obj; if (this.lastName != otherPerson.lastName) return this.lastName.CompareTo(otherPerson.lastName); else return this.firstName.CompareTo (otherPerson.firstName); } public Person(string _firstName, string _lastName){ firstName = _firstName; lastName = _lastName; } override public string ToString() { return firstName + " " + lastName; } } Упорядочение объектов класса Car с использованием интерфейса IComparable public class car : IComparable { // поля класса private int year; private string make; // конструктор public car(string Make,int Year) { make=Make; year=Year; } // свойства public int Year { get {return year;} set {year=value;} } public string Make { get {return make;} set {make=value;} } // Реализация интерфейса IComparable // метод CompareTo для сортировки по умолчанию. int IComparable.CompareTo(object obj) { car c=(car)obj; return String.Compare(this.make,c.make); } } Интерфейс IComparer Включает только один метод, который сравнивает два объекта: int Compare(object obj1, object obj2); • Если – <0, obj1 меньше, чем obj2 – =0, obj1 равен obj2 – >0, obj1 больше, чем obj2 • Используется с такими методами, как Array.Sort() и Array.BinarySearch(). Array.Sort (<массив>, [ <comparer>]); • Для использования данного интерфейса нужно создать объект класса, который реализует данный интерфейс и передать его в качестве второго параметра. Вспомогательные классы с интерфейсом IComparer // Вспомогательный класс для сортировки по увеличению свойства Year. class sortYearAscending : IComparer { int IComparer.Compare(object a, object b) { car c1 = (car)a; car c2 = (car)b; if (c1.Year > c2.Year) return 1; if (c1.Year < c2.Year) return -1; else return 0; } } // Вспомогательный класс для сортировки по уменьшению свойства Make. class sortMakeDescending : IComparer { int IComparer.Compare(object a, object b) { car c1 = (car)a; car c2 = (car)b; return String.Compare(c2.Make, c1.Make); } } Пример использования интерфейса IComparer static void Main(string[] args) { // Создаем массив объектов car. car[] arrayOfCars = new car[6]{ new car("Ford",1992), new car("Fiat",1988), new car("Buick",1932), new car("Ford",1932), new car("Dodge",1999), new car("Honda",1977)}; // выводим не отсортированный массив. foreach (car c in arrayOfCars) Console.WriteLine(c.Make + "\t\t" + c.Year); // Сортируем с использованием IСomparable (по умолчанию). Array.Sort(arrayOfCars); Console.WriteLine("\nМассив отсортирован по возрастанию Make (IComparable)\n"); foreach (car c in arrayOfCars) Console.WriteLine(c.Make + "\t\t" + c.Year); // Сортировка по возрастанию Year с помощью IСomparer. Array.Sort (arrayOfCars, new sortYearAscending()); Console.WriteLine("\nМассив отсортирован по убыванию Year(IComparer)\n"); foreach (car c in arrayOfCars) Console.WriteLine(c.Make + "\t\t" + c.Year); // Сортировка по убыванию Make с помощью IComparer. Array.Sort(arrayOfCars, new sortMakeDescending()); Console.WriteLine("\nМассив отсортирован по убыванию Make(IComparer)\n"); foreach (car c in arrayOfCars) Console.WriteLine(c.Make + "\t\t" + c.Year); Console.ReadLine(); } Интерфейс ICloneable • Один метод, который возвращает копию текущего объекта Object Clone()