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

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

  • автор:

Интерфейсы в C#: зачем они нужны?

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

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

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

Для иллюстрации мы используем типовой пример «Сотрудник в компании»:

Рассмотрим следующий сценарий: в компании есть 3 типа сотрудников: служащий (Executive), менеджер (Manager) и топ-менеджер (C-suite).

Служащий имеет имя (Name), должность (Designation) и ключевой показатель эффективности (KPI). Вот так может выглядеть класс Executive :

У менеджера все то же самое, что и у служащего, но у него еще есть дополнительные права оценивать сотрудников уровня “служащий”. Вот так может выглядеть класс Manager :

У топ-менеджеров есть имя и должность, но нет KPI. Мы предполагаем, что их KPI связаны с доходностью акций компании или другими показателями. Топ-менеджеры также имеют право оценивать только сотрудников уровня “менеджер”, и, кроме того, они имеют право уволить любого неэффективного сотрудника. Вот так может выглядеть класс CSuite :

Обратите внимание, что:

Хоть классы Manager и CSuite имеют метод EvaluateSubordinate , они принимают разные типы аргументов.

CSuite имеет две функции TerminateExecutive , которые принимают различные типы аргументов.

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

Отобразить их имя и должность.

Соответствующий руководитель оценит их KPI.

Топ-менеджер уволит тех, у кого KPI меньше 70.

И получить что-то вроде этого:

Для начала я инициализирую всех своих сотрудников:

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

Польза от интерфейса № 1: Может выступать в роли обобщенного класса

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

Поскольку у всех типов сотрудников есть имя и должность, я создам общий интерфейс, который содержит эти два свойства. Этот интерфейс предназначен для реализации возможности группировки всех сотрудников вместе. Я назвал его IEmployee :

Во-вторых, поскольку KPI должны оцениваться только для сотрудников классов Executive и Manager, я создал интерфейс IEvaluatedEmployee , который имеет только одно свойство: KPI. Обратите внимание, что мой интерфейс IEvaluatedEmployee также реализует интерфейс IEmployee . Это означает, что любой класс, реализующий этот интерфейс, также будет иметь свойства IEmployee (а именно имя и должность).

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

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

Я закончил с интерфейсами, так что давайте теперь посмотрим, как наши классы будут их реализовывать.

Для класса Executive :

Для класса Manager :

Обратите внимание, что класс Manager реализует и IManagementLevelEmployee , и IEvaluatedEmployee , т.е. это указывает на то, что сотрудники, принадлежащие к этому классу, имеют право оценивать других сотрудников, но в то же время также могут оцениваться кем-то другим.

Наконец, наш класс C-Suite :

Диаграмма классов после того, как все классы реализовали доступные интерфейсы, будет выглядеть так:

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

Преимущества использования интерфейсов не ограничивается на возможности группировки взаимосвязанных классов. Они также дают нам гибкость при написании функций. Давайте вернемся к функции для увольнения сотрудников топ-менеджером. Нам достаточно написать только одну функцию TerminateEmployee , которая принимает аргументы с типом IEvaluatedEmployee (который реализуют Executive и Manager), вместо двух функций для удаления Executive и Manager соответственно.

Польза от интерфейса № 2: Обязывающий “контакт” и расширение возможностей класса

Многие люди рассматривают интерфейсы как “контракты” в мире классов. Рассмотрим пример из реальной жизни: владелец обувной фабрики подписывает контракт с инвестором о том, что фабрика должна будет произведет 100 пар обуви в течении одного месяца. Фабрика ДОЛЖНА произвести 100 пар обуви для инвестора, и невыполнение этого обязательства повлечет за собой штраф.

Для примера, класс CSuite реализует два интерфейса: IManagementLevelEmployee и ICSuite_Privilege . Это “контракт”, который обязывает этот класс иметь все функции из этих двух интерфейсов (оценки и увольнения сотрудника). Если мы не позаботимся о их создании, то компилятор выдаст ошибку (аналог штрафа в реальном мире).

Скажем, в будущем будет введен новый класс под названием “Board” (правление/совет директоров). Мы сможем назначить аналогичные привилегии классу Board, чтобы гарантировать, что он будет иметь такие же полномочия, как и CSuite. Благодаря этому мы можем гарантировать, что все инстансы Board будут иметь функцию увольнения сотрудника. Эта фича дает программисту возможность быстро оценить назначение и ответственность каждого класса, просто посмотрев на интерфейсы, реализуемые ими.

Польза от интерфейса № 3: Множественное наследование для разделения ответственности

Вы могли заметить, что интерфейс IManagementLevelEmployee имеет функцию EvaluateSubordinate , и параметр, который он принимает, — IEvaluatedEmployee , а не IEmployee .

Функция EvaluateSubordinate будет так же прекрасно работать, если она будет принимать IEmployee , поскольку классы Executive и Manager также реализуют IEmployee . Так почему вместо этого я использую IEvaluatedEmployee ?

Потому, что в нашем случае:

CSuite также реализует IEmployee ,

но сам CSuite никем не оценивается,

а по какому показателю будет оцениваться сотрудник? В нашем случае это KPI.

Поэтому, чтобы удовлетворить всем требованиям, я создал интерфейс под названием IEvaluatedEmployee , у которого есть свойство KPI, и в то же время он реализует IEmployee . Таким образом, это означает, что кто бы ни реализовывал этот интерфейс (Executive и Manager), он будет иметь не только KPI, но и все свойства IEmployee (имя и должность).

Но класс CSuite не реализует интерфейс IEvaluatedEmployee . Поэтому мы можем предотвратить передачу в функцию EvaluateSubordinate инстанс класса CSuite , установив такое ограничение с помощью интерфейса. Это один из способов не нарушать нашу бизнес-логику нашим кодом.

Польза от интерфейса № 4: Анализ воздействия

И, скажем, в будущем, председатель правления компании представит новую политику для сотрудников

Топ-менеджеры тоже подлежат оценке

Введет еще два параметра (a и b) для оценки

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

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

Заставлю класс CSuite реализовывать интерфейс IEvaluatedEmployee

Введу еще два параметра a и b для EvaluateSubordinate в интерфейсе IManagementLevelEmployee

Visual Studio немедленно выдаст мне предупреждение об ошибке, указывающее, что мой CSuite не реализует IEvaluatedEmployee (отсутствует свойство int KPI), а класс Manager и CSuite неправильно реализует интерфейс IManagementLevelEmployee (функция EvaluateSubordinate требует два новых параметра).

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

Польза от интерфейса № 5: Абстракция планирования

В реальной жизни, как правило, проект разрабатывается сразу несколькими разработчиками. И очень часто мы начинаем разработку еще до окончательного оформления бизнес-требований. Таким образом, с помощью интерфейсов ведущий программист или архитектор может стандартизировать функции каждого класса. Используя тот же пример, технический руководитель может не знать, какова точная бизнес-логика для оценки сотрудника, но с помощью интерфейса он/она может наглядно продемонстрировать для всех программистов, работающих с классами Manager или CSuite , что они должны содержать функцию EvaluateSubordinate , а также иметь имя и должность для каждого сотрудника и т. д., указав это в интерфейсе.

Польза от интерфейса № 6: Модульное тестирование

И последнее, но не менее важное (хотя на самом деле это может быть одной из самых важных причин для реализации интерфейса) — модульное тестирование. Эта польза очень похожа на пользу № 5, поскольку многие компании применяют методологию Test Driven Development (TDD) в процессе разработки своего программного обеспечения, поэтому на этапе планирования оглядка на модульное тестирование очень важна до фактического начала разработки.

Допустим, есть функция CheckEmployee() для класса Executive и Manager , которая получает доступ к базе данных и проверяет информацию о сотруднике, прежде чем мы сможем его уволить.

Но в рамках модульного теста мы можем быть не в состоянии подключиться к реальной производственной базе данных, или у нас не будет полномочий этого делать, если мы можем поставить под угрозу производственные данные. Поэтому для модульного теста мы будем использовать тестовых дублеров, чтобы предположить, что мы получили доступ к базе данных, и проверить работу интересующей нас функции. Код модульного теста будет выглядеть так (я использую Moq):

Мы использовали интерфейс IEvaluatedEmployee , чтобы мокнуть класс Executive , чтобы функция CheckEmployee всегда возвращала значение true .

Если у нас не реализован интерфейс и мы используем реальный класс Executive для создания моков, Visual Studio выдаст ошибку. Это связано с тем, что система не может переопределить функцию конкретного класса, чтобы она всегда возвращала либо true , либо false .

Заключение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Интерфейсы

В статье Абстрактные классы, методы и свойства был рассмотрен пример наследования от абстрактного класса классов-потомков и использования абстрактных методов, как шаблонов для реализации их в классах-потомках. Было отмечено, что другой вариант реализации — использование интерфейсов.
Они позволяют обойтись без абстрактных классов, но позволяют контролировать работу методов родственных классов.
Рассмотрим тот же пример — Правильный многоугольник. Разобрав его, вам станут понятны интерфейсы и их назначение в программировании в среде .Net Framework.

Пример использования интерфейса

В пространство ИнтерфейсФигур добавим новый элемент — интерфейс IFigureInfo. Это можно сделать через Меню Проект/Добавить новый элемент/Интерфейс или Ctrl+Shift+A/Интерфейс (тогда добавится файл Interface1.cs) со следующим содержанием или же вставить это объявление в текст основного кода приложения (см. ниже).

Это означает, что во всех классах, поддерживающих этот интерфейс, должны быть реализованы эти два метода без параметров, возвращающих тип double.

Создадим три независимых класса: Circle, Square, Triangle. У каждого из этих классов есть свой набор полей и методов, связанных с их особенностями, в том числе — характерный размер — Length. Для круга это, например, его радиус, для квадрата и равностороннего треугольника — длина его стороны. Нам в каждый из классов необходимо гарантированно добавить методы, которые будут находить площадь (area) и периметр (perimeter) этих фигур.
Вот тут то и могут пригодиться интерфейсы!
Для этого поле имени класса через двоеточие укажем имя интерфейса. Сделаем это и для других классов Square и Triangle.

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

Ошибка «ИнтерфейсФигур.Square» не содержит определения для «area» и не был найден метод расширения «area», принимающий тип «ИнтерфейсФигур.Square» в качестве первого аргумента.

В класс Program добавим статический метод InfoFigure для контроля работы методов класса. Тогда код файла Program.cs будет следующим:

Результат совпадает с ранее рассмотренным примером(проверьте!).

Теперь немного теории.

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

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

Часто бывает необходимо реализовать несколько классов, при этом у них будут одинаковые методы (по названию!), но они по-разному должны быть реализованы.

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

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

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

Свойства интерфейсов

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

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

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

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

Благодаря поддержке интерфейсов в C# может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов.

Объявление интерфейсов

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

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

Как пояснялось выше, в интерфейсе не может быть никакой реализации. Поэтому все методы интерфейса должны быть реализованы в каждом классе, включающем в себя этот интерфейс. В самом же интерфейсе методы неявно считаются открытыми, поэтому доступ (public) к ним не нужно указывать явно.

Помимо методов, в интерфейсах можно также указывать свойства, индексаторы и события. Интерфейсы не могут содержать член-данные (кроме событий?). В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как static.

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

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

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

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

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

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

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

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

Интерфейсы, как и классы, могут наследоваться:

При применении этого интерфейса класс BaseAction должен будет реализовать как методы и свойства интерфейса IRunAction, так и методы и свойства базового интерфейса IAction.

Однако в отличие от классов мы не можем применять к интерфейсам модификатор sealed (а также к методам интерфейсов), чтобы запретить наследование интерфейсов.

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

Однако методы интерфейсов могут использовать ключевое слово new для сокрытия методов из базового интерфейса:

Здесь метод Move из IRunAction скрывает метод Move из базового интерфейса IAction. Большого смысла в этом нет, так как в данном случае нечего скрывать, то тем не менее мы так можем делать. А класс RunAction реализует метод Move сразу для обоих интерфейсов.

Модификаторы доступа интерфейсов

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

Но при наследовании интерфейсов, как и при наследовании классов, следует учитывать, что производный интерфейс должен иметь тот же уровень доступа или более строгий, чем базовый интерфейс. Например:

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

Далее в примерах применение интерфейсов будет рассмотрено более подробно.

NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.

Зачем придумали интерфейсы?

В интернете много статей на тему интерфейсов, что это такое и как их реализовывать. Но я не нашел внятного ответа кто и зачем их придумал? Я только начинаю изучать C# и вообще не вижу смысла в интерфейсах. Классы удобны например в ситуации когда нам нужно в игре создать много врагов, нам не надо каждому врагу прописовать параметры, можно все задать в классе. Вот пример с метанина:

Реализация в классе:

Но если я просто удалю интерфейс и сделаю вот так

Код по прежнему будет работать.
Вот и вопрос зачем все это нужно?
Я просто не могу представить себе ситуацию где бы было необходимо использовать интерфейсы.
В C#8 добавили реализацию метода по умолчанию в интерфейсе тем самым сделав из интерфейса недо-класс.
Смотрю видео на ютубе и там все используют интерфейсы но не говорят зачем, просто как будто так и надо.
Не спроста же все их используют? Но без понимания зачем все это нужно у меня просто не получается найти смысл их применения и понять как работает чужой код когда в нем есть интерфейсы.
Единственный смысл в них вижу в том что не нужно писать документацию к классам, просто наследуем от интерфейса а дальше пусть уже другой программист лезет в код и смотрит чего там и как.
Ни в коем случае не хочу сказать что я тут самый умный а все остальные ошибались, просто хочу разобраться почему придумали интерфейсы если и без них все хорошо работает?
Извините что так сумбурно написал, сам на эмоциях, неделю уже читаю статьи по теме и ни как не могу осилить.

qwabra's user avatar

User12351259599491's user avatar

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

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

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

Давайте возьмем типичный пример. Вы пишете свою игру и вам надо предусмотреть сохранение вашей игры. Вы точно знаете, что именно вам надо сохранить, но вы ещё не знаете, куда вы будете сохранять, в файл или в БД или передавать данные на сервер. Что делать в такой ситуации?

В такой ситуации вам поможет интерфейс. Например, у вас есть класс, который вам надо сохранить.

Напишем для него интерфейс сохранения

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

Другими словами, интерфейс помогает определять взаимодействие между модулями вашей программы, даже если эти модули ещё не существуют.

Но тут вы можете возразить мне и сказать — что вы с таким же успехом можете определить класс для сохранения, например

Да, это верно, вы можете определить абстрактный класс. В таком случае, для реализации сохранения, вам надо будет унаследоваться от абстрактрого класса и реализовать нужные методы. Но давайте подумаем, какие ограничения это накладывает на наш класс сохранения? А вот какие — наш класс сохранения должен будет быть унаследован именно от указанного абстрактного класса и больше ни от кого. Вы можете возразить — мол, в случае интерфейса будет то же самое — ведь класс сохранения должен его реализовать. Но вот и нет — в C# реализация интерфейса никак не ограничивает класс, так как класс может реализовать сколько угодно интерфейсов. А вот наследование от абстрактного класса — уже дело другое, ведь вы не можете насловаться от 2 классов. Отсюда вторая причина использования интерфйсов — Использование интерфйсов позволяет обойти ограничения множественного наследования.

Окей, ну, допустим, мы решили, что мы будем сохранять нашу игру, но для сохранения нашей игры, нам обязательно надо знать имя текушего игрока — то есть его никнейм. Это требование леко выразить, добавив в наш абстрактный класс конструктор, который принимает имя игрока, например (код может не копилироваться, я его практически на телефоне пишу, но идея должна быть ясна)

Теперь посмотрите что произошло. Мы не только заставляем классы для сохранения игры наследоваться от нашего абстрактного класса, но ещё и накладываем на них определенные огреничения. Интересны ли эти ограничения остальной части игры? Думаю, нет. В таком случае, зачем остальной части игры знать об этих огреничениях? Нет никаких причин для этого. Таким образом, Используя интерфейс, вы декларируете желаемое поведение. Используя класс — вы декларируете детали реализации. То есть, когда у вас был интерфейс в игре, вы декларировали, что «не важно что тут будет за объект, не важно как он будет написан, но мне надо чтобы он мог сохранять и загружать состояние игры» — то есть вам было важно, что объект может делать, при этом не важно, как он это делает. В этом суть инкапсуляции. Когда же вы используете класс, особенно если класс содержит какие либо посторонние детали, вы уже покажываете, что вам важно не только что объект может делать, но и также кем объект является и какие у объекта есть детали реализации.

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

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

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