Руководство по интерфейсам Kotlin

Руководство по интерфейсам Kotlin

1. обзор

В этом руководствеwe’ll discuss how to define and implement interfaces in Kotlin.

Мы также рассмотрим, как несколько интерфейсов могут быть реализованы в классе. Это, безусловно, может вызвать конфликты, и мы узнаем, какой механизм у Kotlin для их разрешения.

2. Интерфейсы в Котлине

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

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

2.1. Определение интерфейсов

Начнем с определения нашего первого интерфейса на Kotlin:

interface SimpleInterface

Это самый простой интерфейс, который полностью пуст. These are also known as marker interfaces.

Давайте теперь добавим несколько функций в наш интерфейс:

interface SimpleInterface {
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, World!")
    }
}

Мы добавили два метода к нашему ранее определенному интерфейсу:

  • Один из них называется firstMethod  - абстрактный метод.

  • В то время как другой называется secondMethod - это реализация по умолчанию.

Давайте продолжим и добавим некоторые свойства в наш интерфейс:

interface SimpleInterface {
    val firstProp: String
    val secondProp: String
        get() = "Second Property"
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, from: " + secondProp)
    }
}

Здесь мы добавили два свойства в наш интерфейс:

  • Один из них называетсяfirstProp is типа String и является абстрактным.

  • Второй, называемыйsecondProp, также относится к типу string, но определяет реализацию для его метода доступа.

Обратите внимание, чтоproperties in an interface cannot maintain state. Таким образом, следующее является недопустимым выражением в Kotlin:

interface SimpleInterface {
    val firstProp: String = "First Property" // Illegal declaration
}

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

Теперь, когда мы определили базовый интерфейс, давайте посмотрим, как мы можем реализовать его в классе в Kotlin:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
}

Обратите внимание, что когда мы определяемSimpleClass как реализациюSimpleInterface,we only have to provide the implementation for abstract properties and functions. Однако мыcan override any previously defined property or function, too.

Теперь давайте переопределим все ранее определенные свойства и функции в нашем классе:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override val secondProp: String
        get() = "Second Property, Overridden!"

    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
    override fun secondMethod(): String {
        return("Hello, from: " + secondProp + firstProp)
    }
}

Здесь мы переопределили свойствоsecondProp и функциюsecondFunction, которые были ранее определены в интерфейсеSimpleInterface.

2.3 Implementing Interfaces through Delegation

Делегирование - это шаблон проектирования в объектно-ориентированном программированииto achieve code reusability through composition instead of inheritance. Хотя это возможно реализовать на многих языках, таких как Java,Kotlin has native support for implementation through delegation.

Если мы начнем с базового интерфейса и класса:

interface MyInterface {
    fun someMethod(): String
}

class MyClass() : MyInterface {
    override fun someMethod(): String {
        return("Hello, World!")
    }
}

Пока что ничего нового. Но теперь мы можем определить другой класс, который реализует сквозное делегированиеMyInterface :

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass ожидает делегата в качестве аргумента, который фактически реализует интерфейсMyInterface.

Давайте посмотрим, как мы можем вызвать функцию интерфейса через делегат:

val myClass = MyClass()
MyDerivedClass(myClass).someMethod()

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

3. Множественное наследование

Множественное наследование является ключевым понятием в парадигме объектно-ориентированного программирования. This allows for a class to inherit characteristics from more than one parent object, like an interface, for example.

Хотя это обеспечивает большую гибкость в моделировании объектов, оно имеет свой собственный набор сложностей. Одной из таких проблем является «проблема алмазов».

Java 8 has its own mechanisms for addressing the diamond problem, как и любой другой язык, допускающий множественное наследование.

Давайте посмотрим, как Kotlin решает эту проблему через интерфейсы.

3.1. Наследование нескольких интерфейсов

Мы начнем с определения двух простых интерфейсов:

interface FirstInterface {
    fun someMethod(): String
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in FirstInterface")
    }
}

interface SecondInterface {
    fun someMethod(): String {
        return("Hello, from someMethod in SecondInterface")
    }
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in SecondInterface")
    }
}

Обратите внимание, что у обоих интерфейсов есть методы с одним и тем же контрактом.

Теперь давайте определим класс, который наследуется от обоих этих интерфейсов:

class SomeClass: FirstInterface, SecondInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SomeClass")
    }
    override fun anotherMethod(): String {
        return("Hello, from anotherMethod in SomeClass")
    }
}

Как мы видим,SomeClass реализует какFirstInterface, так иSecondInterface. Хотя синтаксически это довольно просто, здесь есть немного семантики, которая требует внимания. Мы рассмотрим это в следующем подразделе.

3.2. Разрешение конфликтов

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

To resolve this conflict, Kotlin requires the subclass to provide an overridden implementation for such functions to make the resolution explicit.

Например,SomeClass выше реализуетanotherMethod. Но если бы этого не произошло, Kotlin не знал бы, следует ли вызывать реализациюanotherMethod по умолчаниюFirst orSecondInterface’s. SomeClassmust implement anotherMethod for this reason.

ОднакоsomeMethod  немного отличается, поскольку на самом деле конфликта нет. FirstInterface  не предоставляет реализацию по умолчанию дляsomeMethod. Тем не менее,SomeClass по-прежнему должен его реализовать, потому чтоKotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.

3.3. Решение проблемы с алмазом

«Алмазная проблема» возникает, когда два дочерних объекта базового объекта описывают конкретное поведение, определенное базовым объектом. Теперь объект, наследующий от обоих этих дочерних объектов, должен определить, на какое унаследованное поведение он подписывается.

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

interface BaseInterface {
    fun someMethod(): String
}

interface FirstChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in FirstChildInterface")
    }
}

interface SecondChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SecondChildInterface")
    }
}

class ChildClass: FirstChildInterface, SecondChildInterface {
    override fun someMethod(): String {
        return super.someMethod()
    }
}

Здесь мы определилиBaseInterface, который объявил абстрактную функцию с именемsomeMethod. Оба интерфейсаFirstChildInterface иSecondChildInterface наследуются отBaseInterface и реализуют функциюsomeMethod.

Теперь, когда мы реализуемChildClass, наследование отFirstChildInterface иSecondChildInterface, нам необходимо переопределить функциюsomeMethod. Однакоeven though we must override the method, we can still simply call super as we do here with SecondChildInterface.

4. Интерфейсы по сравнению с абстрактными классами в Kotlin

Абстрактные классы в Котлине - этоclasses which cannot be instantiated. Это может содержать одно или несколько свойств и функций. Эти свойства и функции могут быть абстрактными или конкретными. Любой класс, наследующий от абстрактного класса, должен реализовывать все унаследованные абстрактные свойства и функции, если только сам этот класс также не объявлен как абстрактный.

4.1. Различия между интерфейсом и абстрактным классом

Подождите! Разве это не похоже на то, что делает интерфейс?

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

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

  • Свойства в интерфейсе не могут поддерживать состояние, в то время как они могут в абстрактном классе

4.2. Когда мы должны использовать что?

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

Обычноinterfaces should be used to define the contract, что указывает на возможности, которые он обещает предоставить. Реализующий класс несет ответственность за выполнение этих обещаний. abstract class, however, should be used to share partial characteristics with extending classes. Расширяющий класс может пойти дальше, чтобы завершить его.

5. Сравнение с интерфейсами Java

С изменениями интерфейса Java в Java 8they have come very close to Kotlin interfaces. Одна из наших предыдущих статей захватываетthe new features introduced in Java 8 including changes to the interface.

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

6. Заключение

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

Наконец, мы обсудили интерфейсы по сравнению с абстрактными классами в Kotlin. Мы также кратко рассказали о том, как интерфейс Kotlin сравнивается с интерфейсом Java.

Как всегда, доступен код для примеровover on GitHub.