Зачем использовать интерфейсы, множественное наследование и интерфейсы, преимущества интерфейсов?
У меня все еще есть некоторая путаница по поводу этой вещи. То, что я нашел до сих пор,
(Подобные вопросы уже задавались здесь, но у меня были некоторые другие вопросы.)
Интерфейс - это коллекция ТОЛЬКО абстрактных методов и конечных полей.
В Java нет множественного наследования.
Интерфейсы могут использоваться для достижения множественного наследования в Java.
Сильной стороной наследования является то, что мы можем использовать код базового класса в производном классе, не записывая его снова. Может быть, это самое важное для наследства.
Сейчас..
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Тогда зачем делать интерфейсы?
ПРИМЕЧАНИЕ. Я обнаружил один случай, когда интерфейсы полезны. Одним из примеров этого является то, что в интерфейсе Runnable у нас есть публичный метод void run(), в котором мы определяем функциональность потока, и существует встроенное кодирование, согласно которому этот метод будет запускаться как отдельный поток. Так что нам просто нужно написать код, что делать в потоке, Rest предопределен. Но и этого можно добиться с помощью абстрактных классов и всего остального.
Тогда каковы точные преимущества использования интерфейсов? Действительно ли мы получаем множественное наследование с помощью интерфейсов?
11 ответов
Интерфейсы - это набор конечных статических полей и абстрактных методов (недавно в Java 8 добавлена поддержка наличия статических методов в интерфейсе).
Интерфейсы создаются в ситуациях, когда мы знаем, что какая-то задача должна быть выполнена, но способ ее выполнения может отличаться. Другими словами, мы можем сказать, что реализуем интерфейсы, чтобы наш класс начал вести себя определенным образом.
Позвольте мне объяснить на примере, мы все знаем, что такое животные. Как Лев - животное, обезьяна - животное, слон - животное, корова - животное и так далее. Теперь мы знаем, что все животные что-то едят и спят. Но способ, которым каждое животное может что-то есть или спать, может отличаться. Как Лев ест, охотясь на других животных, где корова ест траву. Но оба едят. Таким образом, мы можем иметь некоторый псевдокод, как это,
interface Animal {
public void eat();
public void sleep();
}
class Lion implements Animal {
public void eat() {
// Lion's way to eat
}
public void sleep(){
// Lion's way to sleep
}
}
class Monkey implements Animal {
public void eat() {
// Monkey's way to eat
}
public void sleep() {
// Monkey's way to sleep
}
}
Согласно псевдокоду, упомянутому выше, все, что способно есть или спать, будет называться животным, или мы можем сказать, что все животные должны есть и спать, но способ есть и спать зависит от животного.
В случае интерфейсов мы наследуем только поведение, а не реальный код, как в случае наследования классов.
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
Реализация интерфейсов - это другой вид наследования. Это не похоже на наследование классов, так как в этом наследовании дочерний класс получает реальный код для повторного использования из базового класса.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Это объясняется тем, что один класс может реализовывать более одного интерфейса. Но мы должны понимать, что это наследование отличается от наследования классов.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Реализация интерфейса навязывает классу обязательство переопределять все его абстрактные методы.
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
Мы не можем Интерфейсы не используются для множественного наследования. Они заменяют его более безопасной, хотя и несколько менее мощной конструкцией. Обратите внимание на ключевое слово implements
скорее, чем extends
,
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Они не. С интерфейсами один класс может иметь несколько "представлений", разных API или возможностей. Например, класс может быть Runnable
а также Callable
в то же время, в то время как оба метода эффективно делают одно и то же.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Интерфейсы являются своего рода множественным наследованием без каких-либо проблем, которые создает последняя (например, проблема Diamond).
Есть несколько вариантов использования для интерфейсов:
У объекта фактически есть две идентичности:
Tank
это одновременноVehicle
иWeapon
, Вы можете использовать экземплярTank
где ожидается либо первое, либо второе (полиморфизм). Это редко встречается в реальной жизни и на самом деле является верным примером, когда множественное наследование было бы лучше (или черты).Простые обязанности: пример
Tank
объект в игре такжеRunnable
чтобы позволить вам выполнить его в потоке иActionListener
реагировать на события мыши.Интерфейсы обратного вызова: если объект реализует данный интерфейс обратного вызова, он получает уведомление о своем жизненном цикле или других событиях.
Маркерные интерфейсы: не добавляются никакие методы, но легко доступны через
instanceof
раскрыть возможности объекта или пожелания.Serializable
а такжеCloneable
примеры этого.
То, что вы ищете, это черта (как в Scala), к сожалению, недоступная в Java.
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
К сожалению, в разговорной речи слово inheritance
все еще часто используется, когда класс реализует интерфейс, хотя interface implementation
будет предпочтительным термином - ИМО, термин inheritance
должен строго использоваться с наследованием конкретного или абстрактного класса. В таких языках, как C++ и C#, одинаковый синтаксис (т.е. Subclass : Superclass
а также Class : Interface
) используется как для наследования классов, так и для реализации интерфейса, что, возможно, способствовало распространению неправильного использования слова inheritance
с интерфейсами. Java имеет другой синтаксис для extend
класс в отличие от implement
интерфейс, что хорошо.
В2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для множественного наследования?
Вы можете добиться "эффекта" множественного наследования посредством композиции - реализовав несколько интерфейсов в классе, а затем предоставив реализации для всех методов, свойств и событий, требуемых для всех интерфейсов класса. Один из распространенных способов сделать это с конкретными классами - это создать отношения has-a (композиция) с классами, которые реализуют внешние интерфейсы, "связывая" реализацию с каждой из реализаций внутреннего класса. (Такие языки, как C++, напрямую поддерживают множественное конкретное наследование, но это создает другие потенциальные проблемы, такие как проблема алмазов).
Q3 В любом случае, каковы преимущества использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Интерфейсы позволяют существующим классам (например, фреймворкам) взаимодействовать с вашими новыми классами, даже не видя их раньше, благодаря способности общаться через известный интерфейс. Думайте об интерфейсе как о контракте. Внедрив этот интерфейс в классе, вы по контракту обязаны выполнять обязательные для него обязательства, и как только этот контракт будет реализован, тогда ваш класс сможет взаимозаменяемо использоваться с любым другим кодом, который использует интерфейс.
Пример из реального мира
Примером "реального мира" может быть законодательство и конвенция (интерфейс), касающиеся электрической розетки в конкретной стране. Каждое электрическое устройство, подключенное к розетке, должно соответствовать спецификациям (контракту), которые власти определили для розетки, например, расположение линии, нейтральных и заземляющих проводов, положение и окраска двухпозиционного переключателя, а также соответствие электрическое напряжение, частота и максимальный ток, который будет подаваться через interface
когда он включен.
Преимущество отсоединения интерфейса (то есть от стандартной настенной розетки) вместо простой пайки проводов заключается в том, что вы можете подключить (и отключить) вентилятор, чайник, двойной адаптер или какое-то новое устройство, которое будет изобретено в следующем году. хотя это устройство не существовало на момент разработки интерфейса. Зачем? Потому что это будет соответствовать требованиям интерфейса.
Зачем использовать интерфейсы?
Интерфейсы отлично подходят для слабой связи классов и являются одной из опор SOLID- парадигмы дяди Боба, особенно Dependency Inversion Principle
а также Interface Segregation Principles
,
Проще говоря, гарантируя, что зависимости между классами связаны только на интерфейсах (абстракциях), а не на других конкретных классах, это позволяет заменить зависимость любой другой реализацией класса, которая отвечает требованиям интерфейса.
В тестировании заглушки и макеты зависимостей можно использовать для модульного тестирования каждого класса, а за взаимодействием класса с зависимостью можно "шпионить".
ПОЦЕЛУЙ
Я искал дни, а то и недели, пытаясь понять интерфейсы и, похоже, читал ту же самую общую справку; Я не пытаюсь принизить вклад, но я думаю, что лампочка просто щелкнула, так что я потрясен:))
Я предпочитаю Keep It Simple Stupid, поэтому предложу мой новый вид интерфейсов.
Я случайный кодер, но я хочу опубликовать этот код, который я написал в VB.NET (принцип тот же для других языков), чтобы помочь другим понять интерфейсы.
Если я ошибаюсь, пожалуйста, сообщите другим в последующих комментариях.
объяснение
Три кнопки в форме, при нажатии на каждую из них сохраняется ссылка на другой класс для переменной интерфейса (_data). Весь смысл различных ссылок на классы в переменную интерфейса, это то, что я не понял, так как она казалась избыточной, тогда ее мощность становится очевидной с помощью msgbox, мне нужно только вызвать тот же метод, чтобы выполнить задачу, в которой я нуждаюсь, в этом case 'GetData()', который использует метод в классе, который в настоящее время содержится в переменной ссылки на интерфейс (_data).
Поэтому, как бы я ни хотел получить свои данные (из базы данных, Интернета или текстового файла), это всегда делается только с использованием одного и того же имени метода; код этой реализации... мне все равно.
Затем легко изменить код каждого класса, используя интерфейс без какой-либо зависимости... это ключевая цель в ОО и инкапсуляции.
Когда использовать
Классы кода, и если вы заметили тот же глагол, который используется для методов, например, "GetData ()", то это хороший кандидат для реализации интерфейса в этом классе и использования этого имени метода в качестве абстракции / интерфейса.
Я искренне надеюсь, что это помогает товарищу новичку в этом сложном принципе.
Public Class Form1
Private _data As IData = Nothing
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
_data = New DataText()
MsgBox(_data.GetData())
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
_data = New DataDB()
MsgBox(_data.GetData())
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
_data = New DataWeb()
MsgBox(_data.GetData())
End Sub
End Class
Public Interface IData
Function GetData() As String
End Interface
Friend Class DataText : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataText"
End Function
End Class
Friend Class DataDB : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataDB"
End Function
End Class
Friend Class DataWeb : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataWeb"
End Function
End Class
Старый вопрос Я удивлен, что никто не процитировал канонические источники: Java: обзор Джеймса Гослинга, Шаблоны проектирования: элементы многократно используемого объектно-ориентированного программного обеспечения "Банды четырех" или " Эффективная Java " Джошуа Блоха (среди других источников).
Я начну с цитаты:
Интерфейс - это просто спецификация набора методов, на которые реагирует объект. Он не содержит никаких переменных экземпляра или реализации. Интерфейсы могут быть множественным наследованием (в отличие от классов), и их можно использовать более гибким способом, чем обычная жесткая структура наследования классов. (Гослинг, стр.8)
Теперь давайте рассмотрим ваши предположения и вопросы один за другим (я добровольно проигнорирую функции Java 8).
Предположения
Интерфейс - это коллекция ТОЛЬКО абстрактных методов и конечных полей.
Вы видели ключевое слово abstract
в интерфейсах Java? Нет. Тогда вы не должны рассматривать интерфейс как набор абстрактных методов. Возможно, вас вводят в заблуждение так называемые интерфейсы C++, которые представляют собой классы только с чисто виртуальными методами. C++ по своему дизайну не имеет (и не должен иметь) интерфейсы, потому что имеет множественное наследование.
Как объяснил Гослинг, вы должны рассматривать интерфейс как "набор методов, на которые реагирует объект". Мне нравится видеть интерфейс и связанную документацию как сервисный контракт. Он описывает, что вы можете ожидать от объекта, который реализует этот интерфейс. В документации должны быть указаны предварительные и постусловия (например, параметры должны быть не нулевыми, выходные данные всегда положительными,...) и инварианты (метод, который не изменяет внутреннее состояние объекта). Этот контракт является сердцем, я думаю, ООП.
В Java нет множественного наследования.
В самом деле.
JAVA опускает многие редко используемые, плохо понимаемые, запутанные возможности C++, которые по нашему опыту приносят больше горя, чем пользы. Это в первую очередь состоит из перегрузки операторов (хотя она и имеет перегрузку методов), множественного наследования и обширных автоматических приведений. (Гослинг, с.2)
Нечего добавить.
Интерфейсы могут использоваться для достижения множественного наследования в Java.
Нет, simlpy, потому что в Java нет множественного наследования. Смотри выше.
Сильной стороной наследования является то, что мы можем использовать код базового класса в производном классе, не записывая его снова. Может быть, это самое важное для наследства.
Это называется "наследование реализации". Как вы написали, это удобный способ повторного использования кода.
Но у него есть важный аналог:
родительские классы часто определяют, по крайней мере, часть физического представления своих подклассов. Поскольку наследование предоставляет подклассу детали реализации его родителя, часто говорят, что "наследование нарушает инкапсуляцию" [Sny86]. Реализация подкласса становится настолько связанной с реализацией его родительского класса, что любое изменение в реализации родителя заставит подкласс измениться. (GOF, 1.6)
(Есть аналогичная цитата в Блохе, пункт 16.)
На самом деле наследование служит и другой цели:
Наследование классов объединяет наследование интерфейса и наследование реализации. Наследование интерфейса определяет новый интерфейс в терминах одного или нескольких существующих интерфейсов. Наследование реализации определяет новую реализацию в терминах одной или нескольких существующих реализаций. (GOF, Приложение A)
Оба используют ключевое слово extends
на Яве. У вас могут быть иерархии классов и иерархии интерфейсов. Первые разделяют реализацию, вторые разделяют обязательства.
Вопросы
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.**
Реализация интерфейса не наследование. Это реализация. Таким образом, ключевое слово implements
,
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?**
Нет множественного наследования в Java. Смотри выше.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем./ Тогда зачем создавать интерфейсы?/ Каковы точные преимущества использования интерфейсов? Действительно ли мы получаем множественное наследование с помощью интерфейсов?
Самый важный вопрос: почему вы хотели бы иметь множественное наследование? Я могу придумать два ответа: 1. дать объектам несколько типов; 2. повторно использовать код.
Присвойте объектам типы mutliple
В ООП один объект может иметь разные типы. Например, в Java ArrayList<E>
имеет следующие типы: Serializable
, Cloneable
, Iterable<E>
, Collection<E>
, List<E>
, RandomAccess
, AbstractList<E>
, AbstractCollection<E>
а также Object
(Надеюсь, я никого не забыл). Если объект имеет разные типы, различные потребители смогут использовать его, не зная о его особенностях. Мне нужен Iterable<E>
и ты дашь мне ArrayList<E>
? Все нормально. Но если мне нужно сейчас List<E>
и ты дашь мне ArrayList<E>
тоже нормально. И т.п.
Как вы вводите объект в ООП? Вы взяли Runnable
интерфейс в качестве примера, и этот пример идеально подходит для иллюстрации ответа на этот вопрос. Я цитирую официальный документ Java:
Кроме того, Runnable предоставляет средства для того, чтобы класс был активным, не наследуя Thread.
Вот в чем дело: Наследование - это удобный способ печатать объекты. Вы хотите создать тему? Давайте подкласс Thread
учебный класс. Вы хотите, чтобы объект имел разные типы, давайте используем mutliple-наследование. Argh. Это не существует в Java. (В C++, если вы хотите, чтобы объект имел разные типы, множественное наследование - это путь.)
Как дать типы Mutliple объекту тогда? В Java вы можете напечатать свой объект напрямую. Это то, что вы делаете, когда ваш класс implements
Runnable
интерфейс. Зачем использовать Runnable
если вы поклонник наследства? Может быть, потому что ваш класс уже является подклассом другого класса, скажем, A
, Теперь у вашего класса есть два типа: A
а также Runnable
,
С несколькими интерфейсами вы можете назначить объекту несколько типов. Вам просто нужно создать класс, который implements
несколько интерфейсов. Пока вы соблюдаете контракты, все в порядке.
Повторно использовать код
Это сложный предмет; Я уже процитировал GOF о нарушении инкапсуляции. Другой ответ затронул проблему алмазов. Вы также можете подумать о принципе единой ответственности:
У класса должна быть только одна причина для изменения. (Роберт К. Мартин, Agile Software Development, Принципы, Шаблоны и Практики)
Наличие родительского класса может дать классу причину для изменения, помимо его собственных обязанностей:
Реализация суперкласса может изменяться от выпуска к выпуску, и если это так, подкласс может сломаться, даже если его код не был затронут. Как следствие, подкласс должен развиваться в тандеме со своим суперклассом (Bloch, пункт 16).
Я бы добавил более прозаическую проблему: у меня всегда возникает странное чувство, когда я пытаюсь найти исходный код метода в классе и не могу его найти. Тогда я помню: это должно быть определено где-то в родительском классе. Или в классе бабушек и дедушек. Или, может быть, даже выше. Хорошая IDE в этом случае является ценным активом, но, на мой взгляд, остается чем-то волшебным. Ничего подобного с иерархиями интерфейсов, поскольку единственное, что мне нужно, - это javadoc: один ярлык клавиатуры в IDE, и я его получаю.
У наследства есть преимущества:
Безопасно использовать наследование в пакете, где реализации подкласса и суперкласса находятся под контролем одних и тех же программистов. Также безопасно использовать наследование при расширении классов, специально разработанных и задокументированных для расширения (пункт 17: Разработка и документирование для наследования или иное запрещение). (Блох, п.16)
Примером класса, "специально разработанного и задокументированного для расширения" в Java, является AbstractList
,
Но Блох и GOF настаивают на этом: "Пользуйся композицией, а не наследством":
Делегирование - это способ сделать композицию такой же мощной для повторного использования, как наследование [Lie86, JZ91]. При делегировании два объекта участвуют в обработке запроса: принимающий объект делегирует операции своему делегату. Это аналогично подклассам, откладывающим запросы к родительским классам. (GOF стр.32)
Если вы используете композицию, вам не придется писать один и тот же код снова и снова. Вы просто создаете класс, который обрабатывает дубликаты, и передаете экземпляр этого класса классам, которые реализуют интерфейс. Это очень простой способ повторного использования кода. И это поможет вам следовать принципу единой ответственности и сделать код более тестируемым. У Rust и Go нет наследования (у них тоже нет классов), но я не думаю, что код более избыточен, чем в других языках ООП.
Более того, если вы используете композицию, вы, естественно, будете использовать интерфейсы, чтобы придать вашему коду необходимую структуру и гибкость (см. Другие ответы о вариантах использования интерфейсов).
Примечание: вы можете поделиться кодом с интерфейсами Java 8
И, наконец, последняя цитата:
Во время запоминающейся сессии вопросов и ответов кто-то спросил его [Джеймса Гослинга]: "Если бы вы могли снова заняться Java, что бы вы изменили?" "Я бы пропустил занятия" (где-нибудь в сети, не знаю, правда ли это)
Это очень старый вопрос, и в выпуске Java-8 добавлены дополнительные функции и возможности интерфейса.
Объявление интерфейса может содержать
- подписи метода
- методы по умолчанию
- статические методы
- постоянные определения.
Единственными методами, которые имеют реализации в интерфейсе, являются стандартные и статические методы.
Использование интерфейса:
- Определить договор
- Связать несвязанные классы с возможностями (например, классы, реализующие интерфейс Serializable, могут иметь или не иметь никакого отношения между ними, кроме реализации этого интерфейса
- Чтобы обеспечить взаимозаменяемую реализацию, например,
Strategy_pattern
- Методы по умолчанию позволяют вам добавлять новые функциональные возможности к интерфейсам ваших библиотек и обеспечивать двоичную совместимость с кодом, написанным для более старых версий этих интерфейсов.
- Организуйте вспомогательные методы в своих библиотеках с помощью статических методов (вы можете хранить статические методы, специфичные для интерфейса, в одном интерфейсе, а не в отдельном классе)
Посмотрите на этот связанный вопрос SE для примера кода, чтобы лучше понять концепции:
Как мне объяснить разницу между интерфейсом и абстрактным классом?
Возвращаясь к вашим запросам:
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Интерфейс может содержать код для статических и стандартных методов. Эти методы по умолчанию обеспечивают обратную совместимость, а статические методы предоставляют вспомогательные / служебные функции.
Вы не можете иметь истинное множественное наследование в Java, и интерфейс не способ получить его. Интерфейс может содержать только константы. Таким образом, вы не можете наследовать состояние, но вы можете реализовать поведение.
Вы можете заменить наследование с возможностью. Интерфейс предоставляет множество возможностей для реализации классов.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Обратитесь к разделу " Использование интерфейса " в моем ответе.
Наследование - это когда один класс наследуется от другого класса (который может быть абстрактным) или интерфейса. Сильной стороной объектно-ориентированного (наследования) является не повторное использование кода (есть много способов сделать это), а полиморфизм.
Полиморфизм - это когда у вас есть код, который использует интерфейс, а его экземплярный объект может иметь любой класс, производный от этого интерфейса. Например, у меня может быть такой метод: public void Pet(IAnimal animal), и этот метод получит объект, который является экземпляром Dog или Cat, который наследуется от IAnimal. или у меня может быть такой код: IAnimal animal, а затем я могу вызвать метод этого интерфейса: animal.Eat(), который Dog или Cat может реализовать по-другому.
Основным преимуществом интерфейсов является то, что вы можете наследовать от некоторых из них, но если вам нужно наследовать только от одного, вы также можете использовать абстрактный класс. Вот статья, которая объясняет больше различий между абстрактным классом и интерфейсом: http://www.codeproject.com/KB/cs/abstractsvsinterfaces.aspx
Оба метода работают (интерфейсы и множественное наследование).
Быстрый Практический Короткий Ответ
Интерфейсы лучше, когда у вас есть многолетний опыт использования множественного наследования, в котором есть суперклассы с определением только метода и вообще без кода.
Дополнительный вопрос может быть следующим: "Как и зачем переносить код из абстрактных классов в интерфейсы".
Если у вас мало абстрактных классов, вы можете пропустить интерфейсы.
Не спешите использовать интерфейсы.
Длинный скучный ответ
Интерфейсы очень похожи или даже эквивалентны абстрактным классам.
Если в вашем коде много абстрактных классов, то самое время подумать об интерфейсах.
Следующий код с абстрактными классами:
MyStreamsClasses.java
/* File name : MyStreamsClasses.java */
import java.lang.*;
// Any number of import statements
public abstract class InputStream {
public void ReadObject(Object MyObject);
}
public abstract class OutputStream {
public void WriteObject(Object MyObject);
}
public abstract class InputOutputStream
imnplements InputStream, OutputStream {
public void DoSomethingElse();
}
Можно заменить на:
MyStreamsInterfaces.java
/* File name : MyStreamsInterfaces.java */
import java.lang.*;
// Any number of import statements
public interface InputStream {
public void ReadObject(Object MyObject);
}
public interface OutputStream {
public void WriteObject(Object MyObject);
}
public interface InputOutputStream
extends InputStream, OutputStream {
public void DoSomethingElse();
}
Приветствия.
Так. Здесь есть много отличных ответов, подробно объясняющих, что такое интерфейс. Тем не менее, это пример его использования, как один из моих лучших коллег когда-либо объяснил мне это много лет назад, с тем, что я усвоил в университете за последние пару лет.
Интерфейс - это своего рода "контракт". Он предоставляет некоторые доступные методы, поля и так далее. Он не раскрывает никаких деталей реализации, только то, что он возвращает, и какие параметры он принимает. И здесь лежит ответ на третий вопрос, и то, что я чувствую, является одной из самых сильных сторон современного ООП:
"Код дополнением, а не модификацией" - Магнус Мадсен, AAU
Это то, что он назвал по крайней мере, и он может получить его из другого места. Пример кода ниже написан на C#, но все показанное можно сделать примерно так же, как в Java.
То, что мы видим, это класс с именем SampleApp, который имеет одно поле, IOContext. IOContext - это интерфейс. SampleApp не заботится о том, как он сохраняет свои данные, он просто нуждается в этом в своем методе doSomething().
Мы можем представить, что в начале процесса разработки сохранение данных, возможно, было более важным, чем то, КАК они были сохранены, и поэтому разработчик решил просто написать класс FileContext. Позже, однако, ему нужно было поддерживать JSON по любой причине. Поэтому он написал класс JSONFileContext, который наследует FileContext. Это означает, что это фактически IOContext, который имеет функциональность FileContext, сохраняет замену FileContexts SaveData и LoadData, он все еще использует свои методы "запись / чтение".
Реализация JSON-класса была небольшой работой, по сравнению с написанием этого класса и просто наследованием IOContext.
Поле SampleApp могло бы быть просто типа FileContext, но в этом случае оно было бы ограничено использованием только потомков этого класса. Создавая интерфейс, мы можем даже реализовать реализацию SQLiteContext и записать в базу данных, SampleApp никогда не узнает и не позаботится, а когда мы напишем класс sql lite, нам потребуется внести только одно изменение в наш код: new JSONFileContext();
вместо становится new SQLiteContext();
У нас все еще есть наши старые реализации, и мы можем вернуться к ним, если возникнет такая необходимость. Мы ничего не сломали, и все изменения в нашем коде - это пол строки, которые можно изменить в мгновение ока.
Итак: код дополнением, а НЕ модификацией.
namespace Sample
{
class SampleApp
{
private IOContext context;
public SampleApp()
{
this.context = new JSONFileContext(); //or any of the other implementations
}
public void doSomething()
{
//This app can now use the context, completely agnostic of the actual implementation details.
object data = context.LoadData();
//manipulate data
context.SaveData(data);
}
}
interface IOContext
{
void SaveData(object data);
object LoadData();
}
class FileContext : IOContext
{
public object LoadData()
{
object data = null;
var fileContents = loadFileContents();
//Logic to turn fileContents into a data object
return data;
}
public void SaveData(object data)
{
//logic to create filecontents from 'data'
writeFileContents(string.Empty);
}
protected void writeFileContents(string fileContents)
{
//writes the fileContents to disk
}
protected string loadFileContents()
{
string fileContents = string.Empty;
//loads the fileContents and returns it as a string
return fileContents;
}
}
class JSONFileContext : FileContext
{
public new void SaveData(object data)
{
//logic to create filecontents from 'data'
base.writeFileContents(string.Empty);
}
public new object LoadData()
{
object data = null;
var fileContents = loadFileContents();
//Logic to turn fileContents into a data object
return data;
}
}
class SQLiteContext : IOContext
{
public object LoadData()
{
object data = null;
//logic to read data into the data object
return data;
}
public void SaveData(object data)
{
//logic to save the data object in the database
}
}
}
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем интерфейс, то это наследование? Мы не используем его код.
Это не равное наследство. Это просто похоже. Позволь мне объяснить:
VolvoV3 extends VolvoV2, and VolvoV2 extends Volvo (Class)
VolvoV3 extends VolvoV2, and VolvoV2 implements Volvo (Interface)
line1: Volvo v = new VolvoV2();
line2: Volvo v = new VolvoV3();
Если вы видите только line1 и line2, вы можете сделать вывод, что VolvoV2 и VolvoV3 имеют одинаковый тип. Вы не можете определить, является ли Volvo суперклассом или Volvo интерфейсом.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Теперь с помощью интерфейсов:
VolvoXC90 implements XCModel and Volvo (Interface)
VolvoXC95 implements XCModel and Volvo (Interface)
line1: Volvo a = new VolvoXC90();
line2: Volvo a = new VolvoXC95();
line3: XCModel a = new VolvoXC95();
Если вы видите только line1 и line2, вы можете сделать вывод, что VolvoXC90 и VolvoXC95 имеют одинаковый тип (Volvo). Вы не можете сделать вывод, что Volvo является суперклассом или Volvo является интерфейсом.
Если вы видите только line2 и line3, вы можете сделать вывод, что Volvo95 реализует два типа, XCModel и Volvo, в Java вы знаете, что по крайней мере один должен быть интерфейсом. Если бы этот код был написан, например, на C++, это могли быть оба класса. Поэтому множественное наследование.
Q3. В любом случае, в чем преимущество использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Представьте себе систему, в которой вы используете класс VolvoXC90 в 200 других классах.
VolvoXC90 v = new VolvoXC90();
Если вам нужно усовершенствовать свою систему для запуска VolvoXC95, вам придется изменить 200 других классов.
Теперь представьте систему, в которой вы используете интерфейс Volvo в 10000000 классов.
// Create VolvoXC90 but now we need to create VolvoXC95
Volvo v = new VolvoFactory().newCurrentVolvoModel();
Теперь, если вам нужно развивать свою систему для создания моделей VolvoXC95, вам придется изменить только один класс - Factory.
Это вопрос здравого смысла. Если ваша система состоит только из нескольких классов и имеет мало обновлений, использование интерфейсов повсеместно контрпродуктивно. Для больших систем это может избавить вас от многих трудностей и избежать риска принятия интерфейсов.
Я рекомендую вам прочитать больше о принципах SOLID и прочитать книгу Эффективная Java. У него есть хорошие уроки от опытных разработчиков программного обеспечения.
Интерфейсы сделаны так, что класс будет реализовывать функциональность в интерфейсе и вести себя в соответствии с этим интерфейсом.
Интерфейсы
Интерфейс - это контракт, определяющий, как взаимодействовать с объектом. Они полезны для выражения того, как ваши внутренние объекты намерены взаимодействовать с объектом. После инверсии зависимости ваш публичный API будет иметь все параметры, выраженные через интерфейсы. Вам все равно, как он делает то, что вам нужно, просто он делает именно то, что вам нужно.
Пример: вам может просто понадобиться Vehicle
для перевозки товаров вам не безразличен конкретный вид транспорта.
наследование
Наследование является расширением конкретной реализации. Эта реализация может удовлетворять или не удовлетворять конкретному интерфейсу. Вы должны ожидать, что предок конкретной реализации, только когда вы заботитесь о том, как.
Пример: вам может понадобиться Plane
реализация автомобиля для быстрой перевозки.
Состав
Композиция может использоваться как альтернатива наследования. Вместо вашего класса, расширяющего базовый класс, он создается с объектами, которые реализуют меньшие части ответственности основного класса. Композиция используется в facade pattern
а также decorator pattern
,
Пример: вы можете создать DuckBoat
( DUKW) класс, который реализует LandVehicle
а также WaterVehicle
которые оба реализуют Vehicle
состоит из Truck
а также Boat
Реализации.
ответы
Q1. Поскольку интерфейсы имеют только абстрактные методы (без кода), как мы можем сказать, что если мы реализуем какой-либо интерфейс, то это наследование? Мы не используем его код.
Интерфейсы не наследование. Реализация интерфейса выражает намерение вашего класса работать так, как это определено интерфейсом. Наследование - это когда у вас общий предок, и вы получаете то же самое поведение (inherit
) как предок, так что вам не нужно его определять.
Q2. Если реализация интерфейса не является наследованием, то как интерфейсы используются для достижения множественного наследования?
Интерфейсы не достигают множественного наследования. Они выражают, что класс может подходить для нескольких ролей.
Q3. В любом случае, какова польза от использования интерфейсов? У них нет никакого кода. Нам нужно снова и снова писать код во всех классах, которые мы его реализуем.
Одним из основных преимуществ интерфейсов является разделение задач:
- Вы можете написать класс, который делает что-то с другим классом, не заботясь о том, как этот класс реализован.
- Любая будущая разработка может быть совместима с вашей реализацией без необходимости расширения определенного базового класса.
В духе DRY
Вы можете написать реализацию, которая удовлетворяет интерфейсу, и изменить его, сохраняя при этом open/closed principal
если вы используете композицию.