Практическая работа №13
Тема: Реализация полиморфизма на основе интерфейсов
Цель работы: изучение и понимание концепции полиморфизма в объектно-ориентированном программировании (ООП) с акцентом на реализацию полиморфизма через интерфейсы. Краткие теоретические сведения Назначение интерфейсов. Особенности применения интерфейсов в C#
Интерфейс определяет ряд методов (свойств, индексаторов, событий), которые должны быть реализованы в классе, который наследует (реализует) данный интерфейс. Интерфейсы используются для того, чтобы указать классам, что именно нужно реализовать в этих классах. Реализовывать нужно методы (свойства, индексаторы, события). Таким образом, интерфейс описывает функциональные возможности без конкретной реализации. Иными словами интерфейс определяет спецификацию но не реализацию.
Использование интерфейсов есть эффективным в случаях, когда нужно создать альтернативу множественного наследования. Любой класс может унаследовать несколько интерфейсов. При этом все методы унаследованных интерфейсов должны быть реализованы в классе.
Структура также как и класс может реализовывать любое количество интерфейсов.
Особенности интерфейсов
в интерфейсе нельзя вписывать реализацию его элементов;
невозможно создать экземпляр интерфейса;
можно создать ссылку на интерфейс;
в интерфейсе не может быть конструкторов;
интерфейс не может содержать поля;
в интерфейсе не может быть осуществлена перегрузка операторов;
все методы интерфейса по умолчанию объявлены как public. При использовании интерфейсов в классах-наследниках:
запрещено изменять модификатор доступа для метода при его реализации;
невозможно объявить методы интерфейса как virtual;
запрещено объявлять методы интерфейса с ключевым словом static (как статические).
Какое отличие между интерфейсами и абстрактными классами?
В языке программирования C# существуют следующие отличия между интерфейсами и абстрактными классами:
В интерфейсе запрещено прописывать реализацию его членов. В абстрактном классе часть членов может иметь реализацию. Иными словами, интерфейс это тот же абстрактный класс, у которого все методы абстрактные.
В интерфейсе запрещено описывать поля (переменные, объекты), в абстрактном классе можно.
В интерфейсе запрещено объявление перегруженных операторов. В абстрактном классе при соблюдении некоторых условий допускается объявление перегруженных операторов.
Интерфейс не может содержать конструктор. В абстрактном классе может быть объявлен конструктор.
Любой класс может быть унаследован от нескольких интерфейсов. При этом любой класс может быть унаследован только от одного абстрактного класса (и не более).
В интерфейсе запрещено объявлять статические элементы (с ключевым словом static). В абстрактном классе допускается объявление статических элементов.
В интерфейсе все элементы считаются как public, и поэтому модификаторы доступа в интерфейсе не используются. В абстрактном классе элементы могут быть объявлены с любым модификатором доступа (private, protected, public).
Сколько классов могут иметь реализацию методов интерфейса?
Если интерфейс определен, то он может быть реализован в любом количестве классов.
Сколько интерфейсов может быть реализовано в одном классе?
В одном классе может быть реализовано любое количество интерфейсов.
Какой общий вид описания интерфейса?
Интерфейсы объявляются с помощью ключевого слова interface. Общая форма описания интерфейса, в котором определяются методы, следующая:
interface имя
{
возвращаемый_тип1 имя_метода1(параметры1); возвращаемый_тип2 имя_метода2(параметры2);
// ...
возвращаемый_типN имя_методаN(параметрыN);
}
где
имя – конкретное имя интерфейса;
имя_метода1, имя_метода2, …, имя_методаN – имена методов интерфейсов;
возвращаемый_тип1, возвращаемый_тип2, …, возвращаемый_типN – типы, которые возвращаются методами интерфейса;
параметры1, параметры2, …, параметрыN – списки параметров методов интерфейса.
Кроме методов, в интерфейсах можно указывать свойства, события и индексаторы.
Какие элементы языка программирования можно указывать в интерфейсах?
В интерфейсах можно указывать:
методы;
свойства;
индексаторы;
события.
Как выглядит общая форма реализации интерфейса в классе?
Общая форма реализации интерфейса в классе имеет следующий вид:
class имя_класса : имя_интерфейса
{
// тело класса
...
}
где имя_интерфейса – имя интерфейса, методы (свойства, индексаторы, события) которого реализуются в классе. Класс обязательно должен реализовать все методы интерфейса.
Какая общая форма класса реализующего несколько интерфейсов?
Класс может реализовать несколько интерфейсов. В этом случае все интерфейсы определяются списком через запятую.
Общая форма класса реализующего несколько интерфейсов:
class имя_класса : имя_интерфейса1, имя_интерфейса2, ..., имя_интерфейсаN
{
// тело класса
...
}
где имя_интерфейса1, имя_интерфейса2, …, имя_интерфейсаN – имена интерфейсов, которые должен реализовать класс. Класс должен реализовать все методы всех интерфейсов.
Пример объявления интерфейса и класса наследующего этот интерфейс
В данном примере интерфейсу присваивается имя IMyInterface. Рекомендовано к имени интерфейса добавить префикс ‘I’ в соответствии с общераспространенной практикой.
Интерфейс объявлен как public.
public interface IMyInterface
{
int MyGetInt(); // метод, возвращающий число типа int double MyGetPi(); // метод, возвращающий число Pi
int MySquare(int x); // метод, возвращающий x в квадрате
double MySqrt(double x); // метод возвращающий корень квадратный из x
}
В данном примере, в интерфейсе объявлено описание четырех методов, которые должны быть реализованы во всех классах, определяющих эти интерфейсы. Это методы: MyGetInt(), MyGetPi(), MySquare(), MySqrt().
Пример описания класса использующего этот интерфейс.
public class MyClass : IMyInterface
{
// модификатор доступа public public int MyGetInt()
{
return 25;
}
public double MyGetPi()
{
return Math.PI;
}
public int MySquare(int x)
{
return (int)(x * x);
}
public double MySqrt(double x)
{
return (double)Math.Sqrt(x);
}
}
Все методы, которые определяются в классе, должны иметь тип доступа public. Если установить другой тип доступа (private или protected), то Visual Studio выдаст следующее сообщение:
"MyClass does not implement interface member MyFun() because it is not public."
где MyFun() – название функции, которая реализована в классе с модификатором доступа private или protected.
Это связано с тем, что в самом интерфейсе эти методы неявно считаются открытыми (public). Поэтому их реализация должна быть открытой.
Пример объявления двух интерфейсов и класса, который реализует методы этих интерфейсов
В нижеследующем примере объявлено два интерфейса с именами MyInterface и MyInterface2. Первый интерфейс содержит 4 методы. Второй интерфейс содержит 1 метод.
Также объявлен класс MyClass, использующий эти два интерфейса. Класс
обязательно должен реализовать все методы обоих интерфейсов, то есть в сумме 5 методов.
public interface IMyInterface
{
int MyGetInt(); // метод, возвращающий число типа int double MyGetPi(); // метод, возвращающий число Pi
int MySquare(int x); // метод, возвращающий x в квадрате
double MySqrt(double x); // метод, возвращающий корень квадратный из x
}
public interface IMyInterface2
{
double MySqrt2(double x); // корень квадратный из x
}
public class MyClass : IMyInterface, IMyInterface2
{
// методы из интерфейса MyInterface public int MyGetInt()
{
return 25;
}
public double MyGetPi()
{
return Math.PI;
}
public int MySquare(int x)
{
return (int)(x * x);
}
public double MySqrt(double x)
{
return (double)Math.Sqrt(x);
}
// метод из интерфейса MyInterface2 public double MySqrt2(double x)
{
return (double)Math.Sqrt(x);
}
}
Пример использования ссылки на интерфейс для доступа к методам класса
В C# допускается описывать ссылки на интерфейс. Если описать переменную- ссылку на интерфейс, то с ее помощью можно вызвать методы класса, который использует этот интерфейс.
Пример.
public interface IMyInterface
{
double MyGetPi(); // метод, возвращающий число Pi
}
class MyClass : IMyInterface
{
// методы из интерфейса MyInterface public double MyGetPi()
{
return Math.PI;
}
}
// вызов из программного кода
private void button1_Click(object sender, EventArgs e)
{
MyClass mc = new MyClass(); // создание объекта класса mc IMyInterface mi; // ссылка на интерфейс
double d;
mi = mc; // mi ссылается на объект класса mc d = mi.MyGetPi(); // d = 3.14159265358979
label1.Text = d.ToString();
}
В данном примере создается объект (экземпляр) класса MyClass с именем mc. Затем описывается ссылка на интерфейс IMyInterface с именем mi.
Строка
mi=mc;
приводит к тому, что ссылка mi указывает на объект класса mc. Таким образом, через ссылку mi можно иметь доступ к методам класса MyClass, так как класс MyClass реализует методы интерфейса IMyInterface.
С помощью ссылки на интерфейс можно иметь доступ к методам классов, которые реализуют описанные в этом интерфейсе методы.
Каким образом в интерфейсе описывается свойство?
Свойство описывается в интерфейсе без тела. Общая форма объявления интерфейсного свойства следующая:
тип имя_свойства
{
get; set;
}
Если свойство предназначено только для чтения, то используется один только аксессор get.
Если свойство предназначено для записи, то используется только один аксессор set.
Пример. Описывается интерфейс и класс. Класс возвращает свойство MyPi.
public interface IMyInterface
{
double MyGetPi(); // метод, возвращающий число Pi
// свойство, возвращающее число Pi double MyPi
{
get;
}
}
class MyClass : IMyInterface
{
// метод
public double MyGetPi()
{
return Math.PI;
}
// реализация свойства в классе public double MyPi
{
get
{
}
}
}
return Math.PI;
// использование интерфейсного свойства в обработчике события клика на кнопке private void button1_Click(object sender, EventArgs e)
{
MyClass mc = new MyClass(); // создание объекта класса mc
label1.Text = mc.MyPi.ToString(); // чтение свойства
}
Пример интерфейса, в котором описывается индексатор.
Общая форма объявления интерфейсного индексатора имеет вид:
тип this[int индекс]
{
get; set;
}
Пример описания и использования интерфейсного индексатора, который считывает элемент из массива, состоящего из 5 элементов типа double.
public interface IMyInterface
{
// интерфейсный индексатор double this[int index]
{
get;
}
}
class MyClass : IMyInterface
{
double[] mas = { 3, 2.9, 0.5, 7, 8.3 }; public double this[int index]
{
get
{
}
}
}
return mas[index];
private void button1_Click(object sender, EventArgs e)
{
MyClass mc = new MyClass(); // создание объекта класса mc double d;
d = mc[2]; // d = 0.5 label1.Text = d.ToString();
}
Какие элементы программирования языка C# нельзя описывать в интерфейсах?
Интерфейсы не могут содержать:
члены данных;
конструкторы;
деструкторы;
операторные методы.
Как работает механизм наследования интерфейсов?
Интерфейс может наследовать другой интерфейс. Синтаксис наследования интерфейсов такой же, как и у классов.
Общая форма наследования интерфейса следующая:
interface имя_интерфейса : имя_интерфейса1, имя_интерфейса2, ..., имя_интерфейсаN
{
// методы, свойства, индексаторы и события интерфейса
...
}
где имя_интерфейса – имя интерфейса, который наследует другие интерфейсы; имя_интерфейса1, имя_интерфейса2, …, имя_интерфейсаN – имена интерфейсов- предков.
Пример. В данном примере класс MyClass использует интерфейс, который наследует другой интерфейс. В классе нужно реализовать все методы (свойства, индексаторы, события) интерфейса MyInterface1 и интерфейса MyInterface2.
// базовый интерфейс interface MyInterface1
{
void Int1_Meth();
}
// интерфейс, который наследует другой интерфейс interface MyInterface2 : MyInterface1
{
void Int2_Meth();
}
// класс, который использует интерфейс MyInterface2 class MyClass : MyInterface2
{
// реализация метода интерфейса MyInterface1 public void Int1_Meth()
{
// тело метода
// ...
return;
}
// реализация метода интерфейса MyInterface2 public void Int2_Meth()
{
// ...
}
}
// тело метода return;
Что такое явная реализация члена интерфейса?
Если перед именем метода (свойства, индексатора, события) стоит имя интерфейса через разделитель ‘ . ‘ (точка), то это называется явной реализацией члена интерфейса.
Пример явной реализации.
// базовый интерфейс interface MyInterface1
{
void Method();
}
// класс, который реализует интерфейс MyInterface1 class MyClass : MyInterface1
{
// явная реализация метода интерфейса MyInterface1
void MyInterface1.Method() // указывается имя интерфейса
{
// ...
}
}
// тело метода return;
Когда целесообразно применять явную реализацию члена интерфейса? Примеры.
Явная реализация члена интерфейса применяется в следующих случаях:
когда нужно, чтобы интерфейсный метод был доступен по интерфейсной ссылке, а не по объекту класса, реализующего данный интерфейс. В этом случае интерфейсный метод не является открытым (public) членом класса (см. пример 1);
когда в одном классе реализованы два интерфейса, в которых методы имеют одинаковые имена и сигнатуру (см. пример 2).
Пример 1. Явная реализация интерфейсного метода. По интерфейсной ссылке метод есть доступен, а по объекту класса недоступен.
// Интерфейс
interface MyInterface1
{
void Method();
}
// класс, вызывающий интерфейс class MyClass : MyInterface1
{
// явная реализация метода интерфейса MyInterface1
// модификатор доступа, должен отсутствовать
void MyInterface1.Method() // указывается имя интерфейса
{
// тело метода
// ... return;
}
void InternalMethod()
{
MyInterface1 mi = this; // mi - интерфейсная ссылка mi.Method(); // работает!
MyClass mc = this; // mc - объект класса MyClass
// mc.Method() - невозможно вызвать - метод не открыт для объекта
}
}
Пример 2. Есть два интерфейса MyInterface1 и MyInterface2. Каждый из них имеет методы с одинаковыми именами и сигнатурами. В данном случае это метод Method(), не возвращающий параметров (void). С помощью явной реализации класс распознает эти методы.
// интерфейс 1 interface MyInterface1
{
void Method();
}
// интерфейс 2 interface MyInterface2
{
void Method();
}
// класс, который использует два интерфейса class MyClass : MyInterface1, MyInterface2
{
// явная реализация - модификатор доступа (public) должен отсутствовать
// метод из интерфейса MyInterface1 void MyInterface1.Method()
{
// тело метода
// ...
return;
}
// метод из интерфейса MyInterface2 void MyInterface2.Method()
{
// тело метода
// ... return;
}
}
В каких случаях лучше использовать интерфейс, а в каких абстрактный класс?
Интерфейс целесообразно использовать в случаях, если некоторые понятия должны быть описаны с точки зрения функционального назначения, без уточнения деталей реализации.
Абстрактный класс целесообразно использовать тогда, когда все же нужно уточнять некоторые детали реализации.
Контрольные вопросы:
Как описывается интерфейс? Его назначение.
Какие члены может содержать интерфейс?
Какие спецификаторы допустимы у методов, реализующих интерфейс?
В каких случаях используется явная реализация интерфейса?
Как осуществляется наследование интерфейсов?
Можно ли явно реализованные методы объявлять виртуальными?
Можно ли повторно реализовать интерфейс, указав его имя в списке предков класса наряду с классом-предком?
Какие стандартные интерфейсы используются для работы с кол лекциями?