Интерфейсы в программировании

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

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

Представим, что у нас есть суперкласс "Живые организмы" и суперкласс "Технические устройства". Первый мы используем в качестве родителя для класса "Птицы", а второй – "Самолеты". Если принцип полета и тех и тех одинаков, к каждому классу мы можем подключить один и тот же интерфейс "Полет", в котором описаны функции взлета, посадки и др. Классы не родственны, просто включают один совпадающий интерфейс.

В Kotlin интерфейс объявляется с помощью ключевого слова interface вместо class.

interface ArbitraryStep {
    fun incAS(gap: Int) {
        println("Нельзя изменять на произвольный шаг")
    }
}

Если наш класс будет наследовать данный интерфейс, то получит функцию incAS() в подарок:

class NumInc(n: Int, s: Int): ArbitraryStep {
    var number = n
    var step = s
    fun inc() {number += step}
    fun dec() {number -= step}
}
fun main() {
    val a = NumInc(1, 4)
    a.incAS(10)
}

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

class NumInc(n: Int, s: Int): ArbitraryStep {
 
    …
 
    override fun incAS(gap: Int) {
        number += gap
    }
}

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

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

interface ArbitraryStep {
    fun incAS(gap: Int)
}

В этом случае переопределять функцию incAS() в классе обязательно. Потому что в интерфейсе функция без реализации по умолчанию становится абстрактной.

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

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

interface ArbitraryStep {
    var arbitraryStep: Int
    fun incAS(gap: Int)
}
class NumInc(n: Int, s: Int): ArbitraryStep {
    var number = n
    var step = s
    override var arbitraryStep = 0
 
 fun inc() {number += step}
    fun dec() {number -= step}
 
    override fun incAS(gap: Int) {
        arbitraryStep = gap
        number += gap
    }
}

Свойство интерфейса, также как и его метод, может иметь реализацию. Однако нельзя просто присвоить свойству интерфейса значение. Реализованные свойства интерфейса должны иметь геттер в случае val-переменной и геттер и сеттер в случае var. Причем нельзя использовать ключевое слово field.

Если к классу подключается несколько интерфейсов, они перечисляются после двоеточия через запятую.

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

Практическая работа:

Родительский класс наследует от интерфейса, содержащего метод с реализацией. В самом родительском классе метод интерфейса переопределен. Какую из реализаций получит по умолчанию дочерний класс?

PDF-версия курса с ответами к практическим работам


Введение в объектно-ориентированное программирование на Kotlin




Все разделы сайта