Decorator Pattern adalah pola structural yang menambah behavior ke object tanpa mengubah class aslinya. Pattern ini alternatif fleksibel untuk inheritance: alih-alih membuat sub-class untuk setiap kombinasi fitur, kita membungkus object dengan decorator bertingkat. Kotlin menawarkan by delegation yang menghapus boilerplate Java tradisional: base decorator cukup satu baris, dan kita hanya override method yang ingin ditambah behavior. Pada chapter ini kita akan menerapkan decorator untuk pesan (timestamp, prefix, upper, log) dan kalkulator (logging, plus, times).
Karakteristik Decorator #
| Ciri | Penjelasan |
|---|---|
| Sama interface | Decorator mengimplementasi interface yang sama dengan target |
| Membungkus | Decorator menyimpan reference ke object yang didekorasi |
| Mendelegasikan | Method yang tidak di-override diteruskan ke object dalam |
| Bisa bertingkat | Beberapa decorator bisa dibungkus satu sama lain |
Karakteristik di atas memungkinkan decorator dibangun saat runtime sesuai kebutuhan, alih-alih ditentukan di hierarki class saat kompilasi.
Implementasi Decorator dengan by Delegation #
// Demo Decorator Pattern: tambah behavior tanpa modifikasi original; `by` delegation.
// === Component interface ===
interface Pesan {
fun konten(): String
}
// === Concrete Component ===
class PesanTeks(private val teks: String) : Pesan {
override fun konten() = teks
}
// === Base Decorator: bungkus Pesan, delegasikan via `by` ===
class PesanDecorator(pesan: Pesan) : Pesan by pesan
// === Concrete Decorators: tambah behavior ===
class PesanDenganTimestamp(private val pesan: Pesan) : Pesan by pesan {
override fun konten(): String {
// Tambah prefix timestamp sebelum konten asli
return "[2026-01-15] ${pesan.konten()}"
}
}
class PesanDenganPrefix(private val pesan: Pesan, private val prefix: String) : Pesan by pesan {
override fun konten(): String = "$prefix ${pesan.konten()}"
}
class PesanUpper(private val pesan: Pesan) : Pesan by pesan {
override fun konten(): String = pesan.konten().uppercase()
}
class PesanDenganLog(private val pesan: Pesan) : Pesan by pesan {
override fun konten(): String {
val hasil = pesan.konten()
println("[log] konten diproses: $hasil")
return hasil
}
}
// === Decorator untuk kalkulasi (wrap function/operation) ===
interface Kalkulator { fun hitung(x: Int): Int }
class KalkulatorDasar : Kalkulator {
override fun hitung(x: Int) = x * 2
}
class KalkulatorPlus10(private val inner: Kalkulator) : Kalkulator {
override fun hitung(x: Int) = inner.hitung(x) + 10
}
class KalkulatorTimes3(private val inner: Kalkulator) : Kalkulator {
override fun hitung(x: Int) = inner.hitung(x) * 3
}
class KalkulatorLogging(private val inner: Kalkulator) : Kalkulator {
override fun hitung(x: Int): Int {
val hasil = inner.hitung(x)
println("[log] hitung($x) -> $hasil")
return hasil
}
}
fun main() {
println("=== Demo Decorator Pattern ===")
println("--- 1. Decorator Pesan ---")
val p1: Pesan = PesanTeks("halo")
println("Asli: ${p1.konten()}")
val p2: Pesan = PesanDenganTimestamp(p1)
println("Dengan timestamp: ${p2.konten()}")
val p3: Pesan = PesanUpper(p1)
println("Upper: ${p3.konten()}")
// Stack decorator
val p4: Pesan = PesanDenganPrefix(PesanUpper(PesanDenganTimestamp(p1)), "INFO")
println("Stacked: ${p4.konten()}")
val p5: Pesan = PesanDenganLog(PesanUpper(p1))
println("Dengan log:")
p5.konten()
println()
println("--- 2. Decorator Kalkulator ---")
val k: Kalkulator = KalkulatorLogging(KalkulatorTimes3(KalkulatorPlus10(KalkulatorDasar())))
// urutan: dasar(x*2) -> +10 -> *3 -> log
// x=5: 5*2=10, +10=20, *3=60
println("Hasil untuk x=5:")
k.hitung(5)
val k2: Kalkulator = KalkulatorDasar()
println("Dasar saja (x=5): ${k2.hitung(5)}")
}Output yang Dihasilkan:
=== Demo Decorator Pattern ===
--- 1. Decorator Pesan ---
Asli: halo
Dengan timestamp: [2026-01-15] halo
Upper: HALO
Stacked: INFO [2026-01-15] HALO
Dengan log:
[log] konten diproses: HALO
--- 2. Decorator Kalkulator ---
Hasil untuk x=5:
[log] hitung(5) -> 60
Dasar saja (x=5): 10Bedah Kode Baris-per-Baris #
Mari kita amati bagian kunci:
- Baris 4-6 (
interface Pesan): Component interface yang diimplementasi oleh concrete component dan semua decorator. Interface ini harus tetap stabil; decorator menambah behavior di sekitarnya tanpa mengubah interface. - Baris 9-11 (
PesanTeks): Concrete component dengan implementasi dasar. Tidak tahu bahwa ia akan didekorasi. - Baris 14 (
class PesanDecorator(pesan: Pesan) : Pesan by pesan): Base decorator menggunakanbydelegation. Satu baris ini mendelegasikan seluruh method interface ke object yang dibungkus. Tanpaby, kita harus menulis ulang setiap method interface secara manual. - Baris 17-21 (
PesanDenganTimestamp): Decorator konkret yang overridekontenuntuk menambahkan timestamp. Behavior tambahan ini tidak mengubah object dalam; pesan asli tetap dapat dipakai secara terpisah. - Baris 77 (
PesanDenganPrefix(PesanUpper(PesanDenganTimestamp(p1)), "INFO")): Tiga decorator bertingkat. Decorator dipanggil dari dalam keluar: timestamp → upper → prefix. Hasil akhir adalah pesan yang sudah diberi timestamp, di-uppercased, lalu diberi prefix.
Urutan Decorator #
Urutan decorator memengaruhi hasil akhir. Pada contoh kalkulator KalkulatorLogging(KalkulatorTimes3(KalkulatorPlus10(KalkulatorDasar()))), operasi dieksekusi dari dalam keluar: x*2 → +10 → *3 → log hasil. Memindahkan KalkulatorLogging ke dalam akan menghasilkan log per tahap. Pemahaman urutan ini penting saat merangkai decorator kompleks.
Decorator vs Inheritance #
Inheritance menghasilkan class statis per kombinasi fitur. Untuk 3 fitur biner (timestamp, prefix, upper), inheritance butuh 2^3 = 8 class. Decorator cukup 3 class yang dapat dikombinasikan secara bebas. Trade-off: decorator lebih bertele-tele saat dirakit, tetapi jauh lebih fleksibel saat kombinasi fitur tidak diketahui di muka.
Penutup #
Decorator Pattern menambah behavior tanpa mengubah kode asli, dengan memanfaatkan by delegation untuk menghapus boilerplate Java tradisional. Pola ini cocok untuk fitur yang dapat dikombinasikan bebas seperti logging, caching, atau transformasi data. Pada chapter berikutnya kita akan membahas Repository Pattern: abstraksi akses data yang menjadi pilar banyak arsitektur modern.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home
— halaman utama dokumentasi Kotlin untuk delegation
byyang menjadi idiom decorator pattern