interface (справочник по C#)
Интерфейс определяет контракт. Любой 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 type обозначает аргумент 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, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Зачем придумали интерфейсы?
В интернете много статей на тему интерфейсов, что это такое и как их реализовывать. Но я не нашел внятного ответа кто и зачем их придумал? Я только начинаю изучать C# и вообще не вижу смысла в интерфейсах. Классы удобны например в ситуации когда нам нужно в игре создать много врагов, нам не надо каждому врагу прописовать параметры, можно все задать в классе. Вот пример с метанина:
interface IMovable
Реализация в классе:
// применение интерфейса в классе class Person : IMovable < public void Move() < Console.WriteLine("Человек идет"); >> // применение интерфейса в структуре struct Car : IMovable < public void Move() < Console.WriteLine("Машина едет"); >>
Но если я просто удалю интерфейс и сделаю вот так
class Person < public void Move() < Console.WriteLine("Человек идет"); >>
Код по прежнему будет работать.
Вот и вопрос зачем все это нужно?
Я просто не могу представить себе ситуацию где бы было необходимо использовать интерфейсы.
В C#8 добавили реализацию метода по умолчанию в интерфейсе тем самым сделав из интерфейса недо-класс.
Смотрю видео на ютубе и там все используют интерфейсы но не говорят зачем, просто как будто так и надо.
Не спроста же все их используют? Но без понимания зачем все это нужно у меня просто не получается найти смысл их применения и понять как работает чужой код когда в нем есть интерфейсы.
Единственный смысл в них вижу в том что не нужно писать документацию к классам, просто наследуем от интерфейса а дальше пусть уже другой программист лезет в код и смотрит чего там и как.
Ни в коем случае не хочу сказать что я тут самый умный а все остальные ошибались, просто хочу разобраться почему придумали интерфейсы если и без них все хорошо работает?
Извините что так сумбурно написал, сам на эмоциях, неделю уже читаю статьи по теме и ни как не могу осилить.
Отслеживать
4,962 1 1 золотой знак 9 9 серебряных знаков 27 27 бронзовых знаков
задан 17 янв 2020 в 23:07
User12351259599491 User12351259599491
299 4 4 серебряных знака 17 17 бронзовых знаков
+1 за желание разобраться.
– user176262
17 янв 2020 в 23:11
Дополню небольшим примером из видео о том как сделать парсер сайта: Человек создает интерфейс с разными полями в частности там есть поле string BaseUrl < get; set; >и в классе он пишет реализацию public string BaseUrl < get; set; >= «https://habrahabr.ru»; У меня просто в этот момент происходит крик в голове «Зачем ты создал этот интерфейс если все равно в классе ты прописал тоже самое. » Это же просто лишний код, который будет путать людей если они не очень хорошо знают c#.
17 янв 2020 в 23:45
На этот вопрос отвечали много раз, используйте поиск. Если вы захотите составить список всех объектов, которые могут двигаться: машин, людей, галактик. какой у него будет тип?
17 янв 2020 в 23:46
см. видео 1 и 2
18 янв 2020 в 0:06
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Есть как минимум 4 причины для использования интерфейсов. Их может и больше, но я текст пишу из головы, потому остальные причины, если такие есть, загуглите или додумаете сами.
Давайте начнем с того, как строятся большие и сложные системы. Основной механизм построения чего то большого — это разделение этого большого на малые части, определения того, как эти части взаимодействуют, и после этого программирование каждой из частей.
Ключевой момент здесь в том, что вы не можете сразу продумать все эти мелкие части и сазу написать все классы для них.
Давайте возьмем типичный пример. Вы пишете свою игру и вам надо предусмотреть сохранение вашей игры. Вы точно знаете, что именно вам надо сохранить, но вы ещё не знаете, куда вы будете сохранять, в файл или в БД или передавать данные на сервер. Что делать в такой ситуации?
В такой ситуации вам поможет интерфейс. Например, у вас есть класс, который вам надо сохранить.
public class MyGameState < //. my game data >
Напишем для него интерфейс сохранения
public interface IGameStore
Что тут произошло? Вашего сохранения ещё в природе нет, кода для него нет, какой это будет класс, какая у класса-хранилища будет иерария наследования, ничего не известно, но вы уже определили, как ваша игра будет взаимодействовать с этим, ещё не существующим классом. Вы можете этот класс написать позже, или его может написать любой другой программист и все будет работать, так как контракт вашего будушего класса уже известен — вы уже определили с помощью интерфейса все нужные для класса методы.
Другими словами, интерфейс помогает определять взаимодействие между модулями вашей программы, даже если эти модули ещё не существуют.
Но тут вы можете возразить мне и сказать — что вы с таким же успехом можете определить класс для сохранения, например
public absract class GameStore
Да, это верно, вы можете определить абстрактный класс. В таком случае, для реализации сохранения, вам надо будет унаследоваться от абстрактрого класса и реализовать нужные методы. Но давайте подумаем, какие ограничения это накладывает на наш класс сохранения? А вот какие — наш класс сохранения должен будет быть унаследован именно от указанного абстрактного класса и больше ни от кого. Вы можете возразить — мол, в случае интерфейса будет то же самое — ведь класс сохранения должен его реализовать. Но вот и нет — в C# реализация интерфейса никак не ограничивает класс, так как класс может реализовать сколько угодно интерфейсов. А вот наследование от абстрактного класса — уже дело другое, ведь вы не можете насловаться от 2 классов. Отсюда вторая причина использования интерфйсов — Использование интерфйсов позволяет обойти ограничения множественного наследования.
Окей, ну, допустим, мы решили, что мы будем сохранять нашу игру, но для сохранения нашей игры, нам обязательно надо знать имя текушего игрока — то есть его никнейм. Это требование леко выразить, добавив в наш абстрактный класс конструктор, который принимает имя игрока, например (код может не копилироваться, я его практически на телефоне пишу, но идея должна быть ясна)
public absract class GameStore < protected string PlayerNamepublic GameStore(string playerName) < PlayerName = playerName; >public abstract void SaveGame(MyGameState state); public abstract MyGameState Load(); >
Теперь посмотрите что произошло. Мы не только заставляем классы для сохранения игры наследоваться от нашего абстрактного класса, но ещё и накладываем на них определенные огреничения. Интересны ли эти ограничения остальной части игры? Думаю, нет. В таком случае, зачем остальной части игры знать об этих огреничениях? Нет никаких причин для этого. Таким образом, Используя интерфейс, вы декларируете желаемое поведение. Используя класс — вы декларируете детали реализации. То есть, когда у вас был интерфейс в игре, вы декларировали, что «не важно что тут будет за объект, не важно как он будет написан, но мне надо чтобы он мог сохранять и загружать состояние игры» — то есть вам было важно, что объект может делать, при этом не важно, как он это делает. В этом суть инкапсуляции. Когда же вы используете класс, особенно если класс содержит какие либо посторонние детали, вы уже покажываете, что вам важно не только что объект может делать, но и также кем объект является и какие у объекта есть детали реализации.
А теперь давайте посмотрим на это с точки зрения игры. Игре, на самом деле, не интересно знать, как и куда будет происходить сохранение. Игре не интересны детали сохранения. Игре не интересен даже факт — сущестует ли уже написанный класс сохранения. Все, что игре надо — это получить объект, который содержит нужные игре методы. Всё. При таком подходе связь между игрой и реальным классом сохранения лежит только через интерфейс, что является связью гораздо более слабой. Сам класс может быть написан как угодно, содержать какие угодно огранчения или детали реализации, эти ограничения и детали могут меняться и быть переписаны любое количество раз и все будет работать, пока класс сохранения реализует интерфейс. Таким образом, связь классов через интерфейс является более слабой связью, чем связь классов через абстрактный класс или напрямую, откуда следует выводы:
- Если вы хотите держать свои модули слабо связанными (а вы должны хотеть), используйте для связи интерфейсы
- При использовании интерфейов для связи, на них ложится бремя совместимости — интерфейсы должны меняться как можно реже
- Так как интерфейсы должны меняться как можно реже, написание интерфейса становится задачай более ответственной, чем написание класса. Класс, закрытый интерфейсом, всегда можно переписать. Интерфейс же, используемый во многих компонентах, переписать возможно не всегда, при этом есть большой риск потери обратной совместимости с уже написанным до этого кодом.
Интерфейсы
Интерфейс представляет ссылочный тип, который может определять некоторый функционал — набор методов и свойств без реализации. Затем этот функционал реализуют классы и структуры, которые применяют данные интерфейсы.
Определение интерфейса
Для определения интерфейса используется ключевое слово 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#: зачем они нужны?

Интерфейсы — одна из самых важных фич в C# для реализации объектно-ориентированного программирования в целом. Однако, основываясь на моем опыте чтения онлайн-статей об интерфейсах (включая и книги по программированию), я могу с уверенностью сказать, что в большинстве случаев в этих статьях подробно раскрывается вопрос, как использовать интерфейсы, но очень скупо — зачем.
Поэтому сегодня я хочу поделиться с вами своим опытом. А именно, чем же интерфейс так полезен в повседневной разработке.
Дисклеймер: это всего лишь мой личный опыт использования интерфейсов, и я не настаиваю на том, что это единственный способ достичь ваших целей, или что они могут быть достигнуты только с помощью интерфейсов.
Для иллюстрации мы используем типовой пример «Сотрудник в компании»:

Рассмотрим следующий сценарий: в компании есть 3 типа сотрудников: служащий (Executive), менеджер (Manager) и топ-менеджер (C-suite).
Служащий имеет имя (Name), должность (Designation) и ключевой показатель эффективности (KPI). Вот так может выглядеть класс Executive :
public class Executive < public string Name < get; set; >public string Designation < get; set; >public int KPI < get; set; >>
У менеджера все то же самое, что и у служащего, но у него еще есть дополнительные права оценивать сотрудников уровня “служащий”. Вот так может выглядеть класс Manager :
public string Name < get; set; >public string Designation < get; set; >public int KPI < get; set; >public Executive EvaluateSubordinate(Executive executive)
У топ-менеджеров есть имя и должность, но нет KPI. Мы предполагаем, что их KPI связаны с доходностью акций компании или другими показателями. Топ-менеджеры также имеют право оценивать только сотрудников уровня “менеджер”, и, кроме того, они имеют право уволить любого неэффективного сотрудника. Вот так может выглядеть класс CSuite :
public class CSuite < public string Name < get; set; >public string Designation < get; set; >public Manager EvaluateSubordinate(Manager manager) < Random random = new Random(); manager.KPI = random.Next(60, 100); return manager; >public void TerminateExecutive(Executive executive) < Console.WriteLine($ "Employee with KPI has been terminated because of KPI below 70"); > public void TerminateManager(Manager manager) < Console.WriteLine($ "Employee with KPI has been terminated because of KPI below 70"); > >
Обратите внимание, что:
- Хоть классы Manager и CSuite имеют метод EvaluateSubordinate , они принимают разные типы аргументов.
- CSuite имеет две функции TerminateExecutive , которые принимают различные типы аргументов.
Допустим мы хотим сгруппировать все инстансы этих классов в один список и проитерировать по всем сотрудникам, чтобы выполнить следующие задачи:
- Отобразить их имя и должность.
- Соответствующий руководитель оценит их KPI.
- Топ-менеджер уволит тех, у кого KPI меньше 70.
И получить что-то вроде этого:

Для начала я инициализирую всех своих сотрудников:
Executive executive = new Executive() < Name = "Alice", Designation = "Programmer">; Manager manager = new Manager() < Name = "Bob", Designation = "Sales Manager">; CSuite cSuite = new CSuite() < Name = "Daisy", Designation = "CFO" >;
И вот я сразу же сталкиваюсь с первой проблемой — я не могу сгруппировать всех этих сотрудников вместе в один список, так как они принадлежат к разным классам.
Польза от интерфейса № 1: Может выступать в роли обобщенного класса
Мы очень быстро достигли первой причины, по которой интерфейс может быть для нас полезен — для группировки разных классов вместе.
Поскольку у всех типов сотрудников есть имя и должность, я создам общий интерфейс, который содержит эти два свойства. Этот интерфейс предназначен для реализации возможности группировки всех сотрудников вместе. Я назвал его IEmployee :
public interface IEmployee < string Name < get; set; >string Designation < get; set; >>
Во-вторых, поскольку KPI должны оцениваться только для сотрудников классов Executive и Manager, я создал интерфейс IEvaluatedEmployee , который имеет только одно свойство: KPI. Обратите внимание, что мой интерфейс IEvaluatedEmployee также реализует интерфейс IEmployee . Это означает, что любой класс, реализующий этот интерфейс, также будет иметь свойства IEmployee (а именно имя и должность).
public interface IEvaluatedEmployee: IEmployee < int KPI < get; set; >> >
Я создал еще один интерфейс с именем IManagementLevelEmployee , который указывает, что этот интерфейс имеет право управлять людьми, т. е. в нашем примере оценивать сотрудников по их KPI.
public interface IManagementLevelEmployee: IEmployee
Наконец, мы знаем, что только топ-менеджер имеет право увольнять сотрудников, поэтому я создал интерфейс ICSuite_Privilege с функцией увольнения сотрудников.
public interface ICSuite_Privilege: IEmployee
Я закончил с интерфейсами, так что давайте теперь посмотрим, как наши классы будут их реализовывать.
Для класса Executive :
public class Executive: IEvaluatedEmployee < public string Name < get; set; >public string Designation < get; set; >public int KPI < get; set; >>
Для класса Manager :
public class Manager: IManagementLevelEmployee, IEvaluatedEmployee < public string Name < get; set; >public string Designation < get; set; >public int KPI < get; set; >public IEvaluatedEmployee EvaluateSubordinate(IEvaluatedEmployee evaluatedemployee) < Random random = new Random(); evaluatedemployee.KPI = random.Next(40, 100); return evaluatedemployee; >>
Обратите внимание, что класс Manager реализует и IManagementLevelEmployee , и IEvaluatedEmployee , т.е. это указывает на то, что сотрудники, принадлежащие к этому классу, имеют право оценивать других сотрудников, но в то же время также могут оцениваться кем-то другим.
Наконец, наш класс C-Suite :
public class CSuite: IManagementLevelEmployee, ICSuite_Privilege < public string Name < get; set; >public string Designation < get; set; >public IEvaluatedEmployee EvaluateSubordinate(IEvaluatedEmployee Manager) < Random random = new Random(); Manager.KPI = random.Next(60, 100); return Manager; >public bool TerminateEmployee(IEvaluatedEmployee evemp) < Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($ "Employee with KPI has been terminated because of KPI below 70"); Console.ForegroundColor = ConsoleColor.White; return true; > >
Диаграмма классов после того, как все классы реализовали доступные интерфейсы, будет выглядеть так:

И вот теперь я могу сгруппировать представителей всех трех классов через IEmployee и выполнить итерацию по всем сотрудникам, чтобы отобразить их информацию.
employees.Add(new Executive() < Name = "Alex", Designation = "Programmer" >); employees.Add(new Manager() < Name = "Bob", Designation = "Sales Manager" >); employees.Add(new CSuite() < Name = "Daisy", Designation = "CFO" >); #region Display Employees Info Console.WriteLine("-----Display Employee's Information-----"); foreach(IEmployee employee in employees) < DisplayEmployeeInfo(employee); >Console.WriteLine(); #endregion static void DisplayEmployeeInfo(IEmployee employee) < Console.WriteLine($ "is a "); >
Преимущества использования интерфейсов не ограничивается на возможности группировки взаимосвязанных классов. Они также дают нам гибкость при написании функций. Давайте вернемся к функции для увольнения сотрудников топ-менеджером. Нам достаточно написать только одну функцию TerminateEmployee , которая принимает аргументы с типом IEvaluatedEmployee (который реализуют Executive и Manager), вместо двух функций для удаления Executive и Manager соответственно.
public bool TerminateEmployee(IEvaluatedEmployee evemp) < Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($ "Employee with KPI has been terminated because of KPI below 70"); Console.ForegroundColor = ConsoleColor.White; return true; >
Польза от интерфейса № 2: Обязывающий “контакт” и расширение возможностей класса
Многие люди рассматривают интерфейсы как “контракты” в мире классов. Рассмотрим пример из реальной жизни: владелец обувной фабрики подписывает контракт с инвестором о том, что фабрика должна будет произведет 100 пар обуви в течении одного месяца. Фабрика ДОЛЖНА произвести 100 пар обуви для инвестора, и невыполнение этого обязательства повлечет за собой штраф.
public class CSuite : IManagementLevelEmployee, ICSuite_Privilege
Для примера, класс CSuite реализует два интерфейса: IManagementLevelEmployee и ICSuite_Privilege . Это “контракт”, который обязывает этот класс иметь все функции из этих двух интерфейсов (оценки и увольнения сотрудника). Если мы не позаботимся о их создании, то компилятор выдаст ошибку (аналог штрафа в реальном мире).
Скажем, в будущем будет введен новый класс под названием “Board” (правление/совет директоров). Мы сможем назначить аналогичные привилегии классу Board, чтобы гарантировать, что он будет иметь такие же полномочия, как и CSuite. Благодаря этому мы можем гарантировать, что все инстансы Board будут иметь функцию увольнения сотрудника. Эта фича дает программисту возможность быстро оценить назначение и ответственность каждого класса, просто посмотрев на интерфейсы, реализуемые ими.
public class Board : ICSuite_Privilege
Польза от интерфейса № 3: Множественное наследование для разделения ответственности
Вы могли заметить, что интерфейс IManagementLevelEmployee имеет функцию EvaluateSubordinate , и параметр, который он принимает, — IEvaluatedEmployee , а не IEmployee .
IEvaluatedEmployee EvaluateSubordinate(IEvaluatedEmployee employee);//почему IEvaluatedEmployee IEvaluatedEmployee EvaluateSubordinate(IEmployee employee);//вместо IEmployee?
Функция EvaluateSubordinate будет так же прекрасно работать, если она будет принимать IEmployee , поскольку классы Executive и Manager также реализуют IEmployee . Так почему вместо этого я использую IEvaluatedEmployee ?
Потому, что в нашем случае:
- CSuite также реализует IEmployee ,
- но сам CSuite никем не оценивается,
- а по какому показателю будет оцениваться сотрудник? В нашем случае это KPI.
Поэтому, чтобы удовлетворить всем требованиям, я создал интерфейс под названием IEvaluatedEmployee , у которого есть свойство KPI, и в то же время он реализует IEmployee . Таким образом, это означает, что кто бы ни реализовывал этот интерфейс (Executive и Manager), он будет иметь не только KPI, но и все свойства IEmployee (имя и должность).
public interface IEvaluatedEmployee: IEmployee < int KPI < get; set; >> public class Executive: IEvaluatedEmployee public class Manager: IManagementLevelEmployee, IEvaluatedEmployee public class CSuite: IManagementLevelEmployee, ICSuite_Privilege
Но класс CSuite не реализует интерфейс IEvaluatedEmployee . Поэтому мы можем предотвратить передачу в функцию EvaluateSubordinate инстанс класса CSuite , установив такое ограничение с помощью интерфейса. Это один из способов не нарушать нашу бизнес-логику нашим кодом.
Польза от интерфейса № 4: Анализ воздействия
И, скажем, в будущем, председатель правления компании представит новую политику для сотрудников
- Топ-менеджеры тоже подлежат оценке
- Введет еще два параметра (a и b) для оценки
Поскольку мой пример — это небольшая программа, я могу изменить свой код за пару секунд без риска возникновения каких-либо ошибок. Однако в реальной жизни система может быть огромной и очень сложной, и без интерфейсов очень сложно поддерживать или применять подобные изменения.
В целях демонстрации, чтобы реализовать новую политику двух компаний, я
- Заставлю класс CSuite реализовывать интерфейс IEvaluatedEmployee
public class CSuite : IManagementLevelEmployee, ICSuite_Privilege, IEvaluatedEmployee
- Введу еще два параметра a и b для EvaluateSubordinate в интерфейсе IManagementLevelEmployee
public interface IManagementLevelEmployee : IEmployee < IEvaluatedEmployee EvaluateSubordinate(IEvaluatedEmployee employee, string a, string b);//добавляем еще два параметра a и b >
Visual Studio немедленно выдаст мне предупреждение об ошибке, указывающее, что мой CSuite не реализует IEvaluatedEmployee (отсутствует свойство int KPI), а класс Manager и CSuite неправильно реализует интерфейс IManagementLevelEmployee (функция EvaluateSubordinate требует два новых параметра).

Представьте себе, без интерфейсов программист не сможет определить все части, которые нуждаются в изменении, и осознает это только после того, как система будет запущена. Интерфейс же помогает сразу проверить, что наш код всегда соответствует новейшей бизнес-логике.
Польза от интерфейса № 5: Абстракция планирования
В реальной жизни, как правило, проект разрабатывается сразу несколькими разработчиками. И очень часто мы начинаем разработку еще до окончательного оформления бизнес-требований. Таким образом, с помощью интерфейсов ведущий программист или архитектор может стандартизировать функции каждого класса. Используя тот же пример, технический руководитель может не знать, какова точная бизнес-логика для оценки сотрудника, но с помощью интерфейса он/она может наглядно продемонстрировать для всех программистов, работающих с классами Manager или CSuite , что они должны содержать функцию EvaluateSubordinate , а также иметь имя и должность для каждого сотрудника и т. д., указав это в интерфейсе.
Польза от интерфейса № 6: Модульное тестирование
И последнее, но не менее важное (хотя на самом деле это может быть одной из самых важных причин для реализации интерфейса) — модульное тестирование. Эта польза очень похожа на пользу № 5, поскольку многие компании применяют методологию Test Driven Development (TDD) в процессе разработки своего программного обеспечения, поэтому на этапе планирования оглядка на модульное тестирование очень важна до фактического начала разработки.
Допустим, есть функция CheckEmployee() для класса Executive и Manager , которая получает доступ к базе данных и проверяет информацию о сотруднике, прежде чем мы сможем его уволить.
public class Executive: IEvaluatedEmployee < public string Name < get; set; >public string Designation < get; set; >public int KPI < get; set; >public bool CheckEmployee() < //давайте сделаем вид, что здесь мы делаем вызов к базе данныхe return false; >>
Но в рамках модульного теста мы можем быть не в состоянии подключиться к реальной производственной базе данных, или у нас не будет полномочий этого делать, если мы можем поставить под угрозу производственные данные. Поэтому для модульного теста мы будем использовать тестовых дублеров, чтобы предположить, что мы получили доступ к базе данных, и проверить работу интересующей нас функции. Код модульного теста будет выглядеть так (я использую Moq):
[TestMethod] public void TestMethod1() < Mock < IEvaluatedEmployee >EvEmp = new Mock < IEvaluatedEmployee >(); EvEmp.Setup(x => x.CheckEmployee()).Returns(true); CSuite obje = new CSuite(); Assert.AreEqual(obje.TerminateEmployee(EvEmp.Object), true); >
Мы использовали интерфейс IEvaluatedEmployee , чтобы мокнуть класс Executive , чтобы функция CheckEmployee всегда возвращала значение true .
Если у нас не реализован интерфейс и мы используем реальный класс Executive для создания моков, Visual Studio выдаст ошибку. Это связано с тем, что система не может переопределить функцию конкретного класса, чтобы она всегда возвращала либо true , либо false .

Заключение
Я надеюсь, что эта статья помогла вам понять важность интерфейса, и вы узнали, когда и почему вы должны применять его в своей работе. Весь исходный код можно найти на моем Github.
Чем отличается объектно-ориентированное программирование от программирования на основе абстрактных типов данных? Приглашаем всех желающих на бесплатное открытое занятие, на котором разберем: что такое наследование, критерий правильного его применения и примеры ошибочного применения наследования. Регистрация — по ссылке.