Adapter Pattern adalah pola structural yang menyatukan dua interface yang tidak kompatibel. Adapter bekerja sebagai penerjemah: klien memanggil method yang familiar, adapter mengonversi panggilan tersebut menjadi method yang dipahami target. Pada chapter ini kita akan membangun beberapa adapter: class adapter yang membungkus logger lama, extension function sebagai adapter idiomatic, adapter untuk konversi List ke JSON, serta adapter untuk parsing JSON string menjadi data class.
Kapan Adapter Diperlukan #
Adapter berguna ketika kode sudah ada (legacy library, third-party service) yang interfacenya tidak sesuai dengan harapan kode baru. Mengubah kode legacy seringkali tidak mungkin (vendor) atau berisiko (sudah dipakai banyak tempat). Adapter menjadi jembatan tanpa menyentuh kode legacy.
| Skenario | Adapter yang cocok |
|---|---|
| Library lama dengan interface berbeda | Class adapter yang mengimplementasi target interface |
| Perlu beberapa method konversi pada tipe yang sama | Extension function |
| Konversi struktur data (Map ↔ data class) | Adapter class dengan method toX() |
| Parsing format string ke tipe domain | Adapter + parser |
Implementasi Adapter #
// Demo Adapter Pattern: wraps incompatible interface; extension function as adapter.
// === Target interface yang diharapkan klien ===
interface LoggerModern {
fun log(level: String, message: String)
fun error(message: String) = log("ERROR", message)
fun info(message: String) = log("INFO", message)
fun warn(message: String) = log("WARN", message)
}
// === Adaptee: library lama dengan interface berbeda ===
class LegacyLogger {
fun writeLog(priority: Int, message: String) {
val level = when (priority) {
1 -> "INFO"
2 -> "WARN"
3 -> "ERROR"
else -> "UNKNOWN"
}
println("[legacy] $level: $message")
}
}
// === Adapter: class yang mengimplementasi Target dan membungkus Adaptee ===
class LegacyLoggerAdapter(private val legacy: LegacyLogger) : LoggerModern {
override fun log(level: String, message: String) {
val priority = when (level.uppercase()) {
"INFO" -> 1
"WARN" -> 2
"ERROR" -> 3
else -> 0
}
legacy.writeLog(priority, message)
}
}
// === Extension function as adapter (idiomatic Kotlin) ===
fun LegacyLogger.logModern(level: String, message: String) {
val priority = when (level.uppercase()) {
"INFO" -> 1
"WARN" -> 2
"ERROR" -> 3
else -> 0
}
writeLog(priority, message)
}
// === Adapter untuk collection: List<Int> -> JSON array string ===
class ListToJsonAdapter(private val list: List<Int>) {
fun toJson(): String = "[" + list.joinToString(",") + "]"
}
// === Adapter untuk Map<String, Any> -> data class instance ===
data class Pengguna(val id: Int, val nama: String, val email: String)
class MapToPenggunaAdapter(private val map: Map<String, Any>) {
fun toPengguna(): Pengguna {
@Suppress("UNCHECKED_CAST")
return Pengguna(
id = (map["id"] as Number).toInt(),
nama = map["nama"] as String,
email = map["email"] as String
)
}
}
// === Adapter untuk third-party JSON library (simulated) ===
// Misalkan kita punya library JSON yang mengembalikan String, padahal kita butuh Map
class JsonStringAdapter(private val json: String) {
// Parser sangat sederhana untuk JSON seperti {"id":1,"nama":"Budi","email":"[email protected]"}
fun toMap(): Map<String, Any> {
val body = json.trim().removeSurrounding("{", "}")
return body.split(",").associate { pair ->
val (k, v) = pair.split(":", limit = 2)
val key = k.trim().trim('"')
val rawVal = v.trim()
val value: Any = if (rawVal.startsWith("\"")) rawVal.trim('"')
else rawVal.toIntOrNull() ?: rawVal
key to value
}
}
}
fun main() {
println("=== Demo Adapter Pattern ===")
println("--- 1. Class Adapter (LegacyLogger -> LoggerModern) ---")
val modern: LoggerModern = LegacyLoggerAdapter(LegacyLogger())
modern.info("Aplikasi dimulai")
modern.error("Koneksi gagal")
modern.warn("Stok menipis")
println()
println("--- 2. Extension function as adapter ---")
val legacy = LegacyLogger()
legacy.logModern("INFO", "via extension function")
legacy.logModern("ERROR", "via extension function")
println()
println("--- 3. List -> JSON ---")
val daftar = listOf(1, 2, 3, 4, 5)
val json = ListToJsonAdapter(daftar).toJson()
println("List: $daftar -> JSON: $json")
println()
println("--- 4. Map -> data class ---")
val map = mapOf("id" to 1, "nama" to "Budi", "email" to "[email protected]")
val pengguna = MapToPenggunaAdapter(map).toPengguna()
println("Map: $map -> Pengguna: $pengguna")
println()
println("--- 5. JSON string -> Map -> data class ---")
val jsonString = """{"id":42,"nama":"Siti","email":"[email protected]"}"""
val parsed = JsonStringAdapter(jsonString).toMap()
println("JSON: $jsonString")
println("Map: $parsed")
val p = MapToPenggunaAdapter(parsed).toPengguna()
println("Pengguna: $p")
}Output yang Dihasilkan:
=== Demo Adapter Pattern ===
--- 1. Class Adapter (LegacyLogger -> LoggerModern) ---
[legacy] INFO: Aplikasi dimulai
[legacy] ERROR: Koneksi gagal
[legacy] WARN: Stok menipis
--- 2. Extension function as adapter ---
[legacy] INFO: via extension function
[legacy] ERROR: via extension function
--- 3. List -> JSON ---
List: [1, 2, 3, 4, 5] -> JSON: [1,2,3,4,5]
--- 4. Map -> data class ---
Map: {id=1, nama=Budi, [email protected]} -> Pengguna: Pengguna(id=1, nama=Budi, [email protected])
--- 5. JSON string -> Map -> data class ---
JSON: {"id":42,"nama":"Siti","email":"[email protected]"}
Map: {id=42, nama=Siti, [email protected]}
Pengguna: Pengguna(id=42, nama=Siti, [email protected])Bedah Kode Baris-per-Baris #
Mari kita perhatikan bagian kunci:
- Baris 4-9 (
LoggerModern): Interface modern yang dipakai klien baru. Method defaulterror,info,warnmendelegasikan kelogsehingga adapter hanya perlu mengimplementasi satu method. - Baris 12-22 (
LegacyLogger): Adaptee dengan interface berbeda. MethodwriteLogmenerima priority integer, bukan string level. Mengubah class ini berisiko karena mungkin sudah dipakai banyak tempat. - Baris 25-35 (
LegacyLoggerAdapter): Adapter yang mengimplementasiLoggerModerndan membungkusLegacyLogger. Methodlogmenerjemahkan string level menjadi priority integer, lalu memanggilwriteLog. - Baris 38-46 (
LegacyLogger.logModern): Extension function sebagai adapter. Tidak membuat class baru; kita hanya menambah method keLegacyLoggerdari luar. Cocok bila konversi cukup sederhana dan tidak perlu instance adapter terpisah. - Baris 69-82 (
JsonStringAdapter): Adapter untuk parsing JSON string menjadiMap<String, Any>. Parser-nya sangat sederhana dan hanya mendukung JSON flat dengan tipe dasar. Untuk produksi, gunakankotlinx.serializationatauGson.
Memilih Class Adapter vs Extension Function #
| Pertanyaan | Jika ya, pilih |
|---|---|
| Apakah perlu mengimplementasi interface target? | Class adapter |
| Apakah perlu akses private member? | Class adapter |
| Apakah konversi hanya menambah beberapa method publik? | Extension function |
| Apakah klien sudah memakai tipe adaptee secara langsung? | Extension function |
Penutup #
Adapter Pattern menyatukan interface yang tidak kompatibel tanpa mengubah kode legacy. Kotlin menyediakan dua pendekatan: class adapter untuk konversi kompleks dan extension function untuk konversi sederhana. Pada chapter berikutnya kita akan membahas Decorator Pattern: bagaimana menambah behavior ke object tanpa mengubah kode aslinya, memanfaatkan by delegation.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home
— halaman utama dokumentasi Kotlin untuk
interfacedan extension function sebagai sarana adapter pattern