Interface pada Kotlin

Muhammad Fajri
Thursday, 15 April 2021

Bismillahirrahmanirrahim.

Bentuk dasar interface di Kotlin, seperti di Java, dapat dilihat pada contoh kode berikut.

interface Fax {
    fun call(number: String) = println("Calling $number")
    fun print(doc: String) = println("Fax:Printing $doc")
    fun answer()
}

Pembuatan interface di Kotlin menggunakan kata kunci interface sama seperti di Java, dan di contoh di atas memuat abstract function. Yang menandakan interface di Kotlin yaitu, memuat properti dan memiliki function dengan implementation (concrete function). Untuk menerapkan interface, Kotlin menggunakan operator titik dua (:).

class MultiFunction: Fax {
    override fun answer() {
        
    }
}

Tanda titik dua (:) digunakan untuk menerapkan interface, berbeda dengan Java yang menggunakan kata kunci implements. Tanda titik dua ini juga digunakan untuk meng-inherit sebuah class. Function answer() yang tidak memiliki implementation pada interface sedangkan function call() dan print() tidak perlu karena ia sudah memiliki implementation di dalam interface. Kata kunci override digunakan untuk memberitahukan compiler bahwa kita tidak ingin menyembunyikan function answer() tetapi kita ingin menggantinya agar bisa menjadi polymorphic. Untuk function answer() ini kita ingin menyediakan behavior-nya di dalam class, dalam hal ini class MultiFunction.

Jika ada yang bertanya, kenapa Kotlin memungkinkan kita untuk membuat implementation dari sebuah function di dalam interface, bukankah ia hanya dapat memuat abstract function dan implementasinya dilakukan di dalam class yang akan menggunakannya?

Ada beberapa alasan praktis kenapa hal ini dibolehkan. Implementation default pada interface memungkinkan kita untuk mengembangkan interface sepanjang waktu. Bayangkan kita menulis interface Foo saat ini dengan member function a(), b(), dan c(), dan ini telah dirilis kepada developer yang lain. Suatu hari, kita mendambahkan function d() pada interface Foo maka semua kode yang menggunakan Foo akan rusak. Akan tetapi, jika kita menyediakan default implementation untuk d(), maka kode yang sudah ada tidak akan rusak. Ini merupakan salah satu use-case di mana penerapan function dalam interface akan sangat berguna.

Diamond Problem

Diamond problem terjadi jika class meng-inherit, katakanlah dua buah super type dan keduanya mengimplementasikan function atau method yang benar-benar sama. Lihat contoh berikut.

interface A {
    fun foo() = println("A:foo")
}

interface B {
    fun foo() = println("B:foo")
}

class Child: A, B {

}

Kode di atas tidak akan di-compile karena tidak jelas behavior dari function yang mana yang akan dieksekusi, foo() didefinisikan di kedua interface (A dan B) dan keduanya menyediakan default implementation untuk function-nya. Ini dipahami sebagai “diamond problem”. Pada contoh tersebut, jika kita memanggil function foo() dari instance Child, ini akan membuat bingung behavior mana yang akan dicetak (“A:foo” ataukah “B:foo”). Di Koltin, untuk mengatasi hal ini kita buat implementation dari function yang mengalami konflik di dalam class, dari contoh ini class Child.

Berikut merupakan penyelesaiannya.

interface A {
    fun foo() = println("A:foo")
}

interface B {
    fun foo() = println("B:foo")
}

class Child: A, B {
    override fun foo() = println("Child:foo")
}

fun main() {
    var child: Child = Child()
    child.foo()
}

Output:

Child:foo

Invoking Super Behavior

Seperti Java, function Kotlin dapat memanggil function dari supertype-nya jika memiliki implementation. Kata kunci yang digunakan sama dengan yang di Java yaitu super yang mereferensi ke instance dari supertype. Untuk memanggil function pada supertype, dibutuhkan kata kunci super, nama supertype yang berada di dalam kurung siku (<...>), dan nama function yang ingin dipanggil.

Struktur kodenya seperti berikut:

super<NameOfSuperType>.functionName()

Sebagai contoh, perhatikan program berikut.

interface Printable {
    fun print(doc: String) = println("Printer:Printing $doc")
}

interface Fax {
    fun call(number: String) = println("Calling $number")
    fun print(doc: String) = println("Fax:Printing $doc")
    fun answer() = println("answering")
}

class MultiFunction: Printable, Fax {
    override fun print(doc: String) {
        println("Multifunction: printing")
    }
}

Pada kode di atas, di dalam class MultiFunction, kita lakukan override pada function print(), ini harus dilakukan karena function ini di-inherit dari interface Printable dan Fax, dan memiliki default implementation di keduanya. Selanjutnya, agar dapat memanggil function pada supertype, kita ubah kode pada class MultiFunction menjadi:

class MultiFunction: Printable, Fax {
    override fun print(doc: String) {
        super<Fax>.print(doc)
        super<Printable>.print(doc)
        println("Multifunction: printing")
    }
}

Dan untuk memanggil membuat object dari class pada function main(), kita bisa buat seperti pembuatan object pada biasanya.

fun main() {
    val mfc = MultiFunction()
    mfc.print("The quick brown fox")
    mfc.call("12345")
}

Untuk kode lengkapnya dapat dilihat pada program berikut.

interface Printable {
    fun print(doc: String) = println("Printer:Printing $doc")
}

interface Fax {
    fun call(number: String) = println("Calling $number")
    fun print(doc: String) = println("Fax:Printing $doc")
    fun answer() = println("answering")
}

class MultiFunction: Printable, Fax {
    override fun print(doc: String) {
        super<Fax>.print(doc)
        super<Printable>.print(doc)
        println("Multifunction: printing")
    }
}

fun main() {
    val mfc = MultiFunction()
    mfc.print("The quick brown fox")
    mfc.call("12345")
}

Output:

Fax:Printing The quick brown fox
Printer:Printing The quick brown fox
Multifunction: printing
Calling 12345

Referensi

  1. Hagos, Ted. 2018. Learn Android Studio 3 with Kotlin: Efficient Android App Development. Apress: Manila.