Интерфейсы

Основные приёмы работы с интерфейсами

Интерфейс — это особый вид класса. Если класс служит для создания объектов каких-либо типов, наделённых неким набором возможностей, то интерфейс предназначен для других целей.

Описание интерфейса во многом напоминает описание класса, но вместо ключевого слова class используется слово interface. По-умолчанию всякий интерфейс являются доступным в сборке (можно использовать слово internal), а можно задать общий доступ (public).

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

Методы, объявленные в интерфейсе, всегда неявно имеют доступ public, но использовать это слово (или какое-нибудь другое, означающее доступ) в объявлении нельзя.

В классе, который наследует от интерфейса, даётся реализация метода, при этом метод объявляется открытым.

Методы, объявленные в интерфейсе, не могут быть виртуальными.

Формально интерфейс с одним методом можно записать таким образом:

interface Имя_интерфейса

{

   Заголовок_метода();

}

Подразумевается, что этот интерфейс содержит один метод. Реализации здесь нет. Она должна быть в классе, который станет наследником интерфейса.

Что нам даёт применение интерфейсов? Это позволяет для разных классов реализовывать однотипный набор действий (методов) с одним видом вызова методов, но при этом реализация методов наверняка будет различной в разных классах.

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

Возможная реализация программы:

using System;

namespace Prim_Interface1

{

   interface IPlayer

   {

      void play();

   }

   public class Ball: IPlayer

   {

      public void play()

      {

         Console.WriteLine("Игра в мяч");

      }

   }

   public class Gitara: IPlayer

   {

      public void play()

      {

         Console.WriteLine("Игра на гитаре");

      }

   }

   class Program

   {

   public static void Main(string[] args)

      {

         Console.WriteLine("Тест одного интерфейса для двух классов");

         Ball x = new Ball();

         x.play();

         Gitara y = new Gitara();

         y.play();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

В нашем примере в интерфейсе IPlayer имеется один метод play(), причём мы записали только заголовок метода. В каждом из классов-наследниках (Ball и Gitara) даём свою реализацию этого метода. В итоге однотипное действие (играть) в объектах разных классов вызывается одним и тем же способом.

Вторая причина использования интерфейсов — отсутствие множественного наследования в языке C#. В этом языке каждый класс может наследовать только от одного класса, а вот количество предков-интерфейсов ничем не ограничено. Поэтому за счёт использования интерфейсов можно создать некое подобие множественного наследования.

Пример 2. Разовьём пример 1. Создадим для классов Ball и Gitara общего наследника — класс Man, при этом наши классы по-прежнему будут наследовать и от интерфейса IPlayer.

Возможная реализация программы:

using System;

namespace Prim_Interface2

{

   interface IPlayer

   {

      void play();

   }

   public class Man

   {

      string name;

      public Man()

      {

         name = "NoName";

      }

      public Man(string n)

      {

         name = n;

      }

      public void print()

      {

         Console.Write(name);

      }

   }

   public class Ball: Man, IPlayer

   {

      public Ball(): base()

      { }

      public Ball(string n): base(n)

      { }

      public void play()

      {

         Console.WriteLine(" - играет в мяч");

      }

   }

   public class Gitara: Man, IPlayer

   {

      public Gitara(): base()

      { }

      public Gitara(string n): base(n)

      { }

      public void play()

      {

         Console.WriteLine("- играет на гитаре");

      }

   }

   class Program

   {

      public static void Main(string[] args)

      {

         //Наследование от одного класса и одного интерфейса для двух классов

         Ball x = new Ball("Иван");

         x.print();

         x.play();

         Gitara y = new Gitara("Андрей");

         y.print();

         y.play();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

Иван - играет в мяч

Андрей- играет на гитаре

Press any key to continue . . .

Во втором примере в классе Man имеется одно поле name и два варианта конструктора. Классы Ball и Gitara наследуют и от класса Man, и от интерфейса IPlayer, причём при описании классов-наследников вначале указывается класс-предок и только потом один или несколько интерфейсов. Здесь пришлось немного изменить тексты классов Ball и Gitara: добавились конструкторы, при этом в них организован вызов подходящего конструктора базового класса (без параметров или с одним параметром).

Как было сказано выше, класс может наследовать от нескольких интерфейсов. Если методы, определённые в интерфейсах, разные, то ни каких проблем быть не может. Но что будет, если в разных интерфейсах имеется один и тот же метод? Можно ли реализовать такое наследование? И если можно, то как? Рассмотрим это на конкретном примере.

Пример 3. Создадим класс Student, который будет наследовать от класса Man и интерфейсов IBall и IGitara (классы Ball и Gitara переделаем в интерфейсы). В этих интерфейсах определим одинаковый метод play(). В общем, наделим объекты типа Student способностью играть в мяч и играть на гитаре (при этом наш «студент» ещё и учится(!!) — у класса Student есть собственный метод study(), отвечающий за этот процесс).

Возможная реализация программы:

using System;

namespace Prim_Interface3

{

   public class Man

   {

      string name;

      public Man()

      {

         name = "NoName";

      }

      public Man(string n)

      {

         name = n;

      }

      public void print()

      {

         Console.Write(name);

      }

   }

   interface IBall

   {

      void play();

   }

   interface IGitara

   {

      void play();

   }

   public class Student: Man, IBall, IGitara

   {

      public Student(): base()

      { }

      public Student(string n): base(n)

      { }

      void IBall.play()

      {

         Console.WriteLine(" - играет в мяч");

      }

      void IGitara.play()

      {

         Console.WriteLine(" - играет на гитаре");

      }

      public void study()

      {

         Console.WriteLine(" - учится в институте, а также");

      }

   }

   class Program

   {

      public static void Main(string[] args)

      {

         // Наследование от класса и двух интерфейсом, имеющих метод с одинаковым именем

         Student x = new Student("Иван");

         x.print();

         x.study();

         ((IBall)x).play();

         ((IGitara)x).play();

         Console.WriteLine();

         Student y = new Student("Пётр");

         y.print();

         y.study();

         IBall i1 = y;

         i1.play();

         IGitara i2 = y;

         i2.play();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

Иван - учится в институте, а также

- играет в мяч

- играет на гитаре


Пётр - учится в институте, а также

- играет в мяч

- играет на гитаре

Press any key to continue . . .

На что надо обратить внимание, изучая текст этой программы? В интерфейсах IBall и IGitara определён одинаковый метод play(). Это вполне допустимо и ни каких проблем не вызывает.

Но как реализовать в классе Student два метода с одинаковыми заголовками? Перегрузка здесь невозможна, так как перегрузить можно методы с одинаковыми именами, но разной сигнатурой. В нашем случае сигнатуры тоже одинаковы. Остаётся только одно: учесть тот факт, что методы наследуются от разных интерфейсов. Мы явно указываем при реализации метода в классе-наследнике Student название того интерфейса, от которого идёт наследование (перед названием метода через точку записывается название интерфейса), например:

1)наследуем от интерфейса IBall:

void IBall.play()

{

   Console.WriteLine(" - играет в мяч");

}

2)наследуем от интерфейса IGitara:

void IGitara.play()

{

   Console.WriteLine(" - играет на гитаре");

}

Теперь возникает вторая проблема: как вызывать такие методы? Выход может быть только один: перед вызовом метода сделать приведение объекта класса Student к типу интерфейса, для которого был описан метод.

Например, для вызова метода play() из интерфейса IBall, который реализован в классе Student как

void IBall.play()

{

   Console.WriteLine(" - играет в мяч");

}

необходимо переменную x класса Student привести к типу интерфейса IBall:

((IBall)x).play();

Здесь выражение (IBall)x необходимо взять в скобки, так как сначала приводим объект к базовому интерфейсу и только потом вызываем метод.

Другой подход — создать ссылку на нужный интерфейс, затем настроить её на нужный объект (это делается в программе для переменной y):

IBall i1 = y;

Здесь ссылка i1 типа интерфейса IBall настроена на объект y класса Student.

А теперь начинаем пользоваться этой ссылкой, т.е. вызывать нужный метод play():

i1.play();

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

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

Любой интерфейс может наследовать от любого количества других интерфейсов. Для демонстрации этого переделаем предыдущий пример 3.

Пример 4. Пусть теперь наш «студент» перевёлся на вечернее отделение. Класс Student преобразуем в интерфейс IStudent, который будет по-прежнему наследовать от интерфейсов IBall и IGitara. Основным классом станет класс Worker, который будет наследовать от класса Man и интерфейса IStudent.

Возможная реализация программы:

using System;

namespace Prim_Interface4

{

   public class Man

   {

      string name;

      public Man()

      {

         name = "NoName";

      }

      public Man(string n)

      {

         name = n;

      }

      public void print()

      {

         Console.Write(name);

      }

   }

   interface IBall

   {

      void play();

   }

   interface IGitara

   {

      void play();

   }

   interface IStudent: IBall, IGitara

   {

      void study();

   }

   public class Worker: Man, IStudent

   {

      public Worker(): base()

      { }

      public Worker(string n): base(n)

      { }

      void IBall.play()

      {

         Console.WriteLine(" - играет в мяч");

      }

      void IGitara.play()

      {

         Console.WriteLine(" - играет на гитаре");

      }

      public void study()

      {

         Console.WriteLine(" - учится в институте, а также");

      }

      public void work()

      {

         Console.WriteLine("\n - работает,");

      }

   }

   class Program

   {

      public static void Main(string[] args)

      {

         Worker x = new Worker("Иван");

         x.print();

         x.work();

         x.study();

         ((IBall)x).play();

         ((IGitara)x).play();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

Иван

- работает,

- учится в институте, а также

- играет в мяч

- играет на гитаре

Press any key to continue . . .

Как видим, всё делается достаточно просто, т.е. по аналогии с примером 3, в котором мы решили проблему с интерфейсами-предками, содержащими метод с одинаковым заголовком.

Но что произойдёт, если интерфейс IStudent также будет содержать метод play(), который есть в интерфейсах-предках (IBall и IGitara)?

Пример 5. Пусть наш «студент-вечерник» из примера 4 стал играть в КВН. Для реализации такой возможности добавим в интерфейс IStudent метод play(), означающий игру в КВН.

Возможная реализация программы:

using System;

namespace Prim_Interface5

{

   public class Man

   {

      string name;

      public Man()

      {

         name = "NoName";

      }

      public Man(string n)

      {

         name = n;

      }

      public void print()

      {

         Console.Write(name);

      }

   }

   interface IBall

   {

      void play();

   }

   interface IGitara

   {

      void play();

   }

   interface IStudent: IBall, IGitara

   {

      void study();

      new void play();

   }

   public class Worker: Man, IStudent

   {

      public Worker(): base()

      { }

      public Worker(string n): base(n)

      { }

      void IBall.play()

      {

         Console.WriteLine(" - играет в мяч");

      }

      void IGitara.play()

      {

         Console.WriteLine(" - играет на гитаре");

      }

      public void study()

      {

         Console.WriteLine(" - учится в институте, а также");

      }

      void IStudent.play()

      {

         Console.WriteLine(" - играет в КВН");

      }

      public void work()

      {

         Console.WriteLine("\n - работает,");

      }

   }

   class Program

   {

      public static void Main(string[] args)

      {

         Worker x = new Worker("Иван");

         x.print();

         x.work();

         x.study();

         ((IBall)x).play();

         ((IGitara)x).play();

         ((IStudent)x).play();

         Console.Write("Press any key to continue . . . ");

         Console.ReadKey(true);

      }

   }

}

Результат работы программы:

Иван

- работает,

- учится в институте, а также

- играет в мяч

- играет на гитаре

- играет в КВН

Press any key to continue . . .

И здесь всё достаточно просто. В интерфейсе IStudent с помощью указания слова new перекрываем описание метода play(), который есть и в интерфейсах-предках:

interface IStudent: IBall, IGitara

{

   void study();

   new void play();

}

Способ реализации метода play(), наследуемого из интерфейса IStudent, в классе Worker ничем не отличается от реализации других методов play(), наследуемых от интерфейсов IBall и IGitara. Использование методов также делается по аналогии.



Назад
На верх
Вперёд
Hosted by uCoz