Memilih pattern itu seperti mengambil obat dari kotak P3K: kamu sebut dulu gejalanya, baru ambil obat yang menyembuhkan gejala itu. Kamu gak minum obat batuk cuma karena obatnya ada di kotak. Dan untuk banyak keluhan, jawabannya bukan obat sama sekali, cukup istirahat. Di kode juga begitu: tiap pattern menyembuhkan satu rasa sakit tertentu, dan kalau rasa sakitnya gak ada, pattern-nya cuma menambah kode.
🧪 Tebakan cepat: Kurir Kancil butuh function yang mengubah angka 7 jadi
KCL-0007. Seorang developer membungkusnya jadi Singleton:ResiFormatter.getInstance().format(7). Rasa sakit apa yang disembuhkan Singleton ini? Tebak dulu, jawabannya ada di bagian "Kapan jawabannya: tanpa pattern".
Mulai dari rasa sakitnya, bukan dari pattern-nya
Urutan yang aman:
- Tulis kode paling sederhana yang jalan.
- Tunggu rasa sakitnya muncul. Biasanya saat perubahan kedua: layanan kedua, pihak kedua yang perlu dikabari, file kedua yang menyalin kode yang sama.
- Sebut rasa sakitnya dalam satu kalimat: "tiap layanan baru, tiga file harus diubah".
- Pilih pattern yang menyembuhkan rasa sakit itu.
- Cek hasilnya: kodenya jadi lebih mudah dibaca dan diubah? Kalau gak, kembalikan.
Prinsip di balik langkah 2 punya nama: YAGNI (You Aren't Gonna Need It), jangan bikin sesuatu untuk kebutuhan yang belum ada.
Peta: rasa sakit Kurir Kancil dan obatnya
Semua lesson sebelumnya mengikuti urutan itu. Ini rasa sakitnya, dan pattern yang menyembuhkannya:
| rasa sakit di Kurir Kancil | pattern |
|---|---|
lastResi bisa ditimpa file lain |
Module: variabel privat di closure atau ES module |
updateStatus diubah tiap ada pihak baru yang perlu dikabari |
Observer: tracker.on("status", fn) |
dua counter resi, dua KCL-0001 |
Singleton: satu resiCounter |
if per layanan disalin ke checkout, admin, dan API |
Factory: createShipment dan SERVICES |
| stiker Merpati dilacak di sistem Jalak | Abstract Factory: satu partnerKit per mitra |
new Shipment(...) dengan 8 argumen berurutan |
Builder: ShipmentBuilder |
switch tarif yang terus memanjang |
Strategy: PRICING[service](kg) |
| asuransi, packing kayu, diskon sebagai flag | Decorator: withAsuransi(hitungOngkir) |
data mitra datang sebagai { awb, weightGrams } |
Adapter: adaptMerpati |
| auth, rate limit, dan validasi di satu handler | Middleware: auth → rateLimit → validate → buatKiriman |
| perubahan status harus bisa di-undo | Command: ubahStatus dengan do dan undo |
| gudang harus kenal semua penerima kabar | Pub/Sub: broker dan topik |
| empat langkah kirim disalin di tiga tempat | Facade: KurirKancil.kirim |
| data, aturan, dan tampilan dicampur di handler klik | MVC atau MVVM |
api.get dan filter di tiap halaman, test butuh server |
Repository |
Baca tabelnya dari kiri: rasa sakitnya dulu. Kalau kode kamu gak punya rasa sakit di kolom kiri, pattern di kolom kanan belum dibutuhkan.
Kapan jawabannya: tanpa pattern
// Singleton untuk sesuatu yang gak menyimpan apa-apa
class ResiFormatter {
static instance = null
static getInstance() {
if (!ResiFormatter.instance) ResiFormatter.instance = new ResiFormatter()
return ResiFormatter.instance
}
format(n) {
return "KCL-" + String(n).padStart(4, "0")
}
}
console.log(ResiFormatter.getInstance().format(7)) // KCL-0007
// Function biasa: hasil sama, tanpa lapisan
function formatResi(n) {
return "KCL-" + String(n).padStart(4, "0")
}
console.log(formatResi(7)) // KCL-0007
Singleton menyembuhkan resiCounter karena dia menyimpan count yang harus cuma satu. formatResi gak menyimpan apa-apa: angka masuk, teks keluar. Gak ada state yang perlu dibuat satu, jadi Singleton-nya gak menyembuhkan apa-apa. Itu jawaban tebakannya. Function biasa sudah cukup.
Dua contoh lain dari track ini:
- Factory untuk satu jenis. Kalau Kurir Kancil cuma punya layanan reguler,
createShipment("reguler", kg)cuma menambah parameter yang gak pernah berubah. Tulis object-nya langsung:{ service: "reguler", kg, price: kg * 10000 }. - Strategy untuk angka. Kalau reguler, kilat, dan sameday cuma beda tarif per kg, cukup tabel data:
const RATE = { reguler: 10000, kilat: 18000, sameday: 25000 }, lalukg * RATE[service]. Function per layanan baru perlu saat rumusnya beda, seperti kargo yang minimal 10 kg.
Pattern yang mirip, beda niat
Beberapa pattern bentuk kodenya mirip, jadi pilih dari niatnya, bukan dari bentuknya.
Notifikasi: Strategy atau Observer? Kurir Kancil bisa mengirim kabar lewat email, SMS, dan push notification. Pertanyaan pentingnya: tiap kabar dikirim lewat satu saluran pilihan pelanggan, atau lewat semua saluran?
// Pelanggan memilih SATU saluran: Strategy, dipilih saat program berjalan
const KIRIM = {
sms: (p) => console.log("SMS ke " + p.nama + ": " + p.resi + " " + p.status),
email: (p) => console.log("Email ke " + p.nama + ": " + p.resi + " " + p.status),
push: (p) => console.log("Push ke " + p.nama + ": " + p.resi + " " + p.status),
}
function kabari(p) {
KIRIM[p.saluran](p)
}
kabari({ nama: "Rina", saluran: "email", resi: "KCL-0001", status: "diantar" })
// Email ke Rina: KCL-0001 diantar
Kalau ketiga fungsi itu didaftarkan sebagai listener Observer (tracker.on("status", ...) tiga kali), satu perubahan status mengirim tiga pesan, dan Rina menerima SMS dan push yang gak dia pilih. Aturannya: satu dipilih dari beberapa adalah Strategy. Semua yang mendaftar dikabari adalah Observer.
Satu koneksi database: Singleton. Koneksi database mahal dibuat, dan database membatasi jumlahnya (MySQL bawaannya menerima 151 koneksi). Kalau tiap query membuka koneksi baru, tiga query membuka tiga koneksi, dan saat ramai database mulai menolak. Buat koneksinya (atau satu pool koneksi) sekali, lalu pakai di seluruh aplikasi. Di JavaScript, object yang di-export dari module sudah singleton. Laravel melakukannya di Service Container: $this->app->singleton('db', ...) membuat semua bagian aplikasi mendapat database manager yang sama selama satu request.
Pasangan mirip lain yang perlu kamu bedakan: Observer dan Pub/Sub (Pub/Sub punya broker di tengah), Adapter dan Decorator (Adapter mengubah bentuk, Decorator mempertahankan bentuk dan menambah sesuatu), dan State dan Strategy di lesson berikutnya.
⚠️ Jebakan yang sering ketemu
- Pattern jaga-jaga. Factory untuk satu layanan, Singleton untuk function tanpa state. Kodenya bertambah, rasa sakitnya gak ada. Tunggu rasa sakit kedua.
- Memilih dari bentuk kodenya. Strategy, Observer, dan State sama-sama "object berisi function". Tanya niatnya: satu dipilih, semua dikabari, atau perilaku yang berubah mengikuti status?
- Menumpuk pattern untuk satu masalah. Factory yang membuat Builder yang memakai Singleton, untuk satu object kiriman. Satu rasa sakit, satu obat.
- Menamai dengan nama pattern.
ShipmentManagerFactorygak bilang apa-apa tentang masalahnya.createShipmentbilang.
TL;DR: Pilih pattern dari rasa sakitnya: tulis kode sederhana, tunggu rasa sakit kedua, sebut dalam satu kalimat, lalu ambil pattern yang menyembuhkan rasa sakit itu (peta Kurir Kancil: if per layanan disalin ke mana-mana → Factory, pihak baru yang dikabari → Observer, dua counter resi → Singleton, dan seterusnya). Sering jawabannya tanpa pattern: formatResi gak menyimpan state, jadi function biasa cukup, dan tarif yang cuma beda angka cukup tabel data (YAGNI). Pattern yang mirip dibedakan dari niatnya: satu saluran pilihan pelanggan adalah Strategy, semua saluran dikabari adalah Observer. Koneksi database yang dibuat sekali dan dipakai seluruh aplikasi adalah Singleton (di JavaScript, cukup module).