Factory Pattern adalah pola creational yang memisahkan proses pembuatan object dari kode yang menggunakan object tersebut. Pola ini berguna ketika proses pembuatan melibatkan keputusan tipe, konfigurasi default, atau validasi input. Kotlin menyediakan tiga pendekatan idiomatic: companion object sebagai factory, generic interface Factory<T>, dan sealed class dengan factory di companion object. Pada chapter ini kita akan membangun factory untuk notifikasi, factory generic untuk hewan dan mobil, serta sealed class factory yang mem-parsing JSON menjadi tipe pesan yang berbeda.

Tiga Varian Factory di Kotlin #

VarianKapan dipakaiContoh
Companion object factoryFactory method statik per classNotifikasiFactory.email("...")
Generic Factory<T> interfaceBanyak class yang punya pola factory samaKucing.create() mengembalikan Kucing
Sealed class factoryPembuatan dari input terstruktur (JSON, command)Pesan.dariJson("teks:Halo")

Companion object adalah singleton per class sehingga cocok menjadi wadah factory method statik. Generic interface memungkinkan beberapa class berbagi kontrak factory yang sama. Sealed class dengan factory di companion memanfaatkan when exhaustive untuk parsing input terstruktur.

Implementasi Tiga Varian #

kotlin
// Demo Factory Pattern: companion object factory, interface Factory<T>, sealed class factory.

// === 1. Companion object sebagai factory ===
interface Notifikasi {
    fun kirim(pesan: String): String
    val jenis: String
}

class EmailNotifikasi(private val tujuan: String) : Notifikasi {
    override fun kirim(pesan: String) = "[email] ke $tujuan: $pesan"
    override val jenis = "EMAIL"
}

class SmsNotifikasi(private val nomor: String) : Notifikasi {
    override fun kirim(pesan: String) = "[sms] ke $nomor: $pesan"
    override val jenis = "SMS"
}

class NotifikasiFactory {
    companion object {
        fun email(tujuan: String) = EmailNotifikasi(tujuan)
        fun sms(nomor: String) = SmsNotifikasi(nomor)

        fun dariJenis(jenis: String, target: String): Notifikasi = when (jenis.uppercase()) {
            "EMAIL" -> email(target)
            "SMS" -> sms(target)
            else -> throw IllegalArgumentException("Jenis tidak dikenal: $jenis")
        }
    }
}

// === 2. Generic interface Factory<T> ===
interface Factory<T> {
    fun create(): T
}

class Kucing(val nama: String) {
    companion object : Factory<Kucing> {
        private var counter = 0
        override fun create(): Kucing {
            counter++
            return Kucing("Kucing-$counter")
        }
    }
}

class Mobil(val merk: String) {
    companion object : Factory<Mobil> {
        override fun create(): Mobil = Mobil("Toyota")
    }
}

// === 3. Sealed class factory ===
sealed class Pesan {
    data class PesanTeks(val isi: String) : Pesan()
    data class PesanAudio(val durasi: Int) : Pesan()
    data class PesanGambar(val url: String, val lebar: Int, val tinggi: Int) : Pesan()

    companion object {
        fun dariJson(json: String): Pesan {
            // Parser sederhana (asumsi format "teks:isi" / "audio:60" / "gambar:url,800,600")
            val parts = json.split(":")
            return when (parts[0]) {
                "teks" -> PesanTeks(parts.getOrNull(1) ?: "")
                "audio" -> PesanAudio((parts.getOrNull(1) ?: "0").toIntOrNull() ?: 0)
                "gambar" -> {
                    val g = parts.getOrNull(1)?.split(",") ?: return PesanGambar("", 0, 0)
                    PesanGambar(g.getOrNull(0) ?: "", g.getOrNull(1)?.toIntOrNull() ?: 0, g.getOrNull(2)?.toIntOrNull() ?: 0)
                }
                else -> PesanTeks(json)
            }
        }
    }
}

fun main() {
    println("=== Demo Factory Pattern ===")

    println("--- 1. Companion object factory ---")
    val n1 = NotifikasiFactory.email("[email protected]")
    val n2 = NotifikasiFactory.sms("081200001111")
    println(n1.kirim("Halo Budi"))
    println(n2.kirim("OTP: 123456"))

    println()
    println("--- 1b. Factory dari jenis ---")
    val n3 = NotifikasiFactory.dariJenis("EMAIL", "[email protected]")
    println("${n3.jenis}: ${n3.kirim("Halo Siti")}")

    println()
    println("--- 2. Generic Factory<T> interface ---")
    val k1 = Kucing.create()
    val k2 = Kucing.create()
    println("Kucing 1: ${k1.nama}")
    println("Kucing 2: ${k2.nama}")
    val m = Mobil.create()
    println("Mobil: ${m.merk}")

    println()
    println("--- 3. Sealed class factory (dari JSON) ---")
    val pesan1 = Pesan.dariJson("teks:Halo dari Budi")
    val pesan2 = Pesan.dariJson("audio:60")
    val pesan3 = Pesan.dariJson("gambar:example.com/gambar.jpg,800,600")
    println("Pesan 1: $pesan1")
    println("Pesan 2: $pesan2")
    println("Pesan 3: $pesan3")
}

Output yang Dihasilkan:

text
=== Demo Factory Pattern ===
--- 1. Companion object factory ---
[email] ke [email protected]: Halo Budi
[sms] ke 081200001111: OTP: 123456

--- 1b. Factory dari jenis ---
EMAIL: [email] ke [email protected]: Halo Siti

--- 2. Generic Factory<T> interface ---
Kucing 1: Kucing-1
Kucing 2: Kucing-2
Mobil: Toyota

--- 3. Sealed class factory (dari JSON) ---
Pesan 1: PesanTeks(isi=Halo dari Budi)
Pesan 2: PesanAudio(durasi=60)
Pesan 3: PesanGambar(url=example.com/gambar.jpg, lebar=800, tinggi=600)

Bedah Kode Baris-per-Baris #

Mari kita amati bagian kunci:

  1. Baris 19-30 (NotifikasiFactory): Companion object berisi beberapa factory method. Method email dan sms adalah constructor yang diberi nama lebih ekspresif. Method dariJenis memutuskan tipe concrete berdasarkan string input.
  2. Baris 33-35 (interface Factory<T>): Generic interface dengan satu method create. Setiap class yang ingin punya factory tinggal mengimplementasi interface ini di companion object-nya.
  3. Baris 37-45 (Kucing): Companion object mengimplementasi Factory<Kucing>. Method create memakai counter statik untuk menghasilkan nama unik. Pola ini cocok untuk entity yang membutuhkan ID berurutan.
  4. Baris 53-73 (sealed class Pesan): Sealed class dengan tiga sub-class. Factory dariJson di companion memakai when untuk memilih sub-class yang tepat berdasarkan prefix string. Karena when exhaustive pada sealed class, kompilator memastikan semua kasus tertangani.
  5. Baris 78 (val n3 = NotifikasiFactory.dariJenis("EMAIL", "[email protected]")): Klien tidak tahu apakah akan menerima EmailNotifikasi atau SmsNotifikasi; ia hanya berinteraksi melalui interface Notifikasi.

Sealed Class sebagai Discriminated Union #

Sealed class di Kotlin berfungsi sebagai discriminated union: tipe yang hanya bisa berupa salah satu dari sejumlah sub-class yang sudah dideklarasikan. Saat dipakai dengan when, kompilator memaksa semua cabang ditangani. Hal ini membuat factory berbasis sealed class aman terhadap penambahan sub-class baru; lupa menambah cabang akan menghasilkan galat kompilasi.

Penutup #

Factory Pattern memberi satu titik perubahan untuk proses pembuatan object. Kotlin menyediakan tiga varian idiomatic yang masing-masing cocok untuk situasi berbeda. Pada chapter berikutnya kita akan membahas Builder Pattern: bagaimana Kotlin data class dengan default parameter menggantikan builder tradisional Java.

Referensi #

Referensi Lanjutan #

  • Kotlin documentation home — halaman utama dokumentasi Kotlin untuk companion object dan invoke sebagai idiom factory pattern