Интерфейсы в программировании
От интерфейсов также как от абстрактных классов нельзя создавать объекты, интерфейсы также как абстрактные классы предназначены исключительно для их наследования другими классами. Разница между интерфейсами и абстрактными классами по большому счету смысловая.
Если родительский класс описывает какую-то общность похожих объектов, объединяет их в единое множество, то интерфейс описывает какую-то дополнительную функциональность, которую можно добавить, включить в любой класс, "прикрутить" к нему. Отсюда также следует, что один класс может наследовать несколько интерфейсов. В 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-версия курса с ответами к практическим работам