Зачем нужны интерфейсы c
Перейти к содержимому

Зачем нужны интерфейсы c

  • автор:

Зачем нужны интерфейсы?

Зачем нужны интерфейсы? Зачем нужно наследование интерфейсов? Зачем нужен полиморфизм? Для тех, кто почитал и понял как делать интерфейсы, но не понял зачем.

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

Когда речь заходит о принципах объектно-ориентированного программирования (ООП): полиморфизм, наследование и инкапсуляция, полезно приводить аналогии из реального мира. Большой плюс ООП в том, что мы в программе можем смоделировать часть реальной вселенной. Смоделируем семью Ивановых: Папа, Мама и мальчик Петя. От Папы Петя унаследовал привычку прихлюпывать когда пьет чай, а от Мамы он унаследовал привычку поджимать губы во время чтения. Если попытаться реализовать эту ситуацию в программу, то у нас получиться три класса:

 class Папа class Мама class Петя 

У Папы и Мамы есть привычки, которые нужно передать Пете. Привычки — это какие-то действия — так что лучше всего реализовать их в программном мире как методы: Сначала Папа:

 class Папа < public void прихлюпывать() < System.out.println("Хлюп"); >> 

Теперь Мама:

 class Мама < public void поджимать() < System.out.println("Поджать губки"); >> 

Если речь идет о наследовании, то логично написать код так:

 class Петя extends Папа, Мама < @Override public void прихлюпывать() < System.out.println("Хлюп"); >@Override public void поджимать() < System.out.println("Поджать губки"); >> 

Зачем нужны интерфейсы? - 1

То есть унаследовать Петю от Папы и Мамы одновременно. Если так написать, то компилятор будет ругаться, потому что в Java нельзя реализовать множественное наследование классов. К слову, в С++ можно, а вот в Java нельзя, потому что с множественным наследованием могут возникнуть большие проблемы: подробно пишут в интернете. Что бы обойти это «нельзя», в Java есть интерфейсы. И для привычек мы придумаем свой интерфейс. Даже два: Выглядеть они будут так:

 public interface ПривычкиПапы < public void прихлюпывать(); >public interface ПривычкиМамы

В интерфейсе мы только описали привычки, но не описали что они конкретно делают, потому что конкретную реализацию мы пропишем в классах. Сначала отдадим Папе и Маме их законные привычки.

 class Папа implements ПривычкиПапы < @Override public void прихлюпывать() < System.out.println("Хлюп"); >> class Мама implements ПривычкиМамы < @Override public void поджимать() < System.out.println("Поджать губки"); >> 

И теперь, абсолютно легально можно передать Пете привычки от Папы и Мамы одновременно

 class Петя implements ПривычкиПапы, ПривычкиМамы < @Override public void прихлюпывать() < System.out.println("Хлюп"); >@Override public void поджимать() < System.out.println("Поджать губки"); >> 

Зачем нужны интерфейсы? - 2

То есть множественная реализация (чаще говорят имплементация) в Java вполне возможна. Смысл интерфейсов теперь должен быть понятен – в Java с помощью интерфейсов можно реализовать множественное наследование. Если развивать ситуацию дальше, например: ведь у Папы и Мамы наверняка есть привычки, которые они не передали Пете, да и у Пети могут быть свои личные привычки. Как эту жизненную Санта-Барбару перенести в плоскость Java вы узнаете в следующих сериях. Это не единственный пример для понимания интерфейсов.Если не читали следующие статьи, то обязательно прочтите: Интерфейсы в Java (если не открыто, можно выйти из профиля или прочитать в режиме — инкогнито) Для чего в Java нужны интерфейсы — тут реализуйте все примеры из статьи и поизменяйте методы и в интерфейсах и в калассах: наименования методов, сигнатуры (то что метод принимает на вход), типы вывода методов. Разберитесь самостоятельно: — с разницей при имплементации интерфейса с классом и абстрактным классом; — дефолтными методами.

Зачем нужны интерфейсы c

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

IMovable m = new IMovable(); // ! Ошибка, так сделать нельзя interface IMovable

В конечном счете интерфейс предназначен для реализации в классах и структурах. Например, реализуем выше определенный интерфейс IMovable:

// применение интерфейса в классе class Person : IMovable < public void Move() < Console.WriteLine("Человек идет"); >> // применение интерфейса в структуре struct Car : IMovable < public void Move() < Console.WriteLine("Машина едет"); >>

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

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

Применение интерфейса в программе:

Person person = new Person(); Car car = new Car(); DoAction(person); DoAction(car); void DoAction(IMovable movable) => movable.Move(); interface IMovable < void Move(); >class Person : IMovable < public void Move() =>Console.WriteLine("Человек идет"); > struct Car : IMovable < public void Move() =>Console.WriteLine("Машина едет"); >

В данной программе определен метод DoAction() , который в качестве параметра принимает объект интерфейса IMovable. На момент написания кода мы можем не знать, что это будет за объект — какой-то класс или структура. Единственное, в чем мы можем быть уверены, что этот объект обязательно реализует метод Move и мы можем вызвать этот метод.

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

Консольный вывод данной программы:

Человек идет Машина едет

Реализация интерфейсов по умолчанию

Начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Зачем это нужно? Допустим, у нас есть куча классов, которые реализуют некоторый интерфейс. Если мы добавим в этот интерфейс новый метод, то мы будем обязаны реализовать этот метод во всех классах, применяющих данный интерфейс. Иначе подобные классы просто не будут компилироваться. Теперь вместо реализации метода во всех классах нам достаточно определить его реализацию по умолчанию в интерфейсе. Если класс не реализует метод, будет применяться реализация по умолчанию.

IMovable tom = new Person(); Car tesla = new Car(); tom.Move(); // Walking tesla.Move(); // Driving interface IMovable < void Move() =>Console.WriteLine("Walking"); > class Person : IMovable < >class Car : IMovable < public void Move() =>Console.WriteLine("Driving"); >

В данном случае интерфейс IMovable определяет реализацию по умолчанию для метода Move . Класс Person не реализует этот метод, поэтому он применяет реализацию по умолчанию в отличие от класса Car , который определяет свою реализацию для метода Move.

Стоит отметить, что хотя для объекта класса Person мы можем вызвать метод Move — ведь класс Person применяет интерфейс IMovable , тем не менее мы не можем написать так:

Person tom = new Person(); tom.Move(); // Ошибка - метод Move не определен в классе Person

Множественная реализация интерфейсов

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

class myClass: myInterface1, myInterface2, myInterface3, .

Рассмотрим на примере:

Message hello = new Message("Hello World"); hello.Print(); // Hello World interface IMessage < string Text < get; set; >> interface IPrintable < void Print(); >class Message : IMessage, IPrintable < public string Text < get; set; >public Message(string text) => Text = text; public void Print()=> Console.WriteLine(Text); >

В данном случае определены два интерфейса. Интерфейс IMessage определяет свойство Text, которое представляет текст сообщения. А интерфейс IPrintable определяет метод Print.

Класс Message реализует оба интерфейса и затем применяется в программе.

Интерфейсы в преобразованиях типов

Все сказанное в отношении преобразования типов характерно и для интерфейсов. Поскольку класс Message реализует интерфейс IMessage, то переменная типа IMessage может хранить ссылку на объект типа Message:

// Все объекты Message являются объектами IMessage IMessage hello = new Message("Hello METANIT.COM"); Console.WriteLine(hello.Text); // Hello METANIT.COM // Не все объекты IMessage являются объектами Message, необходимо явное приведение // Message someMessage = hello; // ! Ошибка // Интерфейс IMessage не имеет свойства Print, необходимо явное приведение // hello.Print(); // ! Ошибка // если hello представляет класс Message, выполняем преобразование if (hello is Message someMessage) someMessage.Print();

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

Обратное преобразование — от интерфейса к реализующему его классу будет аналогично преобразованию от базового класса к производному. Так как не каждый объект IMessage является объектом Message (ведь интерфейс IMessage могут реализовать и другие классы), то для подобного преобразования необходима операция приведения типов. И если мы хотим обратиться к методам класса Message, которые не определены в интерфейсе IMessage, но являются частью класса Message, то нам надо явным образом выполнить преобразование типов:

if (hello is Message someMessage) someMessage.Print();

interface (справочник по C#)

Интерфейс определяет контракт. record Любой class или struct реализующий этот контракт должен предоставлять реализацию элементов, определенных в интерфейсе. Интерфейс может определить реализацию по умолчанию для членов. Он также может определять члены static , чтобы обеспечить единую реализацию для общих функциональных возможностей. Начиная с C# 11 интерфейс может определить static abstract или static virtual члены, чтобы объявить, что тип реализации должен предоставлять объявленные члены. Как правило, методы объявляют, static virtual что реализация должна определять набор перегруженных операторов.

В следующем примере класс ImplementationClass должен реализовать метод с именем SampleMethod , не имеющий параметров и возвращающий значение void .

Дополнительные сведения и примеры см. в разделе Интерфейсы.

Пример интерфейса

interface ISampleInterface < void SampleMethod(); >class ImplementationClass : ISampleInterface < // Explicit interface member implementation: void ISampleInterface.SampleMethod() < // Method implementation. >static void Main() < // Declare an interface instance. ISampleInterface obj = new ImplementationClass(); // Call the member. obj.SampleMethod(); >> 

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

Члены интерфейса по умолчанию

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

  • Константы
  • Операторы
  • Статический конструктор
  • Вложенные типы
  • Статические поля, методы, свойства, индексаторы и события
  • Объявления членов с помощью явного синтаксиса реализации интерфейса.
  • Явные модификаторы доступа (доступ по умолчанию — public ).

Статические абстрактные и виртуальные члены

Начиная с C# 11 интерфейс может объявлять static abstract и static virtual члены для всех типов элементов, кроме полей. Интерфейсы могут объявлять, что реализация типов должна определять операторы или другие статические члены. Эта функция позволяет универсальным алгоритмам указывать поведение, подобное числу. Примеры можно увидеть в числовых типах во время выполнения .NET, например System.Numerics.INumber . Эти интерфейсы определяют общие математические операторы, реализованные многими числовыми типами. Компилятор должен разрешать вызовы static virtual и static abstract методы во время компиляции. Методы static virtual , static abstract объявленные в интерфейсах, не имеют механизма диспетчеризации среды выполнения, аналогичного virtual методам или abstract объявленным в классах. Вместо этого компилятор использует сведения о типе, доступные во время компиляции. static virtual Поэтому методы почти исключительно объявляются в универсальных интерфейсах. Кроме того, большинство интерфейсов, объявляющих или методы, static virtual объявляют, что один из параметров типа должен реализовать объявленный интерфейс. static abstract Например, интерфейс объявляет, INumber что T должен реализовываться INumber . Компилятор использует аргумент типа для разрешения вызовов методов и операторов, объявленных в объявлении интерфейса. Например, int тип реализует INumber . Когда параметр T типа обозначает аргумент int типа, вызываются члены, static объявленные в int ней. Кроме того, если double аргумент типа является аргументом типа, вызываются члены, static объявленные в типе double .

Диспетчеризация static abstract методов и static virtual методов, объявленных в интерфейсах, разрешается с помощью типа времени компиляции выражения. Если тип среды выполнения выражения является производным от другого типа времени компиляции, будет вызываться статические методы базового типа (время компиляции).

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

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

Интерфейсы не могут содержать состояние экземпляра. Хотя статические поля теперь разрешены, поля экземпляров не допускаются в интерфейсах. Автоматические свойства экземпляра не поддерживаются в интерфейсах, так как они неявно объявляют скрытое поле. Это правило оказывает незначительное воздействие на объявления свойств. В объявлении интерфейса следующий код не объявляет автоматически реализованное свойство, как это делается в class или struct . Вместо этого он объявляет свойство, которое не имеет реализации по умолчанию, но должно быть реализовано в любом типе, реализующем интерфейс.

public interface INamed < public string Name > 

Интерфейс может наследовать от одного или нескольких базовых интерфейсов. Когда интерфейс переопределяет метод, реализованный в базовом интерфейсе, он должен использовать синтаксис явной реализации интерфейса.

Если список базовых типов содержит базовый класс и интерфейсы, базовый класс должен стоять первым в списке.

Класс, реализующий интерфейс, может явно реализовывать члены этого интерфейса. Явным образом реализованный член нельзя получить через экземпляр класса, но только через экземпляр интерфейса. Кроме того, обращение к членам интерфейса по умолчанию можно осуществлять только через экземпляр интерфейса.

Дополнительные сведения о явной реализации интерфейса см. в статье Явная реализация интерфейса.

Пример реализации интерфейса

В следующем примере показана реализация интерфейса. В этом примере интерфейс содержит объявление свойства, а класс содержит реализацию. Любой экземпляр класса, который реализует IPoint , имеет целочисленные свойства x и y .

interface IPoint < // Property signatures: int X < get; set; >int Y < get; set; >double Distance < get; >> class Point : IPoint < // Constructor: public Point(int x, int y) < X = x; Y = y; >// Property implementation: public int X < get; set; >public int Y < get; set; >// Property implementation public double Distance => Math.Sqrt(X * X + Y * Y); > class MainClass < static void PrintPoint(IPoint p) < Console.WriteLine("x=, y=", p.X, p.Y); > static void Main() < IPoint p = new Point(2, 3); Console.Write("My Point: "); PrintPoint(p); >> // Output: My Point: x=2, y=3 

Спецификация языка C#

См. также

  • Справочник по C#
  • Руководство по программированию на C#
  • Ключевые слова в C#
  • Ссылочные типы
  • Интерфейсы
  • Использование свойств
  • Использование индексаторов

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Интерфейсы

Интерфейс представляет ссылочный тип, который может определять некоторый функционал — набор методов и свойств без реализации. Затем этот функционал реализуют классы и структуры, которые применяют данные интерфейсы.

Определение интерфейса

Для определения интерфейса используется ключевое слово interface . Как правило, названия интерфейсов в C# начинаются с заглавной буквы I , например, IComparable, IEnumerable (так называемая венгерская нотация), однако это не обязательное требование, а больше стиль программирования.

Что может определять интерфейс? В целом интерфейсы могут определять следующие сущности:

  • Методы
  • Свойства
  • Индексаторы
  • События
  • Статические поля и константы (начиная с версии C# 8.0)

Однако интерфейсы не могут определять нестатические переменные. Например, простейший интерфейс, который определяет все эти компоненты:

interface IMovable < // константа const int minSpeed = 0; // минимальная скорость // статическая переменная static int maxSpeed = 60; // максимальная скорость // метод void Move(); // движение // свойство string Name < get; set; >// название delegate void MoveHandler(string message); // определение делегата для события // событие event MoveHandler MoveEvent; // событие движения >

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

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

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

Модификаторы доступа

Еще один момент в объявлении интерфейса: если его члены — методы и свойства не имеют модификаторов доступа, то фактически по умолчанию доступ public , так как цель интерфейса — определение функционала для реализации его классом. Это касается также и констант и статических переменных, которые в классах и структурах по умолчанию имееют модификатор private. В интерфейсах же они имеют по умолчанию модификатор public. И например, мы могли бы обратиться к константе minSpeed и переменной maxSpeed интерфейса IMovable:

Console.WriteLine(IMovable.maxSpeed); // 60 Console.WriteLine(IMovable.minSpeed); // 0

Но также, начиная с версии C# 8.0, мы можем явно указывать модификаторы доступа у компонентов интерфейса:

interface IMovable < public const int minSpeed = 0; // минимальная скорость private static int maxSpeed = 60; // максимальная скорость public void Move(); protected internal string Name < get; set; >// название public delegate void MoveHandler(string message); // определение делегата для события public event MoveHandler MoveEvent; // событие движения >

Как и классы, интерфейсы по умолчанию имеют уровень доступа internal , то есть такой интерфейс доступен только в рамках текущего проекта. Но с помощью модификатора public мы можем сделать интерфейс общедоступным:

public interface IMovable

Реализация по умолчанию

Также начиная с версии C# 8.0 интерфейсы поддерживают реализацию методов и свойств по умолчанию. Это значит, что мы можем определить в интерфейсах полноценные методы и свойства, которые имеют реализацию как в обычных классах и структурах. Например, определим реализацию метода Move по умолчанию:

interface IMovable < // реализация метода по умолчанию void Move() < Console.WriteLine("Walking"); >>

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

interface IMovable < // реализация метода по умолчанию void Move() =>Console.WriteLine("Walking"); // реализация свойства по умолчанию // свойство только для чтения int MaxSpeed < get < return 0; >> >

Стоит отметить, что если интерфейс имеет приватные методы и свойства (то есть с модификатором private), то они должны иметь реализацию по умолчанию. То же самое относится к статическим методам (не обязательно приватным):

Console.WriteLine(IMovable.MaxSpeed); // 60 IMovable.MaxSpeed = 65; Console.WriteLine(IMovable.MaxSpeed); // 65 double time = IMovable.GetTime(500, 10); Console.WriteLine(time); // 50 interface IMovable < public const int minSpeed = 0; // минимальная скорость private static int maxSpeed = 60; // максимальная скорость // находим время, за которое надо пройти расстояние distance со скоростью speed static double GetTime(double distance, double speed) =>distance / speed; static int MaxSpeed < get =>maxSpeed; set < if (value >0) maxSpeed = value; > > >

Добавление интерфейса

Стоит отметить, что в Visual Studio есть специальный компонент для добавления нового интерфейса в отдельном файле. Для добавления интерфейса в проект можно нажать правой кнопкой мыши на проект и в появившемся контекстном меню выбрать Add -> New Item. и в диалоговом окне добавления нового компонента выбрать пункт Interface :

Интерфейсы в C#

Хотя мы также может добавить стандартный файл класса или любой другой файл кода C# и в нем определить интерфейс.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *