В чем смысл использования интерфейсов в ООП?
Я понимаю что с помощью интерфейса, можно создать отдельный метод к примеру IPrinter в котором есть метод Print() и не важно, как именно печатает принтер (лазерный или струйный).
Знаю что класс может реализовывать один и тот же интерфейс по-своему, это позволяет работать с объектами через общий тип.
Это все что я понимаю,но разве в этом толк интерфейса ? Может с помощью интерфейса можно что-то ещё крутое сделать? Можете пожалуйста объяснить и если можно на вот таком примере:
Есть игра в которой есть юниты (танки, машины, фуры, корабли и т.д) так же разные местности (горы, океаны, зимные биомы) и вот как тут можно применить интерфейсы ? Типа создать к примеру интерфейс движения и применить его к разным юнитам с разными применениями движения?
Ответы (4 шт):
Есть родитель, есть потомок - это логическое отношение. Здесь нужно рассматривать строго типы. Не важно какие у них методы, дело не в этом.
Суть всегда в информации, которая всегда суть - абстракция.
Есть Информация А, есть информация Б.
Пусть Информация А чуть больше чем информация Б, и вся информация Б включается в Информацию А. В таком случае сразу возможно отношение Родитель/Потомок.
С интерфейсами ровно такая же история. Интерфейс - суть родитель чего то, какой то абстракции.
Но тогда встаёт закономерный вопрос. Существуют в языке родители-типы - как классы, и родители-типы - как интерфейсы. Куда где чего применять? На чём строить ООП, как набор типизации выстраиваемой информации в иерархию типов?
Любые типы должны строиться на реальности, во всяком случае к этому стремятся. Тут помогают примеры, которые вы просите, которые вы привели. На самом деле всё это применимо и к базам данных, потому что БД тоже иерархичны, и данные ровно так же имеют иерархии, т.е. типизацию.
На чём всё строится? На чём строится логика типизации? Это логическая система, называется формальной системой, или формализмом. Т.е. выстраиваемая логика типов - это формализм.
Что преследует любой формализм? Это такая логическая система, которая не противоречит в рамках самоё себя. Иначе - формализм/логика плохие, противоречивая логика получается. Это требует прямой проверки. Это всё [выстраиваемая логика, т.е. формализм] проверяется на утверждения сразу на момент построения этой логики.
Простой пример, классификация живности Карла Линнея. Классификация как раз и есть типичный пример вменяемой типизации, на которой можно построить типизацию ООП.
Есть Котик как абстрактный тип, он же - информация. Есть Пёсик как абстрактный тип, он же - информация.
Но есть общая информация от Котика и Пёсика. Это ближайшее общее - суть ближайший родительский тип. Назовём этот тип - Животное.
В таком случае граф абстракций тривиален, и проверяем.
Животное
/ \
Котик Пёсик
Проверяем граф. Строим все утверждения, они должны быть истинными, иначе граф абстракций - не верен, противоречив в пределах себя.
Котик - это Котик
Пёсик - это Пёсик
Котик - это Животное
Пёсик - это Животное
Котик - (Животное)Котик
Пёсик - (Животное)Пёсик
Животное - это Животное
Граф полностью верен, производные утверждения графа абстракций - верные, значит граф верен в пределах самоё себя. Это и есть формализм, формальная система, логическая система, логика (это синонимы).
Вкрутим хвост. Хвост - это информация. Котик, Пёсик, Животное - не могут быть приводимы к Хвосту как к типу, Хвост не может быть приводим к Котику, Пёсику, Животному как к типам. Никак.
С точки зрения биологии Хвост существует как часть центрального скелетного аппарата. Он же производное хорды. Т.е. Хвост как таковой - применим к Хордовым. Строим граф, проверяем, нужно построить все утверждения, они все должны быть верны.
Животное
|
Хордовое - IХвостатое
/ \
Котик Пёсик
Хвост появляется у Хордовых. Все Хордовые имеют хвосты и/или рудименты Хвоста. Здесь нужно прилепить интерфейс Хвоста, как это называется биологически - не знаю, назовём Хвостатостью, но на самом деле нужно спрашивать биологов, потому что только биологи могут построить настоящую типизацию абстракций. Проверяем логику/граф абстракций.
Котик - это Котик
Пёсик - это Пёсик
Котик - это Хордовое
Пёсик - это Хордовое
Котик - (Хордовое)Котик
Пёсик - (Хордовое)Пёсик
Котик - (IХвостатое)Котик
Пёсик - (IХвостатое)Пёсик
Хордовое - это IХвостатое
Хордовое - это Хордовое
IХвостатое - это (IХвостатое)Хордовое
IХвостатое - это IХвостатое
Хордовое - это Животное
Животное - это Животное
Заметьте следующее, здесь есть суть.
Хордовое, Котик, Пёсик - приводимы к IХвостатое. Это то что нужно. Это вопрос об интерфейсе, зачем он нужен. Иначе говоря это родительский тип для Хордового, Котика, Пёсика. Именно так, интерфейс - это родительский тип.
Но это частичный родительский тип для Хордового. Это называют "половиной" настоящего родительского типа Хордового. Или это "треть-родительский" тип. Разумеется, это условность.
Интерфейс - это тип ограниченного функционала. Навешивается на "настоящий" тип, и ограничивает функционал "настоящего" типа. С точки зрения типизации.
Теперь совсем конкретно.
Котик - это Хордовое, и оно же IХвостатое
Пёсик - это Хордовое, и оно же IХвостатое
Хордовое - это Хордовое, и оно же IХвостатое
В этом суть, другой нет, родительский тип может быть полным, а может быть ограничен интерфейсом.
Это существует строго в рамках теории информации, там рассматривается, как теория и практика. На этом существует типизация как таковая, потому что типизация с пустого места не берётся, с неба не падает. Основа типизации - есть теория абстракций как таковая, как формальная система, как логика.
Программистский интерфейс строится строго на этом же. Изначально строится на типизации. Выделяется некоторая нужная часть типа. К этому частичному типу (типу ограниченного функционала) - может быть приводим сам тип и его потомки.
Итого:
С точки зрения программирования, интерфейс - строго утилитарная нужная вещь. Как ограничитель некоторого функционала типа, но к которому сводимы все производные типы, и сам тип.
ПС: Весьма важно здесь то, что верный граф абстракций всегда единственный. Если вы возьмёте сущности Котика, Пёсика, Хордового, IХвостатого, Животного, и перестроите граф - вы всегда получите противоречивый граф.
Если в двух словах, интерфейс - это контракт на реализацию определенного поведения. Объект, реализуя интерфейс, гарантирует наличие этого поведения. Как следствие, объекты не связанные друг с другом никакими отношениями включая отношения наследования могут предоставлять собственную реализацию требуемого поведения, а объект потребитель сможет их единообразно и непротиворечиво использовать.
Например если интерфейс Iterable определяет возможность последовательного перебора элементов коллекции, то потребитель через этот интерфейс может получить требуемый функционал не заботясь о деталях реализации коллекции и даже не зная какой именно класс предоставляет этот функционал.
Сокрытие деталей реализации объекта предоставляющего функционал довольно важная функция интерфейса. Поскольку нет привязки к поведению конкретного класса, его можно легко заменить на любой другой объект, реализующий тот же интерфейс.
Интерфейс, как это ни странно, нужен для создания определенного интерфейса (в общем случае наличия определенных методов и/или полей) в классах.
Если рассматривать это на ваших примерах:
Есть игра в которой есть юниты (танки, машины, фуры, корабли и т.д) так же разные местности (горы, океаны, зимные биомы) и вот как тут можно применить интерфейсы ?
Допустим у нас будет интерфейс ICanFire с методом Fire(Target target), очевидно AK-74, какой-нибудь линкор и танк Т-90 стреляют сильно по-разному. Например, у линкора может быть несколько орудий и следовательно он может за одно выполнение метода Fire выстрелить несколько раз (из разных орудий), а тот же AK-74 очевидно не может выпустить несколько патронов сразу (допустим, что функция Fire будет обрабатывать выпуск именно одного снаряда/патрона из всех доступных орудий).
И следовательно для каждого из этих типов орудий вы будете реализовать собственную логику с помощью интерфейса.
Или например, рассмотрим пример с местностями:
Интерфейс ITerrain с методом Effect(Unit unit) допустим снега будут давать -70% скорости передвижения танкам и/или +20% скрытности пехотинцам и т.д.
Основное отличие наследования Абстрактного класса от реализации Интерфейса состоит в том, что Абстрактный класс делегирует реализацию своих методов ТОЛЬКО СВОИМ потомкам, а Интерфейс делегирует реализацию своих методов ЛЮБЫМ классам.
Поэтому Интерфейс может быть использован и чаще всего используется для декларации возможности использования какой-либо ВНЕШНЕЙ функциональности по отношению к реализуемым конкретным возможностям объектов. Если необходимо использовать РАЗНЫЕ структуры классов одинаково - появляются интерфейсы.
Простейший пример:
Нам нужно отображать на экране все видимые объекты.
При этом в системе есть несколько базовых абстрактных классов, которые представляют разные объекты Оружие, Юниты, Предметы интерфейса итд.
Наследовать все эти классы от одного базового класса по каким-либо причинам не целесообразно.
Поэтому, мы декларируем, что все эти классы РЕАЛИЗУЮТ нужный нам интерфейс отображения на экране. Каждый по своему. Независимо друг от друга.
И при необходимости отобразить на экране нам не надо знать какой конкретно класс был использован для реализации объекта (оружие или юнит...) мы просто вызываем нужные методы отображения, которые реализованы в соответствующих классах, и получаем единообразную возможность получить нужную картинку на экране.
Вторая возможность, которую предоставляет Интерфейс - это ОГРАНИЧЕНИЕ доступной функциональности, предоставляемой каким-либо Абстрактным классом.
Чаще всего эта возможность используется при необходимости скрыть конкретную реализацию классов и декларировать внешние возможности какой-то библиотеки.