Что собой представляют классы объекты интерфейсы

Интерфе́йс (англ. interface ) — программная/синтаксическая структура, определяющая отношение между объектами, которые разделяют определённое поведенческое множество и не связаны никак иначе. При проектировании классов, разработка интерфейса тождественна разработке спецификации (множества методов, которые каждый класс, использующий интерфейс, должен реализовывать).

Интерфейсы, наряду с абстрактными классами и протоколами, устанавливают взаимные обязательства между элементами программной системы, что является фундаментом концепции программирования по контракту (англ. design by contract , DbC). Интерфейс определяет границу взаимодействия между классами или компонентами, специфицируя определённую абстракцию, которую осуществляет реализующая сторона.

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

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

Содержание

Описание и использование интерфейсов [ править | править код ]

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

  • Имя интерфейса строится по тем же правилам, что и другие идентификаторы используемого языка программирования. Разные языки и среды разработки имеют различные соглашения по оформлению кода, в соответствии с которыми имена интерфейсов могут формироваться по некоторым правилам, которые помогают отличать имя интерфейса от имён других элементов программы. Например, в технологии COM и во всех поддерживающих её языках действует соглашение, следуя которому, имя интерфейса строится по шаблону « I », то есть состоит из написанного с заглавной буквы осмысленного имени, которому предшествует заглавная латинская буква I ( IUnknown , IDispatch , IStringList и т. п.).
  • Методы интерфейса. В описании интерфейса определяются имена и сигнатуры входящих в него методов, то есть процедур или функций класса.

Использование интерфейсов возможно двумя способами:

  • Класс может реализовывать интерфейс. Реализация интерфейса заключается в том, что в описании класса данный интерфейс указывается как реализуемый, а в коде класса обязательно определяются все методы, которые описаны в интерфейсе, в полном соответствии с сигнатурами из описания этого интерфейса. То есть, если класс реализует интерфейс, для любого экземпляра этого класса существуют и могут быть вызваны все описанные в интерфейсе методы. Один класс может реализовать несколько интерфейсов одновременно.
  • Возможно объявление переменных и параметров методов как имеющих тип «интерфейс». В такую переменную или параметр может быть записан экземпляр любого класса, реализующего интерфейс. Если интерфейс объявлен как тип возвращаемого значения функции, это означает, что функция возвращает объект класса, реализующего данный интерфейс.

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

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

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

Например, интерфейс « Cloneable » может описать абстракцию клонирования (создания точных копий) объектов, специфицировав метод « Clone », который должен выполнять копирование содержимого объекта в другой объект того же типа. Тогда любой класс, объекты которого может понадобиться копировать, должен реализовать интерфейс Cloneable и предоставить метод Clone , а в любом месте программы, где требуется клонирование объектов, для этой цели у объекта вызывается метод Clone . Причём, использующему этот метод коду достаточно иметь только описание интерфейса, он может ничего не знать о фактическом классе, объекты которого копируются. Таким образом, интерфейсы позволяют разбить программную систему на модули без взаимной зависимости кода.

Интерфейсы и абстрактные классы [ править | править код ]

Можно заметить, что интерфейс, с формальной точки зрения, — это просто чистый абстрактный класс, то есть класс, в котором не определено ничего, кроме абстрактных методов. Если язык программирования поддерживает множественное наследование и абстрактные методы (как, например, C++), то необходимости во введении в синтаксис языка отдельного понятия «интерфейс» не возникает. Данные сущности описываются с помощью абстрактных классов и наследуются классами для реализации абстрактных методов.

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

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

  • если объект может параллельно наследовать n классов, существует n независимых способов к нему обращаться, а значит должно существовать (n — 1) дополнительных указателей на него; с точки зрения автоматического управления памятью это будет означать, что возникают ссылки, указывающие в середину объекта;
  • поддержка виртуальных вызовов подразумевает, что в объекте хранится ссылка на его виртуальную таблицу, а в случае множественного наследования n ссылок; активное использовании множественного наследования сильно увеличит объём памяти, занимаемый каждым объектом (экземпляром).

Использование схемы с интерфейсами (вместо множественного наследования) позволяет отбросить эти проблемы, если не считать вопроса о вызове интерфейсных методов (то есть виртуальных вызовов методов при множественном наследовании, см. выше). Классическое решение состоит в том (например, в JVM для Java или CLR для C#), что интерфейсные методы вызываются менее эффективным способом, без помощи виртуальной таблицы: при каждом вызове сначала определяется конкретный класс объекта, а затем в нём ищется нужный метод (разумеется, с многочисленными оптимизациями).

Множественное наследование и реализация интерфейсов [ править | править код ]

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

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

  • Запрет. В одном классе просто запрещается реализовывать несколько интерфейсов, имеющих методы с одинаковыми сигнатурами. Если для какого-то класса требуется комбинация несовместимых интерфейсов, программист должен выбрать другой путь решения проблемы, например, выделить несколько классов, каждый из которых реализует один из необходимых интерфейсов, и использовать их экземпляры совместно.
  • Явное разрешение неоднозначности. В случае обнаружения компилятором коллизии от программиста требуется явно указать, метод какого из интерфейсов он реализует и вызывает. То есть одноимённые методы реализуются раздельно, а при вызове указывается, какой из них вызывается. При вызове одноимённых методов через переменную типа «интерфейс» неоднозначность не возникает, если использованный в качестве типа переменной интерфейс имеет только один метод с заданным именем. Вариантом этого решения является явное переименование для совпадающих по именам наследуемых или реализуемых методов, за счёт чего в пределах реализующего класса нет одноимённых методов, но при обращении через интерфейс всегда вызывается нужная реализация.
  • Общая реализация одноимённых методов. Если наследуется или реализуется несколько методов с одной и той же сигнатурой, то они объединяются в интерфейсе-наследнике, а в классе-реализаторе получают одну общую реализацию. Это хорошо подходит для случаев, когда одноимённые методы разных интерфейсов идентичны по предполагаемой функциональности, но может вызвать нежелательные эффекты, если поведение этих методов должно различаться.
Читайте также:  Раздел гости в одноклассниках

Интерфейсы с реализованными функциями [ править | править код ]

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

  • Обеспечить функцию-утилиту, обращающуюся к абстрактным функциям интерфейса. Пример: команда «записать целое число в поток» преобразует число в нужный порядок байтов и вызывает функцию «записать буфер памяти в поток». Функции-утилиты невиртуальны (если позволяет язык). Если есть методы расширения, рекомендуется налаживать утилиты именно через них.
  • Обеспечить эталонную реализацию функции. Она будет обращаться к абстрактным функциям, работать (возможно, неэффективно) с большинством реализаций и документировать, как должны вести себя реализации. Пример: функция «остаток байтов в потоке» вычитает из длины потока положение каретки в нём. Но у потока «порт» нет ни длины, ни положения каретки, но остаток есть — количество байтов в буфере ввода. У потока «буфер в памяти» функция неэффективна и лучше напрямую вычесть один указатель из другого.
  • Предложить наиболее частую реализацию. Она может обращаться к абстрактным функциям, а может быть пустой или тривиальной. Например, команда «сбросить буфера у потока» по умолчанию ничего не делает, и это верная реализация, например, для потока «буфер в памяти» или «файл в архиве (только для чтения)».

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

Интерфейсы в конкретных языках и системах [ править | править код ]

Реализация интерфейсов во многом определяется исходными возможностями языка и целью, с которой интерфейсы введены в него. Очень показательны особенности использования интерфейсов в языках Java, Object Pascal системы Delphi и C++, поскольку они демонстрируют три принципиально разные ситуации: изначальная ориентация языка на использование концепции интерфейсов, их применение для совместимости и их эмуляция классами.

  • В Java интерфейсы изначально входят в язык, являясь неотъемлемой его частью.
  • В объектной подсистеме языка Object Pascal никаких интерфейсов не было, их поддержка была введена в Delphi 2 для обеспечения написания и использования COM-компонентов. Соответственно, механизм интерфейсов Delphi ориентирован, в первую очередь, на использование технологии COM.
  • В C++ интерфейсов, строго говоря, нет вообще. Механизм, аналогичный интерфейсам (и исторически предшествующий им) реализуется другими средствами чрезвычайно мощной объектной подсистемы этого языка.

Delphi [ править | править код ]

В Delphi интерфейсы были введены для поддержки технологии COM фирмы Microsoft. Однако при выпуске Kylix интерфейсы как элемент языка были «отвязаны» от технологии COM. Все интерфейсы наследуются от интерфейса IInterface [1], который на платформе win32 совпадает с IUnknown , стандартным одноимённым COM-интерфейсом, подобно тому, как все классы в нём являются наследниками класса TObject . Явное использование в качестве предка IUnknown оставлено для кода, использующего технологию COM.

Пример объявления интерфейса:

Для того, чтобы объявить о реализации интерфейсов, в описании класса необходимо указать их имена в скобках после ключевого слова class , после имени класса-предка. Так как «интерфейс — это договор, который нужно выполнить», программа не компилируется, пока в реализующем классе не будет реализована procedure DoSomething;

Вышеупомянутая ориентированность интерфейсов Delphi на технологию COM привела к некоторым неудобствам. Дело в том, что интерфейс IInterface (от которого наследуются все остальные интерфейсы) уже содержит три обязательных для COM-интерфейсов метода: QueryInterface , _AddRef , _Release . Следовательно, любой класс, реализующий любой интерфейс, обязан реализовать эти методы, даже если по логике программы интерфейс и класс не имеют никакого отношения к COM. Необходимо заметить, что данные три метода также используются для контроля времени жизни объекта и реализации механизма запроса интерфейса через оператор « as ».

Пример класса, реализующего интерфейс:

Программист должен правильно реализовать методы QueryInterface , _AddRef , _Release . Чтобы избавиться от необходимости писать стандартные методы, предусмотрен библиотечный класс TInterfacedObject — он реализует три вышеупомянутых метода, и любой класс, наследуемый от него и его потомков, получает эту реализацию. Реализация этих методов в TInterfacedObject предполагает автоматический контроль за временем жизни объекта путём подсчета ссылок через методы _AddRef и _Release , которые вызываются автоматически при входе в область видимости и выходе из неё.

Пример класса — наследника TInterfacedObject :

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

Пример произвольного класса без подсчета ссылок:

C++ [ править | править код ]

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

Интерфейс реализуется через наследование (благодаря наличию множественного наследования возможно реализовать в одном классе несколько интерфейсов, если в этом есть необходимость; в примере ниже наследование не множественное):

Тестируем всё вместе:

Java [ править | править код ]

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

Объявление интерфейсов очень похоже на упрощённое объявление классов.

Оно начинается с заголовка. Сначала указываются модификаторы. Интерфейс может быть объявлен как public , и тогда он будет доступен для общего использования, либо модификатор доступа может не указываться, в этом случае интерфейс доступен только для типов своего пакета. Модификатор abstract для интерфейса не требуется, поскольку все интерфейсы являются абстрактными классами. Его можно указать, но делать этого не рекомендуется, чтобы не загромождать код.

Далее записывается ключевое слово interface и имя интерфейса.

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

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

Затем в фигурных скобках записывается тело интерфейса.

Пример объявления интерфейса (Ошибка если Colorable и Resizable классы: The type Colorable and Resizable cannot be a superinterface of Drawable; a superinterface must be an interface):

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

Читайте также:  Monster hunter world классы

Все методы интерфейса являются public abstract , и эти модификаторы также необязательны.

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

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

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

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

C# [ править | править код ]

В C# интерфейсы могут наследовать один или несколько других интерфейсов. Членами интерфейсов могут быть методы, свойства, события и индексаторы:

При реализации интерфейса класс должен реализовать как методы самого интерфейса, так и его базовых интерфейсов:

Интерфейсы в UML [ править | править код ]

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

В UML интерфейсы изображаются как классы со стереотипом «interface», либо в виде кружочков (в этом случае содержащиеся в интерфейсе UML-операции не отображаются).

Система, содержащая примеры этой лекции, находится в подкаталоге 04_interface каталога examples ПО Traffic .

4.1. Интерфейсы

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

Определения: Клиент, Поставщик, Интерфейс

Клиентом программных механизмов ( client ) является система любого вида: человек; система, не являющаяся программной; элемент программной системы. Используемое клиентами ПО является для них поставщиком ( supplier ).

Интерфейс ( interface ) программных механизмов – это описание, позволяющее клиентам использовать механизмы поставщика.

Говоря неформально, интерфейс части ПО – это описание того, как остальной мир может "общаться" с этой частью ПО .

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

  • интерфейс пользователя (user interface), когда предполагаемыми клиентами являются люди, использующие ПО;
  • программный интерфейс (program interface), предназначенный для клиентов, которые сами являются программными элементами.

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

  • спецификацию полей, в которых пользователи могут печатать собственные тексты, как, например, поле адреса в верхней части браузера;
  • свойства кнопок ("Back" и др.), которые пользователи могут нажимать для получения определенного эффекта;
  • соглашения для гиперссылок (щелчок левой кнопкой мыши переводит к новой странице, правой кнопкой – задает переход к меню и т.д.).

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

Такой пользовательский интерфейс называется графическим, поскольку он включает картинки и другие визуальные диалоговые элементы, такие как кнопки и меню . В программировании, склонном к акронимам, такой интерфейс называют GUI ( Graphical User Interface , произносится "Gooey" или просто UI – "You-I").

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

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

Но для нашего обсуждения более важен второй вид интерфейсов – программные интерфейсы. Здесь также используется трехбуквенный акроним API ( Abstract Program Interface , старая расшифровка для буквы A – "Application").

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

4.2. Классы

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

  • город, где операции доступа и модификации могли включать поиск характеристик транспортной системы, добавление новых транспортных средств. В качестве примера использовался город Париж, но можно получить объект, представляющий любой другой город, если обеспечить для него релевантные данные;
  • маршрут путешествия. И снова можно иметь различные маршруты, а не только объект Route1, используемый в примерах;
  • список автомобилей, остановившихся на красный свет светофора. Опять речь идет о множестве объектов;
  • элементы GUI , такие как кнопки и окна на экране компьютера. Их может быть довольно много.

Внутри каждой категории объектов, например, для всех объектов, представляющих города, существует сильная схожесть. Операции , применимые к объекту Paris, будут также применимы к любому другому объекту – городу, скажем New_York или Tokyo. Эти операции не применимы к объектам другой категории, например, к объекту Route1. Аналогично операция добавления новой ветки в маршрут , допустимая для Route1, допустима и для всех других объектов, задающих маршрут .

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

" Класс " на самом деле – это технический термин. Характеризует объекты данного класса общее множество применимых операций – методов ( features ). Отсюда следует определение :

В программах имена классов всегда будем писать заглавными буквами: CITY, ROUTE, CAR_LIST, WINDOW. Имена объектов всегда будут строиться из строчных букв, но если речь идет о предопределенных объектах, таких как Paris, то их имена будут начинаться с заглавной буквы.

Класс задает категорию предметов, объект представляет один из этих предметов. Следующие определения задают точное отношение между классами и объектами.

Определения: Экземпляр, Генерирующий класс

Класс CITY представляет все возможные города (поскольку мы решили моделировать их в нашей программе). Объект Paris обозначает экземпляр этого класса.

Отношение между классами и объектами является типичным отношением между категорией и ее членами. "Human" – это категория, описывающая людей, "Socrates" – человек, член этой категории. Моделируя эти понятия в программной системе, мы бы создали класс HUMAN, и один из объектов этого класса мог быть назван Socrates.

Существенное различие между объектами и классами проявляется в ПО . Классы являются статическими, объекты – динамическими.

  • Классы существуют только как тексты программ. Как следует из определения, класс – это описание; оно задано текстом класса, программным элементом, описывающим свойства ассоциированных объектов (экземпляров класса). Программа – это коллекция, состоящая из текстов классов.
  • Объекты – коллекция данных – существуют только в период выполнения программы, вы не можете видеть их в тексте программы, хотя вы, конечно же, видите их имена, такие как Paris и Route1,обозначающие объекты, которые появятся во время исполнения программы.

Как следствие, термин "объект времени выполнения", появляющийся в определении класса, является избыточным, поскольку объекты по определению могут существовать только в период выполнения ("run time"). С этого момента мы будем просто говорить "объект", не подчеркивая его динамическую сущность .

Поиск подходящих классов является центральной задачей проектирования ПО . Его цель: построить основную структуру программы – ее архитектуру. В противоположность этому, описание деталей относится к этапу, называемому реализацией (implementation).

Читайте также:  Как найти верхнее основание равнобедренной трапеции

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

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

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

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

"Придумать класс с правильным именем" – так вы не сможете заставить "наследников" реализовывать функционал.

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

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

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

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

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

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

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

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

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

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

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

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

P.S. Даже когда пишите что-то или задаете вопросы, можете разделять на блоки текста, как написал я, так воспринимается легче чем то, что написано в куче , не правда ли?

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

Например, интерфейс Iterable говорит, что классы реализующие данный интерфейс имеют элементы и их можно перебирать в цикле вызывая метод next(). А значит, если создаете какой-то контейнер или коллекцию, то можно реализовать Iterable. Не выдумывая свои методы для работы с контейнером. Тем самым появляется некий общий стандарт – интерфейс для работы с контейнерами.

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

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

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

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

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

OrcWarrior implements Unit, Iterable – так можно

OrcWarrior extends Unit, Iterable – так в Java нельзя, но можно в С++, а Unit и Iterable тогда всегда будут объявляться как class.

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

class OrcWarrior implements Unit, Iterable <
UnitAI ai;

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

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

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

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

Adblock detector