Design Patterns adalah solusi yang sudah teruji untuk masalah desain peranti lunak yang berulang. Buku klasik Gang of Four (GoF) mengkatalogkan 23 pola ke dalam tiga kelompok: creational (pembuatan object), structural (komposisi object), dan behavioral (interaksi antar object). Pada chapter ini kita akan melihat implementasi idiomatic Kotlin untuk beberapa pola perwakilan dari setiap kelompok. Pembaca akan melihat bagaimana fitur Kotlin seperti object, data class, by delegation, dan function type menggantikan boilerplate Java tradisional.
Tiga Kelompok Pola #
| Kelompok | Tujuan | Contoh pola |
|---|---|---|
| Creational | Membuat object dengan fleksibel | Singleton, Factory Method, Builder |
| Structural | Menyusun object menjadi struktur lebih besar | Adapter, Decorator, Facade |
| Behavioral | Mengatur interaksi antar object | Strategy, Observer, Command, Template Method |
Kotlin menawarkan sintaks yang lebih ringkas untuk banyak pola. Sebagai contoh, Singleton menjadi cukup object, dan Strategy seringkali cukup dengan function type (T) -> R tanpa perlu interface terpisah.
Implementasi Beberapa Pola Idiomatic #
// Demo Design Patterns: overview GoF patterns dengan implementasi Kotlin idiomatic.
// Patterns: Creational, Structural, Behavioral.
// === CREATIONAL PATTERNS ===
// Singleton (Kotlin object)
object KonfigurasiApp {
val versi = "1.0.0"
val nama = "Kotlin Demo"
}
// Factory Method (companion object sebagai factory)
interface Hewan { fun bunyi(): String }
class Kucing : Hewan { override fun bunyi() = "meong" }
class Anjing : Hewan { override fun bunyi() = "guk" }
class HewanFactory {
companion object {
fun buat(jenis: String): Hewan = when (jenis.lowercase()) {
"kucing" -> Kucing()
"anjing" -> Anjing()
else -> throw IllegalArgumentException("Jenis tidak dikenal: $jenis")
}
}
}
// Builder (idiomatic Kotlin: data class + copy + apply)
data class KonfigurasiServer(
val host: String = "localhost",
val port: Int = 8080,
val timeout: Int = 30,
val ssl: Boolean = false
)
// === STRUCTURAL PATTERNS ===
// Adapter
interface Target { fun request(): String }
class Adaptee { fun spesifikRequest() = "respons spesifik adaptee" }
class Adapter(private val adaptee: Adaptee) : Target {
override fun request() = adaptee.spesifikRequest().uppercase()
}
// Decorator (via `by` delegation)
interface Notifier { fun kirim(pesan: String) }
class EmailNotifier : Notifier {
override fun kirim(pesan: String) = println("[email] $pesan")
}
class LoggingNotifier(private val inner: Notifier) : Notifier by inner {
override fun kirim(pesan: String) {
println("[log] akan mengirim notifikasi")
inner.kirim(pesan)
println("[log] notifikasi terkirim")
}
}
// Facade
class CPU { fun nyalakan() = "CPU on" }
class Memory { fun load() = "Memory loaded" }
class Disk { fun read() = "Disk read" }
class ComputerFacade {
private val cpu = CPU()
private val mem = Memory()
private val disk = Disk()
fun start(): String = "${cpu.nyalakan()}, ${mem.load()}, ${disk.read()}"
}
// === BEHAVIORAL PATTERNS ===
// Strategy (function as parameter — idiomatic Kotlin)
fun proses(data: List<Int>, strategi: (List<Int>) -> Int): Int = strategi(data)
val strategiSum: (List<Int>) -> Int = { it.sum() }
val strategiMax: (List<Int>) -> Int = { it.maxOrNull() ?: 0 }
// Observer (via StateFlow-like simple impl)
class Publisher<T> {
private val subscribers = mutableListOf<(T) -> Unit>()
fun subscribe(s: (T) -> Unit) = subscribers.add(s)
fun publish(value: T) = subscribers.forEach { it(value) }
}
// Command
interface Command { fun execute(): String }
class CommandA : Command { override fun execute() = "Command A dieksekusi" }
class CommandB : Command { override fun execute() = "Command B dieksekusi" }
class Invoker {
private val history = mutableListOf<Command>()
fun run(c: Command): String {
history.add(c)
return c.execute()
}
}
// Template Method
abstract class PesananTemplate {
fun proses(): String {
val a = validasi()
val b = bayar()
val c = kirim()
return "$a -> $b -> $c"
}
abstract fun validasi(): String
abstract fun bayar(): String
abstract fun kirim(): String
}
class PesananDigital : PesananTemplate() {
override fun validasi() = "validasi digital"
override fun bayar() = "bayar digital"
override fun kirim() = "kirim email"
}
fun main() {
println("=== Demo Design Patterns ===")
// Creational
println("KonfigurasiApp: ${KonfigurasiApp.nama} v${KonfigurasiApp.versi}")
println("Factory: ${HewanFactory.buat("kucing").bunyi()}")
val cfg = KonfigurasiServer().copy(host = "production.example.com", ssl = true)
println("Builder copy: $cfg")
println()
// Structural
val adapter = Adapter(Adaptee())
println("Adapter: ${adapter.request()}")
println("--- Decorator ---")
val notifier: Notifier = LoggingNotifier(EmailNotifier())
notifier.kirim("halo")
println("Facade: ${ComputerFacade().start()}")
println()
// Behavioral
val data = listOf(1, 2, 3, 4, 5)
println("Strategy sum: ${proses(data, strategiSum)}")
println("Strategy max: ${proses(data, strategiMax)}")
println("--- Observer ---")
val pub = Publisher<String>()
pub.subscribe { println(" Sub 1 menerima: $it") }
pub.subscribe { println(" Sub 2 menerima: $it") }
pub.publish("event")
println("--- Command ---")
val inv = Invoker()
println(inv.run(CommandA()))
println(inv.run(CommandB()))
println("--- Template Method ---")
println(PesananDigital().proses())
}Output yang Dihasilkan:
=== Demo Design Patterns ===
KonfigurasiApp: Kotlin Demo v1.0.0
Factory: meong
Builder copy: KonfigurasiServer(host=production.example.com, port=8080, timeout=30, ssl=true)
Adapter: RESPONS SPESIFIK ADAPTEE
--- Decorator ---
[log] akan mengirim notifikasi
[email] halo
[log] notifikasi terkirim
Facade: CPU on, Memory loaded, Disk read
Strategy sum: 15
Strategy max: 5
--- Observer ---
Sub 1 menerima: event
Sub 2 menerima: event
--- Command ---
Command A dieksekusi
Command B dieksekusi
--- Template Method ---
validasi digital -> bayar digital -> kirim emailBedah Kode Baris-per-Baris #
Mari kita amati bagaimana Kotlin mempersingkat banyak pola:
- Baris 7-10 (
object KonfigurasiApp): Singleton tradisional Java membutuhkanprivate constructordan fieldINSTANCEdengangetInstance(). Kotlin cukupobject; kompilator membuat instance tunggal secara otomatis. - Baris 13-25 (
HewanFactory): Companion object menjadi wadah factory method.whendengan else branch yang melempar exception memberikan jalur yang jelas untuk input tidak dikenal. - Baris 28-34 (
KonfigurasiServer): Builder Java membutuhkan class terpisah dengan banyak setter. Kotlin cukupdata classdengan default parameter dancopy()untuk membuat instance baru dengan field yang diperbarui. - Baris 49-55 (
LoggingNotifier): Decorator tradisional Java harus mendelegasikan setiap method secara manual. Kotlinby innermendelegasikan seluruh interface secara otomatis; kita hanya override method yang ingin ditambah behavior. - Baris 71-72 (
fun proses): Strategy tradisional memerlukan interfaceStrategidan beberapa class concrete. Kotlin cukup function type(List<Int>) -> Int; lambda dapat diteruskan langsung sebagai parameter.
Kapan Tidak Memakai Pola #
Pola adalah solusi, tetapi bukan tujuan. Memaksakan pola pada masalah sederhana hanya menambah kompleksitas tanpa manfaat. Sebelum menerapkan pola, ajukan pertanyaan: apakah masalahnya benar-benar berulang? Apakah kompleksitas pola sepadan dengan fleksibilitas yang diperoleh? Banyak kode yang sehat tanpa pola formal selama tetap mengikuti prinsip SOLID.
Penutup #
Katalog GoF tetap relevan, tetapi implementasinya di Kotlin seringkali lebih ringkas berkat fitur bahasa modern. Pada chapter-chapter berikutnya kita akan membedah setiap pola secara mendalam, mulai dari Factory Pattern yang memanfaatkan companion object sebagai factory.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home
— halaman utama dokumentasi Kotlin yang merangkum
object,data class, daninterfacesebagai pondasi design pattern