Интерфейс что это в программировании

В чем суть интерфейсов в программировании?

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

Метод setDefaultBright принимает любой объект. Ведь мне всё равно яркость чего настраивать.
Мой код используют другие разработчики, я не могу контролировать их.
Как мне убедиться, что у объекта, который мне пришел в качестве аргумента, есть метод setBright?
Я пишу интерфейс, и говорю, что метод setDefaultBright принимает только объекты, которые реализуют этот интерфейс.

Если кроме меня самого никто не будет использовать эту систему контроля яркости. То я просто буду держать у себя в голове, что в метод setDefaultBright можно отправлять только объекты, у которых есть метод setBright, но поддержка кода усложняется, через год и не вспомнишь.

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

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

p.s: программисты дополнят и поправят.

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

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

Почему я не могу сделать это прямо в классе? Зачем интерфейс? Я же и без него могу быть уверенным, что объекты будут обладать функционалам, описав методы в самом классе. Зачем мне кто то(интерфейс) должен говорить, какие мне методы реализовать? Если я знаю, что должен описать play(), то я могу это сделать прямо в классе, не используя интерфейс. И так могу сделать со всеми, кому нужен play(). Не понятно, в чём конкретно выгода?

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

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

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

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

Да, это сделано именно для восприятия человеком кода. Легкие программы, где не особо много строк кода, не сильно сложны для восприятия человека, а программы которые имеют тысячи строк кода, уже соответственно нужно разделять на файлы в одних файлах лежат условия «что нужно сделать?», а в других «как это сделать?».

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

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

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

Без интерфейса (как концепции) немыслимы три из четырех основных понятий ООП.

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

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

А если вы делаете игру, то можете создать интерфейс Unit, тем самым задав классам определенное поведение. Например, unit должен обязательно иметь метод atack(), isDead() и т.д.

А дальше, в цикле делаете проверку всех юнитов:
loop(. ) <
if (unit.isDead())
removeFromMap(unit);
>

Ну и конечно Unit может быть и просто классом или абстрактным классом, в котором реализованы atack и isDead, а может быть только isDead, потому что attack у каждого типа юнита индивидуально и требует собственной реализации. Т.е. приходим к тому, что интерфейс это также частный случай абстрактного класса.

Т.е. тут уже вступает в действие полиформизм, т.е. интерфейсы по сути дают полиформизм. Ну, а в Java они еще позволяют делать множественное наследование или другими словами задать классу несколько свойств поведения, например Unit может быть также и Iterable, тем самым можно дать юнитам инвентарь и перебирать элементы в нем.

И соответсвенно если Unit у вас будет классом или абстрактным классом, то унаследовав Unit в Java, вы просто не сможете дать наследнику еще и Iterable поведение, если Iterable будет тоже классом.

Из-за этого, в Java приветствуется не наследование, а композиция. Т.е. нафига каждый раз реализовывать Unit.isDead, если он стандартный? Поэтому, создается скажем класс UnitAI и делается следующее:

class OrcWarrior implements Unit, Iterable <
UnitAI ai;

interface Unit <
void attack();
UnitAI getAI();
>

Вот это называется композиция, т.е. в OrcWarrior, HumanWarrior вы подмешиваете UnitAI, в котором уже реализовано isDead, и тем самым не нужно каждый раз его реализовывать одним и тем же кодом. В С++ такого можно не делать, там есть поддержка множественного наследование, но оно имеет свои минусы. Впрочем, как и композиция имеет плюсы/минусы.

Источник

Зачем нужны абстракции и интерфейсы

И что это вообще такое?

Как в старом анекдоте: про объектно-ориентированное программирование можно рассказать просто и неправильно либо сложно и неправильно. Мы попробуем рассказать про очередной аспект ООП просто.

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

В этой статье разберём два сложных понятия из объектно-ориентированного программирования: абстракции и интерфейсы. Это ещё одна ступень в понимании непостижимого.

Основные идеи из ООП

Абстракция

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

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

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

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

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

Чтобы работать с абстракциями, используют интерфейсы.

Интерфейс

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

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

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

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

Есть ещё инкапсулированные, то есть внутренние методы. Например, у микрофона есть публичный метод «Слушать голос», и есть внутренний метод «Преобразовать голос в электрические сигналы». С его помощью он взаимодействует с другими частями нашего абстрактного телефона. Про инкапсуляцию будет отдельный материал, потому что тема большая.

Сложная терминология

Строго говоря, интерфейсы — это не действия, а методы. Сейчас объясним.

В программировании есть операции — это простейшие действия, например, скопировать значение из одной переменной в другую.

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

На языке ООП функции, привязанные к объектам, называются методами. Просто такой термин. По сути это функции, то есть склеенные вместе операции.

Итого: метод — это набор простых действий, которые склеили в единое целое и засунули в объект.

Для чего это всё

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

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

Чтобы такого не было, поступают так:

Источник

ООП. Часть 6. Абстрактные классы и интерфейсы

Узнайте истинную мощь наследования и полиморфизма! Раскрываем секреты абстрактных классов и интерфейсов.

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

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

Все статьи про ООП

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.

Абстрактные классы

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

Это может понадобиться, чтобы объединить реализацию других схожих классов. Например, в вашей игре должны быть персонаж игрока и NPC (неигровые персонажи). У них могут быть общие свойства (имя, координаты) и методы (перемещение, изменение анимации).

Чтобы не повторять код несколько раз, можно вынести реализацию этих свойств и методов в абстрактный класс Character:

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

Когда объявляется реализация такого члена класса, необходимо указать ключевое слово override. Абстрактными могут быть следующие члены класса:

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

В остальном всё очень похоже на обычные классы. Например, поле Y класса Character публичное, чтобы можно было использовать его в свойстве Y дочерних классов.

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программировании

Абстрактный класс должен быть публичным.

Источник

Интерфейсы C# — Самый подробный разбор

Возможно, тебе уже приходилось слышать про механизм множественного наследования. Это когда есть возможность создать класс производный от двух и более базовых классов. Так вот, забудьте о нем, CLR его не поддерживает совсем. НО! За-то есть возможность реализовать ограниченное множественное наследование через реализацию интерфейсов. Так вот, что такое интерфейсы, чем они отличаются от классов и особенности их применения мы и будем подробно рассматривать в этой статье и прикрепленном видео.

Наследование классов и реализация интерфейсов. В чем разница?

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

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

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

За счет того, что класс непосредственно в себе содержит определения методов, CLR способна различать к какому интерфейсу относится какой метод и избегать конфликтов. Благодаря этому мы можем реализовывать несколько интерфейсов, в отличии от множественного наследования. Но вот чем похожи наследование и реализация интерфейсов, так это возможностью подставлять экземпляры производного типа на место базовых. То есть наш любимый полиморфизм. Мы можем объявить переменную интерфейсного типа и в нее поместить экземпляр любого класса, который реализует этот интерфейс. Данная схема применяется очень часто, например, в DI-контейнерах или в mock-тестах.

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

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

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

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

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

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

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

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

Данный интерфейс определяет механизм сравнения двух объектов. Теперь реализуем его в классе Point (весь исходный код доступен в GitHub):

InterfacesCSharp/ComparePoints

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

Для того, чтобы проверить работоспособность кода, просто создадим две точки в коллекции в неправильном порядке, а затем выполним сортировку по возрастанию. Метод CompareTo() выполнит сравнение точек и так как они не упорядочены мы выполним их обмен местами, после чего выведем на консоль координаты возрастающих точек.

Для методов, реализующих интерфейс есть несколько требований:

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

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

InterfacesCSharp/VirtualAndSealedImplementation

Если мы наследуем класс, где модификатор virtual не использовался, то метод Dispose() является запечатанным, а следовательно мы не можем переопределить интерфейсный метод. Однако, мы можем немного модернизировать данный код следующим образом:

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

Мы совершенно спокойно можем переопределить реализацию интерфейсного метода, так как он НЕ запечатан.

Но что нам делать, если нам нужно переопределить запечатанный у предка метод и при этом сохранить связь с интерфейсом? И для этого тоже есть решение:

Подробнее о вызовах интерфейсных методов

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программированииПомощник IntelliSence отображает весь список доступных методов, как определенных в самом классе (например, EndsWith()), в интерфейсах (CompareTo()) и в object (GetHashCode())

Но в CLR у нас также есть замечательная возможность определять переменные интерфейсного типа. В таком случае, эта переменная может содержать в себе, ну а если точнее, то ссылаться на экземпляр любого типа, который реализует этот интерфейс. В данном случае для использования будут доступны только те методы, которые определены в самом интерфейсе, плюс методы из object, так как любой тип унаследован от object и CLR может быть на 100% уверена, что эти методы там будут.

InterfacesCSharp/CallingInterfaceMethod

GetMethods() возвращает имена доступных методов типа. Но так как перегрузка метода считается за отдельный метод, то на выходе получается повторение некоторых методов несколько раз. И с помощью Select и Distinct я просто избавляюсь от повторов перегруженных методов.

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

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

Явная и неявная реализация интерфейсных методов

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

InterfacesCSharp/ExtinctInterfaceMethodImplementation

Соответственно, для типа SimpleType должны быть созданы в метаданных:

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

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

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

Обобщенные интерфейсы

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

Ну а теперь к преимуществам. Если коротко — их три:

Давай рассмотрим, данные преимущества обобщенных интерфейсов на примере:

InterfacesCSharp/GenericInterfaces

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

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

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

Обобщения и ограничения интерфейса

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

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

InterfacesCSharp/MethodConstraints

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

Реализация нескольких интерфейсов с одинаковыми сигнатурами и именами методов

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

InterfacesCSharp/NameConflict

Объявим два интерфейса, у которых совпадают сигратуры методов (имя и принимаемые аргументы), но которые при этом имеют совершенно разную логику:

И потом получается так, что один и тот же класс реализует оба этих интерфейса:

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

Таким образом, если мы хотим вызывать явные реализации методов интерфейса, нам необходимо выполнить явное приведение экземпляра класса к этому интерфейсу и только тогда вызывать метод. Кстати, именно по такому принципу работают и новые реализации интерфейсов по умолчанию. Просто явная реализация интерфейса находится не в классе, реализующем интерфейс, а в самом файле интерфейса. А для вызова реализации по умолчанию точно также необходимо выполнять явное приведение к типу интерфейса. Обрати внимание, что эта возможность появилась только в C# 8.0.

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

Interfaces.Csharp.DefaultInterfaceImplementation

Создадим интерфейс и прямо внутри интерфейсы напишем реализацию метода по умолчанию. Затем будет реализовывать этот интерфейс используя разные подходы.

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

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

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

Совершенствование контроля типов за счет явной реализации интерфейсных методов

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

InterfacesCSharp/EimiForNoneGenericInterfaces

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

И реализуем его в тривиальном значимом типе:

При работе с этим типом мы можем столкнуться с нежелательным поведением системы — падение производительности за счет упаковки, а также возможность совершить ошибку при написании кода, так как не соответствие типов не будет определяться синтаксическим анализатором Visual Studio.

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

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

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

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

Путаница в документации или еще отсутствие

Интерфейс что это в программировании. Смотреть фото Интерфейс что это в программировании. Смотреть картинку Интерфейс что это в программировании. Картинка про Интерфейс что это в программировании. Фото Интерфейс что это в программированииИнформация о типе int

InterfacesCSharp/DangerousOfEimi

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

Но даже подобный ход является далеко не оптимальным решением из-за второй проблемы

Упаковка

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

EIMI нельзя вызывать из производных типов

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

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

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

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

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

Базовый класс или интерфейс C#?

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

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

Как использовать базовый абстрактный класс C#

InterfacesCSharp/BaseClassVsInterface

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

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

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

Как использовать интерфейсы C#

Рассмотрим другой синтетический пример. У нас есть интерфейс для всех геометрических фигур, которые определяет метод для вычисления пощади.

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

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

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

Interface ⇽ BaseClass ⇽ ParticularClass

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

Советую прочитать предыдущую статью — 7 обязательных навыков дата-сайентиста
А также подписывайтесь на группу ВКонтакте, Telegram, Инстаграм и YouTube-канал. Там еще больше полезного и интересного для программистов.

Источник

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

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