ООП. Часть 6. Абстрактные классы и интерфейсы
Узнайте истинную мощь наследования и полиморфизма! Раскрываем секреты абстрактных классов и интерфейсов.
В предыдущей статье мы увидели, насколько удобнее становится ООП благодаря наследованию. Но оно может стать ещё лучше, если использовать абстрактные классы и интерфейсы.
Все статьи про ООП
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Абстрактные классы
Особенность абстрактных классов в том, что их можно использовать только как родительский класс, то есть вы не можете создать объект. Для их объявления используется ключевое слово abstract.
Это может понадобиться, чтобы объединить реализацию других схожих классов. Например, в вашей игре должны быть персонаж игрока и NPC (неигровые персонажи). У них могут быть общие свойства (имя, координаты) и методы (перемещение, изменение анимации).
Чтобы не повторять код несколько раз, можно вынести реализацию этих свойств и методов в абстрактный класс Character:
Тут всё как у обычных классов, но в конце можно заметить объявление свойства и метода без реализации. Реализация этих абстрактных свойств должна находиться в дочернем классе:
Когда объявляется реализация такого члена класса, необходимо указать ключевое слово override. Абстрактными могут быть следующие члены класса:
Дочерний класс должен реализовывать все члены родительского абстрактного класса, кроме тех случаев, когда дочерний класс тоже абстрактный.
В остальном всё очень похоже на обычные классы. Например, поле Y класса Character публичное, чтобы можно было использовать его в свойстве Y дочерних классов.
Абстрактный класс должен быть публичным.
Зачем нужны абстракции и интерфейсы
И что это вообще такое?
Как в старом анекдоте: про объектно-ориентированное программирование можно рассказать просто и неправильно либо сложно и неправильно. Мы попробуем рассказать про очередной аспект ООП просто.
Зачем это: ООП — одна из главных концепций современной разработки. Она применима не к каким-то конкретным языкам, это скорее способ мышления в программировании. Если вы понимаете ООП, ваш код на любом языке будет чище, читаемее и эффективнее.
В этой статье разберём два сложных понятия из объектно-ориентированного программирования: абстракции и интерфейсы. Это ещё одна ступень в понимании непостижимого.
Основные идеи из ООП
Абстракция
Представьте, что вы попросили нескольких человек описать в общих чертах, что такое телефон и как им пользоваться: пусть это будут бабушка, мама и подруга. Бабушка вспомнит про дисковые телефоны и трубки с витым проводом. Мама расскажет про радиотелефоны, у которых есть база и есть трубка, с которой можно ходить по всей квартире, а подруга начнёт описывать мобильник.
Несмотря на то что рассказы будут сильно отличаться между собой, у них будет несколько общих моментов про телефон:
Получается, что если представить абстрактный телефон, то получится такое устройство с динамиком, микрофоном и средством набора номера.
Это и есть абстракция: когда мы описываем только самые существенные детали, которые важны для задачи. В нашем случае задача такая — понять, что такое телефон и как им пользоваться. Поэтому микрофон и динамик для этой задачи важен, а способ связи телефона с сетью — нет. Устройство набора номера важно, а то, какая мелодия играет при вызове — нет.
🔥 Абстракция — это когда мы сосредотачиваемся только на существенных для задачи деталях и игнорируем всё остальное. В ООП абстракция означает, что для каждого объекта мы задаём минимальное количество методов, полей и описаний, которые позволят нам решить задачу. Чем меньше характеристик, тем лучше абстракция, но ключевые характеристики убирать нельзя.
Чтобы работать с абстракциями, используют интерфейсы.
Интерфейс
Итак, у нас есть некое устройство с трубкой, микрофоном, динамиком и средством набора номера. Но если вы вспомните рассказы мамы, бабушки и подруги, то обнаружите вот что:
Всё это — интерфейсы. Они позволяют работать с объектом, не вникая в то, как он устроен внутри. Если вы умеете работать с интерфейсом номеронабирателя, то вам всё равно, нужно ли крутить диск, нажимать физические кнопки на радиотрубке или давить пальцем на сенсорный экран.
Такой интерфейс как бы говорит нам — я передам в телефон любые цифры, какие захочешь. Как я это сделаю внутри и как они будут обработаны — неважно, просто набери номер, а дальше телефон сам разберётся.
Интерфейсы — это действия над объектом, доступные другим объектам (поэтому они называются публичными).
Есть ещё инкапсулированные, то есть внутренние методы. Например, у микрофона есть публичный метод «Слушать голос», и есть внутренний метод «Преобразовать голос в электрические сигналы». С его помощью он взаимодействует с другими частями нашего абстрактного телефона. Про инкапсуляцию будет отдельный материал, потому что тема большая.
Сложная терминология
Строго говоря, интерфейсы — это не действия, а методы. Сейчас объясним.
В программировании есть операции — это простейшие действия, например, скопировать значение из одной переменной в другую.
Из простых действий составляются функции — это когда несколько операций «склеиваются» в нечто единое. Мы даём этой склейке название и получаем функцию. Например, может быть функция «проверить правильность электронного адреса», которая состоит из нескольких десятков простых операций.
На языке ООП функции, привязанные к объектам, называются методами. Просто такой термин. По сути это функции, то есть склеенные вместе операции.
Итого: метод — это набор простых действий, которые склеили в единое целое и засунули в объект.
Для чего это всё
Допустим, вы работаете в команде над большим продуктом. В таких случаях удобно разделить одну большую программу на множество мелких подпрограмм и сервисов, каждый из которых решает свою узкую задачу.
Если заранее не договориться о том, как эти компоненты обмениваются данными между собой, то может случиться то, о чём мы уже предупреждали:
Чтобы такого не было, поступают так:
Интерфейсы (C++/CX)
Хотя класс ссылки может наследовать не более чем от одного конкретного базового класса, он может реализовывать любое количество классов интерфейсов. Сам класс интерфейса (или структура интерфейса) может наследовать (или требовать) несколько классов интерфейсов, может перегружать свои функции-члены и иметь параметры-типы.
Характеристики
Интерфейс имеет следующие характеристики:
Класс интерфейса (или структура) должен быть объявлен в пространстве имен, а также может иметь режим доступа public (открытый) или private (закрытый). Только открытые интерфейсы формируют метаданные.
члены интерфейса могут включать в себя свойства, методы и события;
Все члены интерфейса неявно являются открытыми и виртуальными.
поля и статические члены запрещены;
типы, используемые в качестве свойств, параметров методов или возвращаемых значений, могут быть только среда выполнения Windows типами. сюда относятся фундаментальные типы и типы классов enum.
Объявление и использование
В следующем примере показан способ объявления интерфейса. Обратите внимание, что интерфейс можно объявить или как класс, или как тип структуры.
Для реализации интерфейса класс ссылки или структура ссылки объявляет и реализует виртуальные методы и свойства. Интерфейс и реализующий его класс ссылки должны использовать одинаковые имена параметров методов, как показано в следующем примере:
Иерархии наследования интерфейсов
Интерфейс может наследовать от одного или нескольких интерфейсов. Но в отличие от структуры и класса ссылки, интерфейс не объявляет наследуемые члены интерфейса. Если интерфейс B наследует от интерфейса A, а класс ссылки C наследует от интерфейса B, класс C должен реализовывать и A, и B. Это показано в следующем примере.
Реализация свойств и событий интерфейса
Как показано в предыдущем примере, для реализации свойств интерфейса можно использовать тривиальные виртуальные свойства. Также можно определить пользовательские методы получения и задания в реализующем классе. Оба эти метода должны быть открытыми в свойстве интерфейса.
Если интерфейс объявляет свойство, доступное только для получения или только для задания, реализующий класс должен явно предоставить метод получения или задания.
Кроме того, можно реализовать пользовательские методы добавления и удаления для событий в реализующем классе.
Явная реализация интерфейса
Если класс ссылки реализует несколько интерфейсов, и эти интерфейсы содержат методы, имена и сигнатуры которых идентичны компилятору, можно использовать следующий синтаксис для явного указания метода интерфейса, который реализуется методом класса.
Универсальные интерфейсы
в C++/cx generic ключевое слово используется для представления среда выполнения Windows параметризованного типа. Параметризованный тип передается в метаданные и может использоваться кодом, который написан на любом языке, поддерживающем параметры-типы. среда выполнения Windows определяет некоторые универсальные интерфейсы, например Windows:: Foundation:: collections:: IVector T >, но не поддерживает создание общедоступных определяемых пользователем универсальных интерфейсов в C++/CX. Однако можно создавать закрытые универсальные интерфейсы.
вот как можно использовать типы среда выполнения Windows для создания универсального интерфейса:
Универсальный определяемый пользователем interface class в компоненте не может передаваться в соответствующий файл метаданных Windows; поэтому он не может быть открытым и не может реализовываться клиентским кодом в других файлах WinMD. Он может быть реализован неоткрытыми классами ссылок в том же компоненте. Открытый класс ссылки может иметь универсальный тип интерфейса в качестве закрытого члена.
Универсальный интерфейс должен следовать стандартным правилам интерфейсов, регламентирующим возможности доступа, члены, отношения requires (требует), базовые классы и т. д.
параметром типа может быть любой тип среда выполнения Windows. Это означает, что параметр-тип может быть ссылочным типом, типом значения, классом интерфейса, делегатом, основным типом или открытым перечислимым классом.
Закрытый универсальный интерфейс — это интерфейс, наследующий от универсального интерфейса и указывающий аргументы конкретного типа для всех параметров-типов. Его можно использовать везде, где допускается использовать неуниверсальный закрытый интерфейс.
Открытый универсальный интерфейс — это интерфейс, имеющий один или несколько параметров-типов, для которых пока не предоставлено никаких конкретных типов. Его можно использовать везде, где допускается использовать типы, в том числе в качестве аргумента-типа другого универсального интерфейса.
Параметризовать можно только весь интерфейс, но не отдельные методы.
Параметры-типы нельзя ограничивать.
Закрытый универсальный интерфейс имеет неявно создаваемый UUID. Пользователь не может задать UUID.
Если тип параметра метода является параметром-типом, при объявлении этого параметра или переменной используется имя параметра-типа без указателя, собственной ссылки и деклараторов дескрипторов. Иначе говоря, невозможно написать «T^».
Шаблонные классы ссылок должны быть закрытыми. Они могут реализовывать универсальные интерфейсы и передавать параметр шаблона t в универсальный аргумент t. Каждый экземпляр класса ссылки в шаблоне сам по себе является классом ссылки.
BestProg
Интерфейсы
Поиск на других ресурсах:
1. Назначение интерфейсов. Особенности применения интерфейсов в C#
Интерфейс определяет ряд методов (свойств, индексаторов, событий), которые должны быть реализованы в классе, который наследует (реализует) данный интерфейс. Интерфейсы используются для того, чтобы указать классам, что именно нужно реализовать в этих классах. Реализовывать нужно методы (свойства, индексаторы, события). Таким образом, интерфейс описывает функциональные возможности без конкретной реализации. Иными словами интерфейс определяет спецификацию но не реализацию.
Использование интерфейсов есть эффективным в случаях, когда нужно создать альтернативу множественного наследования. Любой класс может унаследовать несколько интерфейсов. При этом все методы унаследованных интерфейсов должны быть реализованы в классе.
Структура также как и класс может реализовывать любое количество интерфейсов.
Особенности интерфейсов
При использовании интерфейсов в классах-наследниках:
⇑
2. Какое отличие между интерфейсами и абстрактными классами?
В языке программирования C# существуют следующие отличия между интерфейсами и абстрактными классами:
⇑
3. Сколько классов могут иметь реализацию методов интерфейса?
Если интерфейс определен, то он может быть реализован в любом количестве классов.
⇑
4. Сколько интерфейсов может быть реализовано в одном классе?
В одном классе может быть реализовано любое количество интерфейсов.
⇑
5. Какой общий вид описания интерфейса?
Кроме методов, в интерфейсах можно указывать свойства, события и индексаторы.
⇑
6. Какие элементы языка программирования можно указывать в интерфейсах?
В интерфейсах можно указывать:
⇑
7. Как выглядит общая форма реализации интерфейса в классе?
Общая форма реализации интерфейса в классе имеет следующий вид:
где имя_интерфейса – имя интерфейса, методы (свойства, индексаторы, события) которого реализуются в классе. Класс обязательно должен реализовать все методы интерфейса.
⇑
8. Какая общая форма класса реализующего несколько интерфейсов?
Класс может реализовать несколько интерфейсов. В этом случае все интерфейсы определяются списком через запятую.
Общая форма класса реализующего несколько интерфейсов:
⇑
9. Пример объявления интерфейса и класса наследующего этот интерфейс
Пример описания класса использующего этот интерфейс.
Это связано с тем, что в самом интерфейсе эти методы неявно считаются открытыми ( public ). Поэтому их реализация должна быть открытой.
⇑
10. Пример объявления двух интерфейсов и класса, который реализует методы этих интерфейсов
В нижеследующем примере объявлено два интерфейса с именами MyInterface и MyInterface2. Первый интерфейс содержит 4 методы. Второй интерфейс содержит 1 метод.
Также объявлен класс MyClass, использующий эти два интерфейса. Класс обязательно должен реализовать все методы обоих интерфейсов, то есть в сумме 5 методов.
⇑
11. Пример использования ссылки на интерфейс для доступа к методам класса
В C# допускается описывать ссылки на интерфейс. Если описать переменную-ссылку на интерфейс, то с ее помощью можно вызвать методы класса, который использует этот интерфейс.
Пример.
С помощью ссылки на интерфейс можно иметь доступ к методам классов, которые реализуют описанные в этом интерфейсе методы.
⇑
12. Каким образом в интерфейсе описывается свойство?
Свойство описывается в интерфейсе без тела. Общая форма объявления интерфейсного свойства следующая:
⇑
13. Пример интерфейса, в котором описывается индексатор.
Общая форма объявления интерфейсного индексатора имеет вид:
⇑
14. Какие элементы программирования языка C# нельзя описывать в интерфейсах?
Интерфейсы не могут содержать:
⇑
15. Как работает механизм наследования интерфейсов?
Интерфейс может наследовать другой интерфейс. Синтаксис наследования интерфейсов такой же, как и у классов.
Общая форма наследования интерфейса следующая:
где имя_интерфейса – имя интерфейса, который наследует другие интерфейсы;
Пример. В данном примере класс MyClass использует интерфейс, который наследует другой интерфейс. В классе нужно реализовать все методы (свойства, индексаторы, события) интерфейса MyInterface1 и интерфейса MyInterface2.
⇑
16. Что такое явная реализация члена интерфейса?
Если перед именем метода (свойства, индексатора, события) стоит имя интерфейса через разделитель ‘ . ‘ (точка), то это называется явной реализацией члена интерфейса.
Пример явной реализации.
⇑
17. Когда целесообразно применять явную реализацию члена интерфейса? Примеры.
Явная реализация члена интерфейса применяется в следующих случаях:
Пример 1. Явная реализация интерфейсного метода. По интерфейсной ссылке метод есть доступен, а по объекту класса недоступен.
⇑
18. В каких случаях лучше использовать интерфейс, а в каких абстрактный класс?
Интерфейс целесообразно использовать в случаях, если некоторые понятия должны быть описаны с точки зрения функционального назначения, без уточнения деталей реализации.
Абстрактный класс целесообразно использовать тогда, когда все же нужно уточнять некоторые детали реализации.
ООП с примерами (часть 1)
Волею судьбы мне приходится читать спецкурс по паттернам проектирования в вузе. Спецкурс обязательный, поэтому, студенты попадают ко мне самые разные. Конечно, есть среди них и практикующие программисты. Но, к сожалению, большинство испытывают затруднения даже с пониманием основных терминов ООП.
Для этого я постарался на более-менее живых примерах объяснить базовые понятия ООП (класс, объект, интерфейс, абстракция, инкапсуляция, наследование и полиморфизм).
Первая часть, представленная ниже, посвящена классам, объектам и интерфейсам.
Вторая часть иллюстрирует инкапсуляцию, полиморфизм и наследование
Основные понятия ООП
Класс
Представьте себе, что вы проектируете автомобиль. Вы знаете, что автомобиль должен содержать двигатель, подвеску, две передних фары, 4 колеса, и т.д. Ещё вы знаете, что ваш автомобиль должен иметь возможность набирать и сбавлять скорость, совершать поворот и двигаться задним ходом. И, что самое главное, вы точно знаете, как взаимодействует двигатель и колёса, согласно каким законам движется распредвал и коленвал, а также как устроены дифференциалы. Вы уверены в своих знаниях и начинаете проектирование.
Вы описываете все запчасти, из которых состоит ваш автомобиль, а также то, каким образом эти запчасти взаимодействуют между собой. Кроме того, вы описываете, что должен сделать пользователь, чтобы машина затормозила, или включился дальний свет фар. Результатом вашей работы будет некоторый эскиз. Вы только что разработали то, что в ООП называется класс.
Класс – это способ описания сущности, определяющий состояние и поведение, зависящее от этого состояния, а также правила для взаимодействия с данной сущностью (контракт).
С точки зрения программирования класс можно рассматривать как набор данных (полей, атрибутов, членов класса) и функций для работы с ними (методов).
С точки зрения структуры программы, класс является сложным типом данных.
В нашем случае, класс будет отображать сущность – автомобиль. Атрибутами класса будут являться двигатель, подвеска, кузов, четыре колеса и т.д. Методами класса будет «открыть дверь», «нажать на педаль газа», а также «закачать порцию бензина из бензобака в двигатель». Первые два метода доступны для выполнения другим классам (в частности, классу «Водитель»). Последний описывает взаимодействия внутри класса и не доступен пользователю.
В дальнейшем, несмотря на то, что слово «пользователь» ассоциируется с пасьянсом «Косынка» и «Microsoft Word», мы будем называть пользователями тех программистов, которые используют ваш класс, включая вас самих. Человека, который является автором класса, мы будем называть разработчиком.
Объект
Вы отлично потрудились и машины, разработанные по вашим чертежам, сходят с конвейера. Вот они, стоят ровными рядами на заводском дворе. Каждая из них точно повторяет ваши чертежи. Все системы взаимодействуют именно так, как вы спроектировали. Но каждая машина уникальна. Они все имеют номер кузова и двигателя, но все эти номера разные, автомобили различаются цветом, а некоторые даже имеют литьё вместо штампованных дисков. Эти автомобили, по сути, являются объектами вашего класса.
Объект (экземпляр) – это отдельный представитель класса, имеющий конкретное состояние и поведение, полностью определяемое классом.
Говоря простым языком, объект имеет конкретные значения атрибутов и методы, работающие с этими значениями на основе правил, заданных в классе. В данном примере, если класс – это некоторый абстрактный автомобиль из «мира идей», то объект – это конкретный автомобиль, стоящий у вас под окнами.
Интерфейс
Когда мы подходим к автомату с кофе или садимся за руль, мы начинаем взаимодействие с ними. Обычно, взаимодействие происходит с помощью некоторого набора элементов: щель для приёмки монеток, кнопка выбора напитка и отсек выдачи стакана в кофейном автомате; руль, педали, рычаг коробки переключения передач в автомобиле. Всегда существует некоторый ограниченный набор элементов управления, с которыми мы можем взаимодействовать.
Интерфейс – это набор методов класса, доступных для использования другими классами.
Очевидно, что интерфейсом класса будет являться набор всех его публичных методов в совокупности с набором публичных атрибутов. По сути, интерфейс специфицирует класс, чётко определяя все возможные действия над ним.
Хорошим примером интерфейса может служить приборная панель автомобиля, которая позволяет вызвать такие методы, как увеличение скорости, торможение, поворот, переключение передач, включение фар, и т.п. То есть все действия, которые может осуществить другой класс (в нашем случае – водитель) при взаимодействии с автомобилем.
При описании интерфейса класса очень важно соблюсти баланс между гибкостью и простотой. Класс с простым интерфейсом будет легко использовать, но будут существовать задачи, которые с помощью него решить будет не под силу. В то же время, если интерфейс будет гибким, то, скорее всего, он будет состоять из достаточно сложных методов с большим количеством параметров, которые будут позволять делать очень многое, но использование его будет сопряжено с большими сложностями и риском совершить ошибку, что-то перепутав.
Примером простого интерфейса может служить машина с коробкой-автоматом. Освоить её управление очень быстро сможет любая блондинка, окончившая двухнедельные курсы вождения. С другой стороны, чтобы освоить управление современным пассажирским самолётом, необходимо несколько месяцев, а то и лет упорных тренировок. Не хотел бы я находиться на борту Боинга, которым управляет человек, имеющий двухнедельный лётный стаж. С другой стороны, вы никогда не заставите автомобиль подняться в воздух и перелететь из Москвы в Вашингтон.






