Pada chapter sebelumnya kita melihat bahwa sebuah class dapat mengimplementasikan banyak interface sekaligus. Namun ketika implementasi sebuah interface sudah tersedia di class lain, kita sering hanya ingin memakai ulang kode tersebut tanpa harus mewarisi class implementasi. Kotlin menyediakan keyword by untuk interface delegation — sebuah class menerima object lain yang sudah mengimplementasikan interface, lalu meneruskan setiap pemanggilan method interface ke object tersebut. Pada chapter ini kita akan mempelajari bentuk paling sederhana dari by, lalu melihat multiple interface delegation yang mendelegasikan dua interface ke dua object berbeda.

Apa Itu Interface Delegation #

Interface delegation adalah mekanisme Kotlin yang membuat sebuah class tampak mengimplementasikan interface dengan meneruskan pemanggilan method ke instance lain. Sintaks dasarnya adalah class X(impl: Interface) : Interface by impl. Kompilator akan menambahkan method luas(), keliling(), dan setiap anggota interface lain ke dalam class X, di mana setiap method hanya memanggil method dengan nama sama pada impl.

Mekanisme ini berbeda dengan inheritance. Class yang mendelegasikan tidak mewarisi field maupun method non-interface dari object pemberi. Class hanya mendapatkan kontrak interface — method yang dideklarasikan di dalam interface tersebut. Karena itu delegation cocok dipakai ketika kita ingin komposisi (composition) alih-alih pewarisan (inheritance).

Delegasi Sederhana #

Contoh berikut mendefinisikan interface Bentuk, dua class implementasi (Lingkaran dan Persegi), lalu class Segitiga yang mendelegasikan implementasi Bentuk ke instance yang disuntikkan lewat constructor.

kotlin
// interface_delegation.kt
// Mendemonstrasikan delegation pattern dengan keyword `by`.

interface Bentuk {
    fun luas(): Double
    fun keliling(): Double
}

class Lingkaran(private val jariJari: Double) : Bentuk {
    override fun luas(): Double = Math.PI * jariJari * jariJari
    override fun keliling(): Double = 2 * Math.PI * jariJari
}

class Persegi(private val sisi: Double) : Bentuk {
    override fun luas(): Double = sisi * sisi
    override fun keliling(): Double = 4 * sisi
}

// Segitiga mendelegasikan implementasi Bentuk ke object yang diberikan.
class Segitiga(b: Bentuk, private val nama: String) : Bentuk by b {
    fun deskripsi(): String = "Segitiga '$nama' mendelegasikan Bentuk ke object eksternal"
}

interface Berwarna {
    fun warna(): String
}

class WarnaDasar(private val nilai: String) : Berwarna {
    override fun warna(): String = nilai
}

// Multiple interface delegation: Bentuk dan Berwarna didelegasikan.
class Lukisan(b: Bentuk, w: Berwarna, private val judul: String) :
    Bentuk by b, Berwarna by w {
    fun ringkasan(): String =
        "Lukisan '$judul' -> luas ${luas()}, keliling ${keliling()}, warna ${warna()}"
}

fun main() {
    val lingkaran: Bentuk = Lingkaran(3.0)
    val segitiga = Segitiga(lingkaran, "Piring Terbang")
    println(segitiga.deskripsi())
    println("Luas segitiga (delegasi): ${segitiga.luas()}")
    println("Keliling segitiga (delegasi): ${segitiga.keliling()}")
    println()

    // Simpan sebagai Any agar pemeriksaan `is` menjadi informatif,
    // bukan selalu true seperti pada tipe statis Lukisan.
    val lukisan: Any = Lukisan(Persegi(4.0), WarnaDasar("merah"), "Pemandangan")
    println("Cek tipe Lukisan terhadap kontrak: Bentuk=${lukisan is Bentuk}, Berwarna=${lukisan is Berwarna}")
    when (lukisan) {
        is Lukisan -> println(lukisan.ringkasan())
        else -> println("Tipe tidak dikenali")
    }
}

Output yang Dihasilkan:

text
Segitiga 'Piring Terbang' mendelegasikan Bentuk ke object eksternal
Luas segitiga (delegasi): 28.274333882308138
Keliling segitiga (delegasi): 18.84955592153876

Cek tipe Lukisan terhadap kontrak: Bentuk=true, Berwarna=true
Lukisan 'Pemandangan' -> luas 16.0, keliling 16.0, warna merah

Mari kita bedah kode di atas:

  1. interface Bentuk { fun luas(); fun keliling() }: kontrak interface yang akan didelegasikan. Dua method tanpa body.
  2. class Lingkaran(...) : Bentuk: implementasi konkret Bentuk yang menghitung luas dan keliling lingkaran dengan rumus Math.PI.
  3. class Persegi(...) : Bentuk: implementasi lain untuk bentuk persegi.
  4. class Segitiga(b: Bentuk, ...) : Bentuk by b: parameter b tidak disimpan sebagai property (tidak memakai val) — class hanya memakainya untuk mendelegasikan implementasi interface. Setiap pemanggilan segitiga.luas() akan diteruskan ke b.luas().
  5. segitiga.luas() dan segitiga.keliling(): kedua method ini tidak ditulis di dalam class Segitiga, tetapi dapat dipanggil karena kompilator membuat implementasi penerusan otomatis dari b.
  6. class Lukisan(b: Bentuk, w: Berwarna, ...) : Bentuk by b, Berwarna by w: multiple interface delegation. Satu class mendelegasikan dua interface ke dua object berbeda. Method luas() diteruskan ke b, method warna() diteruskan ke w.
  7. lukisan is Bentuk dan lukisan is Berwarna: karena class mengimplementasikan kedua interface via delegation, instance Lukisan dapat diperlakukan sebagai Bentuk maupun Berwarna.

Multiple Interface Delegation #

Satu class Kotlin dapat mendelegasikan banyak interface sekaligus dengan menuliskan tiap interface setelah by. Pada contoh di atas, class Lukisan menerima dua parameter — b: Bentuk dan w: Berwarna — lalu mendelegasikan masing-masing interface ke parameter yang sesuai. Hasilnya, instance Lukisan dapat dipanggil .luas() (diteruskan ke b) dan .warna() (diteruskan ke w) seolah-olah keduanya didefinisikan dalam class itu sendiri.

Pola ini sangat berguna ketika kita ingin menyusun class yang menggabungkan beberapa kemampuan tanpa melakukan multiple inheritance (yang tidak diperbolehkan untuk class di Kotlin). Kita cukup menyuntikkan object yang sudah mengimplementasikan tiap interface, dan class baru akan mengikuti kontrak gabungan tanpa menulis kode boilerplate.

Override Method Hasil Delegasi #

Class yang mendelegasikan interface tetap dapat meng-override method tertentu dari interface tersebut. Override ditulis secara eksplisit di body class, dan hanya method yang di-override yang berperilaku berbeda — method lain tetap diteruskan ke object delegasi. Mekanisme ini memberi ruang untuk mengkustomisasi sebagian method tanpa harus menulis ulang seluruh interface.

Referensi Lanjutan #

  • Interfaces — kontrak interface yang akan didelegasikan
  • Delegation — pola resmi delegation dengan keyword by

Ringkasan #

Keyword by memungkinkan class memakai ulang implementasi interface yang sudah ada tanpa inheritance. Satu class dapat mendelegasikan banyak interface sekaligus dengan menuliskannya satu per satu setelah by, sehingga instance class mengikuti kontrak gabungan beberapa interface. Pada chapter Nested Classes berikutnya, kita akan melihat bagaimana mendeklarasikan class di dalam class untuk membungkus helper yang tidak perlu diekspos ke luar.