Интерфейсы — Kotlin

Интерфейсы в Kotlin могут содержать объявления абстрактных методов, а также методы с реализацией. Главное отличие интерфейсов от абстрактных классов заключается в невозможности хранения переменных экземпляров. Они могут иметь свойства, но те должны быть либо абстрактными, либо предоставлять реализацию методов доступа.

Интерфейс определяется ключевым словом interface.

interface MyInterface {
    fun bar()
    fun foo() {
      // необязательное тело
    }
}

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

Класс или объект могут реализовать любое количество интерфейсов.

class Child : MyInterface {
    override fun bar() {
        // тело
    }
}

Свойства в интерфейсах

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

interface MyInterface {
    val prop: Int // абстрактное свойство
    val propertyWithImplementation: String
        get() = "foo"
    fun foo() {
        print(prop)
    }
}
class Child : MyInterface {
    override val prop: Int = 29
}

Наследование интерфейсов

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

interface Named {
    val name: String
}
interface Person : Named {
    val firstName: String
    val lastName: String
    
    override val name: String get() = "$firstName $lastName"
}
data class Employee(
    // реализация 'name' не требуется
    override val firstName: String,
    override val lastName: String,
    val position: Position
) : Person

Устранение противоречий при переопределении

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

interface A {
    fun foo() { print("A") }
    fun bar()
}
interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}
class C : A {
    override fun bar() { print("bar") }
}
class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
    
    override fun bar() {
        super<B>.bar()
    }
}

Оба интерфейса A и B объявляют функции foo() и bar(). Оба реализуют foo(), но только B содержит реализацию bar() (bar() не отмечен как абстрактный метод в интерфейсе A, потому что в интерфейсах это подразумевается по умолчанию, если у функции нет тела). Теперь, если вы унаследуете какой-нибудь класс C от интерфейса

A, вам, очевидно, придётся переопределять метод bar(), обеспечивая его реализацию.

Однако если вы унаследуете класс D от интерфейсов A и B, вам надо будет переопределять все методы, унаследованные от этих интерфейсов, и вам нужно указать, как именно D должен их реализовать. Это правило касается как тех методов, у которых имеется только одна реализация (bar()), так и тех, у которых есть несколько реализаций (foo()).

Kotlin. Абстрактные классы и интерфейсы.

07 July 2020 2020-07-07T20:40:00+08:00

Время чтения 8 мин.

Абстрактные классы и интерфейсы объединены мной в одну тему, так как по своей сути они очень похожи. И те и другие имеют отношение к “моделированию” классов. С их помощью мы можем показать, что у определённой группы классов есть что-то общее: то, что их отличает от всех остальных. Ключевая разница между ними лишь в том, как их применять.

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

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

Другими словами абстрактный класс — это средство, для повторного использования кода.

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

Объявляется абстрактный класс при помощи ключевого слова abstract.

1
2
3
abstract class Tree {
  ...
}

Наследование от такого класса осуществляется с помощью оператора :. При этом абстрактному классу не нужен модификатор open, потому что он “открыт” для наследования по умолчанию.

1
2
3
class Pine : Tree() {
  ...
}

В теле класса можно объявлять абстрактные свойства и функции.

Это полезно, когда часть поведения класса не имеет смысла без реализации в более конкретном подклассе.

1
2
3
4
5
abstract class Tree {
  abstract val name: String
  abstract val description: String
  abstract fun info()
}

Каждый наследник обязан переопределять их все.

1
2
3
4
5
class Pine : Tree() {
  override val name = "Сосна"
  override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
  override fun info() = "$name - ${description.toLowerCase()}."  
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Tree {
  abstract val name: String
  abstract val description: String
  fun info(): String = "$name - ${description.toLowerCase()}."
}
...
class Pine : Tree() {
  override val name = "Сосна"
  override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
}
...
val pine = Pine()
println(pine.info())

Так как этот компонент класса уже не будет абстрактным, наследники не смогут его переопределить.

1
2
3
4
5
6
7
class Pine : Tree() {
  override val name = "Сосна"
  override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
  // ошибка: функция "info" является "final" и не может быть переопределена
  override fun info() = description  
}

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

1
2
3
4
5
6
abstract class Tree {
  abstract val name: String
  abstract val description: String
  open fun info(): String = "$name - ${description.toLowerCase()}."
}

У абстрактного класса может быть конструктор.

1
2
3
abstract class Tree(val name: String, val description: String) {
  open fun info(): String = "$name - ${description.toLowerCase()}."
}

Тогда каждый наследник должен предоставить для него значения.

1
2
3
4
5
6
class Pine(name: String, description: String) : Tree(name, description)
. ..
val pine = Pine("Сосна", "Хвойное дерево с длинными иглами и округлыми шишками")
println(pine.info())


Интерфейсы

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

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

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

Объявляется интерфейс ключевым словом interface.

1
2
3
interface Cultivable {
  ...
}

Реализация интерфейса осуществляется аналогично наследованию: после имени класса ставится оператор : и название интерфейса.

1
2
3
4
5
6
7
abstract class Tree : Cultivable {
  ...
}
class Seaweed : Cultivable {
  ...
}

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

1
2
3
4
interface Cultivable {
  val bloom: Boolean
  fun startPhotosynthesis()
}

Класс должен реализовывать все абстрактные свойства и функции, определённые в интерфейсе.

1
2
3
4
5
6
7
8
9
10
abstract class Tree : Cultivable {
  abstract val name: String
  abstract val description: String
  open fun info(): String = "$name - ${description.toLowerCase()}."
  override val bloom = false
  override fun startPhotosynthesis() {
    ...
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Tree : Cultivable {
  abstract val name: String
  abstract val description: String
  open fun info(): String = "$name - ${description.toLowerCase()}."
  override fun startPhotosynthesis() {
    ...
  }
}
class Pine : Tree() {
  override val name = "Сосна"
  override val description = "Хвойное дерево с длинными иглами и округлыми шишками"
  override val bloom = false
}

В интерфейсе можно определять свойства и функции с конкретной реализацией (по умолчанию). Классы, реализующие этот интерфейс, могут использовать реализацию по умолчанию или определить свою. При этом реализация свойств осуществляется с помощью метода доступа get().

1
2
3
4
5
6
7
8
interface Cultivable {
  val bloom: Boolean
    get() = false
  fun startPhotosynthesis() {
    . ..
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Fruitable {
  val fruit: String
    get() = "неплодоносный"
}
interface Cultivable : Fruitable {
  ...
  fun isFruitable() : Boolean {
    if(fruit == "неплодоносный") return false
    return true
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AppleTree() : Tree() {
  override val name = "Яблоня"
  override val description = "Фруктовое дерево"
  override val fruit = "яблоко"
}
...
val appleTree = AppleTree()
if(appleTree. isFruitable()) {
  println("Плод - ${appleTree.fruit}.")
} else {
  println("${appleTree.name} не плодоносит.")
}


Шпаргалка: абстрактный класс или интерфейс?

  • У вас есть семейство классов, из которых можно выделить общую сущность? Определите эту сущность в качестве абстрактного класса и она будет “заготовкой” для всего семейства.
  • Вам нужно создать более конкретную версию класса? Создайте подкласс этого класса и добавьте недостающее поведение.
  • Требует определить общее поведение для группы независимых друг от друга классов? Создайте интерфейс и реализуйте его теми классами, которым необходимо это поведение.

Ключевые моменты

  • Абстрактный класс — это “заготовка” для целого семейства классов.
  • Нельзя создать экземпляр абстрактного класса.
  • Абстрактный класс может содержать как абстрактные, так и конкретные реализации свойств и функций.
  • Класс, который содержит абстрактное свойство или функцию, должен быть объявлен абстрактным.
  • Абстрактный класс может быть без единого абстрактного свойства или функции.
  • У класса может быть только один суперкласс.
  • Наследники абстрактного класса должны переопределять все его абстрактные свойства и функции.
  • Чтобы наследники могли переопределять конкретные реализации свойств и функций, для них в абстрактном классе должен быть явно указан модификатор open.
  • У абстрактного класса может быть конструктор.
  • Интерфейс определяет поведение класса или общее поведение для группы независимых друг от друга классов.
  • Нельзя создать экземпляр интерфейса.
  • Интерфейс может содержать как абстрактные, так и конкретные реализации функций.
  • Свойства интерфейсов могут быть абстрактными, а могут иметь get() методы.
  • Класс может реализовывать несколько интерфейсов.
  • Класс должен реализовывать все абстрактные свойства и функции, определённые в интерфейсе.
  • Если интерфейс реализовывается абстрактным классом, то переопределение его абстрактных свойств и функций может быть передана наследникам абстрактного класса.
  • Интерфейс может реализовывать другой интерфейс.

Полезные ссылки

Abstract classes — официальная документация.
Абстрактные классы — перевод на русский (об абстрактных классах в самом низу статьи).
Interfaces — официальная документация.
Интерфейсы — перевод статьи про интерфейсы на русский.

наследование — разница между интерфейсом и абстрактным классом только с абстрактными методами

спросил

Изменено 8 лет, 4 месяца назад

Просмотрено 4к раз

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

  • наследование
  • интерфейс
  • абстрактный класс
  • абстрактные методы

3

Насколько я знаю, в C# нет большой разницы между абстрактным классом со всеми абстрактными методами и интерфейсом. Любой класс, наследуемый от них (абстрактный класс или интерфейс), должен реализовывать объявленные методы.

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

Итак, вместо того, чтобы иметь абстрактный класс только с абстрактными методами, объявите его как интерфейс.

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

Я думаю, что этот вопрос связан с JAVA или C#. В C++ абстрактный класс также называется интерфейсом. Но давайте придерживаться различий между интерфейсными и абстрактными классами (скажем, в JAVA):

  1. как интерфейсы, так и абстрактные классы предоставляют ряд методов и свойств, которые должны быть расширены, прежде чем их можно будет использовать.
  2. множественное наследование поддерживается только через интерфейсы: вы можете расширять несколько интерфейсов, но можете наследовать только от одного класса (также относится к абстрактным классам)
  3. абстрактный класс может обеспечивать реализацию одних методов и абстракции других, в то время как интерфейс предоставляет только абстракции, которые должны быть реализованы в классах, которые их расширяют.

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

Различия в разных языках.

В Java вы можете получить класс из ОДНОГО абстрактного класса, но/и из МНОГИХ интерфейсов. В Java 8 интерфейсы тоже могут иметь методы по умолчанию, и эта разница между ними и абстрактными классами остается почти единственной.

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

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

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

Существует два основных отличия абстрактного класса, имеющего все абстрактные методы и интерфейс.

 1. Множественное наследование. - Ваш класс может наследовать более одного интерфейса.
 Но это невозможно наследовать несколько классов в С# из-за известной проблемы с бриллиантами.
 2. Интерфейс поможет вам решить проблему внедрения зависимостей. 
 Вы можете проверить проблему DI здесь -
 

Что такое внедрение зависимостей?

Зарегистрируйтесь или войдите в систему

Зарегистрируйтесь с помощью Google

Зарегистрироваться через Facebook

Зарегистрируйтесь, используя электронную почту и пароль

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Опубликовать как гость

Электронная почта

Требуется, но не отображается

Jeremy Bytes: абстрактные классы и интерфейсы в C#

Чуть больше года назад C# 8 многое изменил в интерфейсах. Одним из последствий является то, что техническая грань между абстрактными классами и интерфейсами стирается. Прошел год, и я не видел много информации об этом в Интернете, поэтому давайте поближе посмотрим, как все изменилось.

Чтобы показать изменения, вот слайд, который я использовал, чтобы показать различия. 4 из 5 отличий больше не применяются:

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

Примечание. Для получения дополнительной информации об изменениях интерфейсов в C# 8 вы можете ознакомиться с кодом и статьями в этом репозитории: https://github.com/jeremybytes/csharp-8-interfaces. Также есть ссылки на видеоролики, демонстрирующие новые функции (и некоторые подводные камни).

Отличие #1

Абстрактный класс может содержать код реализации. Интерфейс может не иметь кода реализации, только объявления.

Статус: Больше не соответствует действительности

Интерфейсы в C# 8 могут иметь код реализации, называемый «реализацией по умолчанию». Например, интерфейсный метод может иметь тело, обеспечивающее функциональность. Эта функциональность используется по умолчанию, если реализующий класс не предоставляет собственную реализацию.

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

Дополнительная информация: Интерфейсы C# 8: свойства и реализация по умолчанию.

Отличие #2

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

Статус: Все еще верно

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

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

Отличие #3

Члены абстрактного класса могут иметь модификаторы доступа (public, private, protected и т. д.). Члены интерфейса автоматически становятся общедоступными и не могут быть изменены.

Статус: Больше не соответствует действительности

В C# 8 члены интерфейса могут иметь модификаторы доступа. По умолчанию общедоступный (поэтому существующие интерфейсы будут работать должным образом). Но интерфейсы теперь могут иметь частные члены, доступные только из самого интерфейса. Это может быть полезно для разбиения более крупных методов с реализацией по умолчанию.

Примечание: Защищенные члены тоже возможны, но из-за странности реализации я не нашел им практического применения.

Дополнительные сведения: Интерфейсы C# 8: общедоступные, частные и защищенные члены.

Отличие #4

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

Статус: В основном верно

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

Однако интерфейсы могут содержать статические элементы (см. «Различие № 5» ниже). Это означает, что интерфейсы могут иметь статических полей, статических конструкторов и статических деструкторов.

Отличие №5

Абстрактные классы могут содержать статические члены. Интерфейсы не могут иметь статических элементов.

Статус: Больше не соответствует действительности

Как отмечалось выше, интерфейсы в C# 8 могут иметь статические члены. Статический метод в интерфейсе работает точно так же, как статический метод в классе.

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

Дополнительная информация: интерфейсы C# 8: статические члены.

Кроме того, в интерфейсе теперь разрешено использование метода «static Main()». Если вы хотите свести с ума своих коллег, вот один из способов злоупотребить этим: неправильное использование C#: несколько методов Main().

Технические различия и различия в использовании

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

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

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

Если *есть* общий код между реализациями…
Я использую абстрактный класс . Абстрактный класс — это хорошее место для размещения общего кода реализации, чтобы его не нужно было повторять в каждом из нисходящих классов.

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

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

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

Mix Ins
Еще один способ реализации по умолчанию — это Mix Ins. Здесь интерфейс *только* содержит реализованные элементы. Затем эту функциональность можно «примешать» к классу, просто объявив, что класс реализует интерфейс. Никаких других изменений в классе не требуется.

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

Подведение итогов

Итак, если мы посмотрим на 5 различий между абстрактными классами и интерфейсами, мы обнаружим, что большинство из них больше не соответствует действительности, начиная с C# 8.

Если вы хотите увидеть последствия этих изменений, ознакомьтесь со статьями, доступными здесь: https://github.com/jeremybytes/csharp-8-interfaces или здесь: A Closer Look at C# 8 Interfaces.

А если вам нужно пошаговое руководство с большим количеством примеров кода, вы можете посмотреть это видео на YouTube: Что нового в интерфейсах C# 8 (и как их эффективно использовать).