что такое интерфейс класса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

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

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

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

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

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

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

Абстракция

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

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

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

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

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

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

Интерфейс

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Интерфейсы (C++/CX)

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

Характеристики

Интерфейс имеет следующие характеристики:

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

члены интерфейса могут включать в себя свойства, методы и события;

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

поля и статические члены запрещены;

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

Объявление и использование

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

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

Иерархии наследования интерфейсов

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

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

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

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

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

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

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

Универсальные интерфейсы

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

универсальный пользователь, определенный interface class в компоненте, не может быть передан в файл метаданных Windows, поэтому он не может иметь открытый доступ, а клиентский код в других winmd-файлах не может его реализовать. Он может быть реализован неоткрытыми классами ссылок в том же компоненте. Открытый класс ссылки может иметь универсальный тип интерфейса в качестве закрытого члена.

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

параметром типа может быть любой тип среда выполнения Windows. Это означает, что параметр-тип может быть ссылочным типом, типом значения, классом интерфейса, делегатом, основным типом или открытым перечислимым классом.

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

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

Параметризовать можно только весь интерфейс, но не отдельные методы.

Параметры-типы нельзя ограничивать.

Закрытый универсальный интерфейс имеет неявно создаваемый UUID. Пользователь не может задать UUID.

Если тип параметра метода является параметром-типом, при объявлении этого параметра или переменной используется имя параметра-типа без указателя, собственной ссылки и деклараторов дескрипторов. Иначе говоря, невозможно написать «T^».

Шаблонные классы ссылок должны быть закрытыми. Они могут реализовывать универсальные интерфейсы и передавать параметр шаблона t в универсальный аргумент t. Каждый экземпляр класса ссылки в шаблоне сам по себе является классом ссылки.

Источник

Интерфейсы 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 не будет опубликован. Обязательные поля помечены *