Builder Pattern adalah pola creational untuk membangun object kompleks langkah demi langkah. Di Java, pola ini lazim diterapkan dengan class Builder terpisah yang punya setter berantai. Kotlin menawarkan alternatif yang lebih ringkas: data class dengan default parameter, named arguments, dan scope function apply. Pada chapter ini kita akan membandingkan Java-style builder tradisional dengan pendekatan idiomatic Kotlin, lalu membangun konfigurasi aplikasi berlapis dengan DSL-like pattern.
Tiga Pendekatan Builder di Kotlin #
| Pendekatan | Kelebihan | Kekurangan |
|---|---|---|
| Java-style Builder | Familiar bagi developer Java, mutable selama proses | Verbose; duplikasi field di Builder dan target |
| Named arguments + default | Ringkas, type-safe, immutability tetap terjaga | Tidak cocok untuk konfigurasi sangat kompleks |
DSL dengan apply | Ekspresif untuk nested config; membaca seperti blok | Sedikit kurang type-safe; salah ketik field tidak terdeteksi di kompilasi |
Untuk sebagian besar kasus, data class dengan default parameter sudah cukup. Java-style builder hanya relevan bila kontrak API mengharuskan kompatibilitas dengan kode Java, dan DSL-like pattern cocok untuk konfigurasi berlapis seperti AppConfig yang berisi ServerConfig dan DatabaseConfig.
Implementasi Tiga Pendekatan #
// Demo Builder Pattern: data class + copy, type-safe builder with apply, idiomatic Kotlin.
// === Java-style Builder (verbose, tidak idiomatic di Kotlin) ===
class PesananBuilder {
private var id: Int = 0
private var pelanggan: String = ""
private var item: MutableList<String> = mutableListOf()
private var total: Double = 0.0
fun id(v: Int) = apply { id = v }
fun pelanggan(v: String) = apply { pelanggan = v }
fun tambahItem(nama: String, harga: Double) = apply {
item.add(nama)
total += harga
}
fun build() = PesananBuilt(id, pelanggan, item.toList(), total)
}
data class PesananBuilt(val id: Int, val pelanggan: String, val item: List<String>, val total: Double)
// === Idiomatic Kotlin: data class dengan default parameter + named arguments ===
data class Pesanan(
val id: Int,
val pelanggan: String,
val item: List<String> = emptyList(),
val total: Double = 0.0
)
// === Builder dengan apply untuk konfigurasi kompleks ===
class ServerConfig {
var host: String = "localhost"
var port: Int = 8080
var timeoutMs: Int = 5000
var ssl: Boolean = false
var maxConnection: Int = 100
var enableCors: Boolean = false
override fun toString() = "ServerConfig(host=$host, port=$port, timeout=${timeoutMs}ms, ssl=$ssl, maxConn=$maxConnection, cors=$enableCors)"
}
class DatabaseConfig {
var url: String = "jdbc:h2:mem:test"
var user: String = "sa"
var password: String = ""
var poolSize: Int = 10
override fun toString() = "DatabaseConfig(url=$url, user=$user, poolSize=$poolSize)"
}
class AppConfig {
var server: ServerConfig = ServerConfig()
private set
var database: DatabaseConfig = DatabaseConfig()
private set
fun server(block: ServerConfig.() -> Unit) = apply { server.apply(block) }
fun database(block: DatabaseConfig.() -> Unit) = apply { database.apply(block) }
override fun toString() = "AppConfig(\n $server,\n $database\n)"
}
fun main() {
println("=== Demo Builder Pattern ===")
println("--- 1. Java-style Builder ---")
val p1 = PesananBuilder()
.id(101)
.pelanggan("Budi")
.tambahItem("Buku", 15000.0)
.tambahItem("Pulpen", 5000.0)
.build()
println(p1)
println()
println("--- 2. Idiomatic Kotlin: named arguments + default ---")
val p2 = Pesanan(
id = 102,
pelanggan = "Siti",
item = listOf("Penggaris", "Buku"),
total = 20000.0
)
println(p2)
println()
println("--- 3. Builder dengan apply ---")
val cfg = ServerConfig().apply {
host = "production.example.com"
port = 443
ssl = true
maxConnection = 1000
}
println(cfg)
println()
println("--- 4. Nested DSL-like config ---")
val app = AppConfig()
.server {
host = "api.example.com"
port = 443
ssl = true
enableCors = true
}
.database {
url = "jdbc:postgresql://db.example.com/app"
user = "app_user"
poolSize = 20
}
println(app)
}Output yang Dihasilkan:
=== Demo Builder Pattern ===
--- 1. Java-style Builder ---
PesananBuilt(id=101, pelanggan=Budi, item=[Buku, Pulpen], total=20000.0)
--- 2. Idiomatic Kotlin: named arguments + default ---
Pesanan(id=102, pelanggan=Siti, item=[Penggaris, Buku], total=20000.0)
--- 3. Builder dengan apply ---
ServerConfig(host=production.example.com, port=443, timeout=5000ms, ssl=true, maxConn=1000, cors=false)
--- 4. Nested DSL-like config ---
AppConfig(
ServerConfig(host=api.example.com, port=443, timeout=5000ms, ssl=true, maxConn=100, cors=true),
DatabaseConfig(url=jdbc:postgresql://db.example.com/app, user=app_user, poolSize=20)
)Bedah Kode Baris-per-Baris #
Mari kita perhatikan bagian kunci:
- Baris 4-18 (
PesananBuilder): Java-style builder dengan setter berantai yang mengembalikanthisviaapply. Methodbuild()akhirnya membangun instancePesananBuilt. Pendekatan ini berlebihan di Kotlin karena duplikasi field antara builder dan target. - Baris 21-27 (
data class Pesanan): Default parameter menggantikan builder. Klien memakai named arguments untuk menyebut field yang ingin diisi; sisanya memakai default. Fielditemdefault keemptyList(), sehingga pesanan tanpa item tetap valid. - Baris 30-38 (
ServerConfig): Class denganvaruntuk setiap field konfigurasi. Field diberi default yang masuk akal sehingga klien hanya perlu override yang berbeda. - Baris 49-59 (
AppConfig): Methodserverdandatabasemenerima lambda receiverServerConfig.() -> Unit. Pola ini disebut “DSL-like” karena pemanggilan terlihat seperti blok konfigurasi, padahal sebenarnya adalah method yang menerima lambda. - Baris 78-93 (penggunaan): Method
applymemungkinkan konfigurasi berantai dalam satu ekspresi. Lambda receiver mengubahthismenjadi instance yang sedang dikonfigurasi, sehinggahost = "..."langsung merujuk ke field diServerConfig.
Java-style Builder vs Idiomatic Kotlin #
Java-style builder relevan hanya bila kode harus dipanggil dari Java, karena Java tidak mendukung named arguments. Untuk kode Kotlin murni, default parameter + named arguments lebih disukai karena lebih ringkas, type-safe, dan tidak menambah duplikasi field. DSL-like dengan apply cocok untuk konfigurasi berlapis yang akan terasa berlebihan jika ditulis sebagai satu constructor call panjang.
Penutup #
Kotlin menggantikan banyak boilerplate Java builder dengan default parameter, named arguments, dan scope function apply. Pendekatan idiomatic ini menghasilkan kode yang ringkas, type-safe, dan tetap mendukung konfigurasi berlapis yang kompleks. Pada chapter berikutnya kita akan membahas Strategy Pattern: bagaimana function type menggantikan interface strategy tradisional.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home
— halaman utama dokumentasi Kotlin untuk
data classcopy()dan DSL builder sebagai alternatif builder pattern