Kapan Pakai Pattern Apa — Design Patterns

Mengetahui design pattern itu bagus. Tapi yang lebih penting adalah tahu kapan menggunakannya — dan kapan tidak perlu. Panduan cepat — pilih pattern…

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:

  1. Tulis kode paling sederhana yang jalan.
  2. Tunggu rasa sakitnya muncul. Biasanya saat perubahan kedua: layanan kedua, pihak kedua yang perlu dikabari, file kedua yang menyalin kode yang sama.
  3. Sebut rasa sakitnya dalam satu kalimat: "tiap layanan baru, tiga file harus diubah".
  4. Pilih pattern yang menyembuhkan rasa sakit itu.
  5. 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:

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

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).

Yang akan kamu pelajari