Panduan untuk Antarmuka Kotlin

1. Ikhtisar

Dalam tutorial ini, kita akan membahas cara mendefinisikan dan mengimplementasikan antarmuka di Kotlin.

Kami juga akan melihat bagaimana beberapa antarmuka dapat diimplementasikan oleh sebuah kelas. Hal ini tentu saja dapat menyebabkan konflik, dan kita akan mempelajari mekanisme yang harus dimiliki Kotlin untuk menyelesaikannya.

2. Antarmuka di Kotlin

Antarmuka adalah cara untuk memberikan deskripsi atau kontrak untuk kelas dalam pemrograman berorientasi objek. Mereka mungkin berisi properti dan fungsi secara abstrak atau konkret tergantung pada bahasa pemrograman. Kami akan membahas detail antarmuka di Kotlin.

Antarmuka di Kotlin mirip dengan antarmuka di banyak bahasa lain seperti Java. Tetapi mereka memiliki sintaks khusus, mari kita tinjau di beberapa sub-bagian berikutnya.

2.1. Mendefinisikan Antarmuka

Mari kita mulai dengan mendefinisikan antarmuka pertama kita di Kotlin:

interface SimpleInterface

Ini adalah antarmuka paling sederhana yang benar-benar kosong. Ini juga dikenal sebagai antarmuka penanda .

Sekarang mari tambahkan beberapa fungsi ke antarmuka kita:

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

Kami telah menambahkan dua metode ke antarmuka yang kami tentukan sebelumnya:

  • Salah satunya disebut f irstMethod adalah metode abstrak
  • Sedangkan yang lainnya bernama econdMethod memiliki implementasi default.

Mari lanjutkan dan tambahkan beberapa properti ke antarmuka kita sekarang:

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

Di sini kami telah menambahkan dua properti ke antarmuka kami:

  • Salah satunya yang disebut firstProp berjenis String dan abstrak
  • Yang kedua disebut secondProp juga berjenis string tetapi mendefinisikan implementasi untuk pengaksesnya .

Perhatikan bahwa properti dalam antarmuka tidak dapat mempertahankan status . Jadi berikut ini adalah ekspresi ilegal di Kotlin:

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

2.2. Menerapkan Antarmuka

Sekarang kita telah mendefinisikan antarmuka dasar, mari kita lihat bagaimana kita bisa mengimplementasikannya di kelas di Kotlin:

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

Perhatikan bahwa ketika kita mendefinisikan SimpleClass sebagai implementasi dari SimpleInterface , kita hanya perlu menyediakan implementasi untuk properti dan fungsi abstrak . Namun, kami juga dapat mengganti properti atau fungsi yang telah ditentukan sebelumnya.

Sekarang mari kita timpa semua properti dan fungsi yang didefinisikan sebelumnya di kelas kita:

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

Di sini, kita telah mengganti properti secondProp dan fungsi secondFunction yang sebelumnya didefinisikan dalam antarmuka SimpleInterface .

2.3 Menerapkan Antarmuka Melalui Delegasi

Delegasi adalah pola desain dalam pemrograman berorientasi objek untuk mencapai kegunaan kembali kode melalui komposisi alih-alih pewarisan . Meskipun hal ini dapat diimplementasikan dalam banyak bahasa, seperti Java, Kotlin memiliki dukungan native untuk implementasi melalui pendelegasian .

Jika kita mulai dengan antarmuka dan kelas dasar:

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

Sejauh ini, tidak ada yang baru. Tapi sekarang, kita bisa mendefinisikan kelas lain yang mengimplementasikan MyInterface melalui delegasi:

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass mengharapkan delegasi sebagai argumen yang benar-benar mengimplementasikan antarmuka MyInterface .

Mari kita lihat bagaimana kita dapat memanggil fungsi antarmuka melalui delegasi:

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

Di sini kami telah membuat instance MyClass dan menggunakannya sebagai delegasi untuk memanggil fungsi antarmuka di MyDerivedClass, yang sebenarnya tidak pernah mengimplementasikan fungsi ini secara langsung.

3. Warisan Ganda

Pewarisan berganda adalah konsep kunci dalam paradigma pemrograman berorientasi objek. Hal ini memungkinkan kelas untuk mewarisi karakteristik dari lebih dari satu objek induk, seperti antarmuka, misalnya .

Meskipun ini memberikan lebih banyak fleksibilitas dalam pemodelan objek, ia hadir dengan kumpulan kompleksitasnya sendiri. Salah satunya adalah "masalah berlian".

Java 8 memiliki mekanisme sendiri untuk mengatasi masalah berlian, seperti halnya bahasa lain yang memungkinkan pewarisan ganda.

Mari kita lihat bagaimana Kotlin mengatasinya melalui antarmuka.

3.1. Mewarisi Beberapa Antarmuka

Kami akan mulai dengan mendefinisikan dua antarmuka sederhana:

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

Perhatikan bahwa kedua antarmuka memiliki metode dengan kontrak yang sama.

Sekarang mari kita tentukan kelas yang mewarisi dari kedua antarmuka ini:

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

Seperti yang bisa kita lihat, SomeClass mengimplementasikan FirstInterface dan SecondInterface . Meskipun secara sintaksis ini cukup sederhana, ada sedikit semantik yang perlu diperhatikan di sini. Kami akan membahas ini di sub-bagian berikutnya.

3.2. Menyelesaikan Konflik

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.

Terakhir, kita membahas antarmuka dibandingkan dengan kelas abstrak di Kotlin. Kami juga berbicara secara singkat tentang bagaimana antarmuka Kotlin dibandingkan dengan antarmuka Java.

Seperti biasa, kode untuk contoh tersedia di GitHub.