Ръководство за Kotlin интерфейси

1. Общ преглед

В този урок ще обсъдим как да дефинираме и внедрим интерфейси в Kotlin.

Също така ще разгледаме как множество интерфейси могат да бъдат внедрени от клас. Това със сигурност може да предизвика конфликти и ще научим механизма, който Котлин трябва да разреши.

2. Интерфейси в Kotlin

Интерфейсът е начин за предоставяне на описание или договор за класове в обектно-ориентирано програмиране. Те могат да съдържат свойства и функции по абстрактни или конкретни начини в зависимост от езика за програмиране. Ще разгледаме подробностите за интерфейсите в Kotlin.

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

2.1. Дефиниране на интерфейси

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

interface SimpleInterface

Това е най-простият интерфейс, който е напълно празен. Те са известни също като интерфейси с маркери .

Нека сега добавим някои функции към нашия интерфейс:

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

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

  • Един от тях, наречен f irstMethod, е абстрактен метод
  • Докато другият, наречен s econdMethod, има изпълнение по подразбиране.

Нека да добавим някои свойства към нашия интерфейс сега:

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

Тук сме добавили две свойства към нашия интерфейс:

  • Един от тях, наречен firstProp, е от тип String и е абстрактен
  • Вторият, наречен secondProp, също е от типа низ, но дефинира изпълнение за своя достъп.

Имайте предвид, че свойствата в интерфейса не могат да поддържат състояние . Така че следното е незаконно изражение в 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 , ние трябва да осигурим изпълнението само за абстрактни свойства и функции . Въпреки това можем да заменим и всяко предварително дефинирано свойство или функция.

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

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 Внедряване на интерфейси чрез делегиране

Делегирането е модел на проектиране в обектно-ориентирано програмиране за постигане на многократна употреба на кода чрез композиция вместо наследяване . Въпреки че това е възможно да се приложи на много езици, като Java, Kotlin има естествена поддръжка за изпълнение чрез делегиране .

Ако започнем с основен интерфейс и клас:

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. Множествено наследяване

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

Въпреки че това осигурява по-голяма гъвкавост при обектното моделиране, то идва със собствен набор от сложности. Един такъв е „проблемът с диамантите“.

Java 8 има свои собствени механизми за справяне с проблема с диамантите, както и всеки друг език, който позволява множествено наследяване.

Нека да видим как 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. Разрешаване на конфликти

When implementing multiple interfaces, a class may inherit a function which has a default implementation for the same contract in multiple interfaces. This raises the problem of invocation for this function from an instance of the implementing class.

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

For example, SomeClass above implements anotherMethod. But, if it didn't, Kotlin wouldn't know whether to invoke First or SecondInterface's default implementation of anotherMethod. SomeClass must implement anotherMethod for this reason.

However, someMethod is a bit different since there is actually no conflict. FirstInterface doesn't provide a default implementation for someMethod. That said, SomeClass still must implement it because Kotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.

3.3. Resolving the Diamond Problem

A “diamond problem” occurs when two child objects of a base object describe a particular behavior defined by the base object. Now an object inheriting from both these child objects has to resolve which inherited behavior it subscribes to.

Kotlin's solution to this problem is through the rules defined for multiple inheritance in the previous sub-section. Let's define a few interfaces and an implementing class to present the diamond problem:

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() } }

Here we have defined BaseInterface which declared an abstract function called someMethod. Both the interfaces FirstChildInterface and SecondChildInterface inherits from BaseInterface and implement the function someMethod.

Now as we implement ChildClass inheriting from FirstChildInterface and SecondChildInterface, it's necessary for us to override the function someMethod. However, even though we must override the method, we can still simply call super as we do here with SecondChildInterface.

4. Interfaces Compared to Abstract Classes in Kotlin

Abstract classes in Kotlin are classes which cannot be instantiated. This may contain one or more properties and functions. These properties and functions can be abstract or concrete. Any class inheriting from an abstract class must implement all inherited abstract properties and functions unless that class itself is also declared as abstract.

4.1. Differences Between Interface and Abstract Class

Wait! Doesn't that sound exactly like what an interface does?

Actually, at the outset, an abstract class is not very different from the interface. But, there are subtle differences which govern the choice we make:

  • A class in Kotlin can implement as many interfaces as they like but it can only extend from one abstract class
  • Properties in the interface cannot maintain state, while they can in an abstract class

4.2. When Should We Use What?

An interface is just a blueprint for defining classes, they can optionally have some default implementations as well. On the other hand, an abstract class is an incomplete implementation which is completed by the extending classes.

Typically interfaces should be used to define the contract, which elicits the capabilities it promises to deliver. An implementing class holds the responsibility of delivering those promises. An abstract class, however, should be used to share partial characteristics with extending classes. An extending class can take it further to complete it.

5. Comparison With Java Interfaces

With the changes to Java interface in Java 8, they have come very close to Kotlin interfaces. One of our previous articles captures the new features introduced in Java 8 including changes to the interface.

There are mostly syntactic differences between Java and Kotlin interfaces now. One difference which stands out is related to the keyword “override”. In Kotlin, while implementing abstract properties or functions inherited from an interface, it is mandatory to qualify them with the keyword “override“. There is no such explicit requirement in Java.

6. Conclusion

In this tutorial, we discussed Kotlin interfaces, how to define and implement them. Then we talked about inheriting from multiple interfaces and the conflict they may create. We took a look at how Kotlin handles such conflicts.

Накрая обсъдихме интерфейсите в сравнение с абстрактните класове в Kotlin. Също така накратко говорихме за това как интерфейсът Kotlin се сравнява с интерфейса Java.

Както винаги, кодът за примерите е достъпен в GitHub.