Strategy Pattern — Design Patterns

Strategy Pattern memungkinkan kamu memilih algoritma atau behavior saat runtime. Alih-alih hardcode satu cara, kamu menyediakan beberapa "strategi" yang bisa…

Strategy Pattern menaruh tiap cara menghitung (algoritma) di function-nya sendiri, dengan bentuk yang sama, lalu kode utama memilih atau menerima salah satunya saat program berjalan, tanpa tahu isinya. Bayangin kasir loket Kurir Kancil yang punya satu kartu rumus per layanan. Pelanggan bilang "kilat", kasir ambil kartu kilat dan menghitung. Layanan baru berarti satu kartu baru. Cara kerja kasirnya gak berubah.

🧪 Tebakan cepat: di switch tarif Kurir Kancil, case "kilat" lupa ditutup break. Berapa hasil hitungOngkir("kilat", 2)? Tarif kilat Rp 18.000/kg. Tebak dulu, jawabannya ada di bagian "Masalahnya: switch yang terus memanjang".

Masalahnya: switch yang terus memanjang

Semua tarif ada di satu function. Tiap layanan baru menambah satu case:

function hitungOngkir(service, kg) {
  let price
  switch (service) {
    case "reguler":
      price = kg * 10000
      break
    case "kilat":
      price = kg * 18000
      // break terlupa
    case "kargo":
      price = Math.max(kg, 10) * 6000
      break
  }
  return price
}

console.log(hitungOngkir("kilat", 2))   // 60000
console.log(hitungOngkir("ekspres", 2)) // undefined

Tanpa break, switch terus jalan ke case berikutnya. price sempat diisi 36000, lalu ditimpa rumus kargo: Math.max(2, 10) * 6000 = 60000. Pelanggan kilat membayar Rp 60.000, bukan Rp 36.000. Itu jawaban tebakannya. Layanan yang gak ada pun gak ditolak, cuma mengembalikan undefined.

Tiap layanan baru berarti membuka function yang sama lagi, di tengah rumus yang sudah benar.

Strategy: satu function per cara menghitung

const PRICING = {
  reguler: (kg) => kg * 10000,
  kilat: (kg) => kg * 18000,
  kargo: (kg) => Math.max(kg, 10) * 6000,
  sameday: (kg) => kg * 25000,
}

function hitungOngkir(service, kg) {
  if (!Object.hasOwn(PRICING, service)) throw new Error("Unknown service")
  const strategy = PRICING[service]
  return strategy(kg)
}

console.log(hitungOngkir("kilat", 2)) // 36000
service strategi yang terpilih hitungOngkir(service, 2)
"reguler" kg * 10000 20000
"kilat" kg * 18000 36000
"kargo" Math.max(kg, 10) * 6000 60000
"sameday" kg * 25000 50000

Strategy yang dikirim dari luar

Strategi gak harus dipilih dengan kunci. Function-nya bisa langsung dikirim sebagai argumen. Promo bulanan Kurir Kancil, misalnya:

const PROMO = {
  tanpa: (price) => price,
  diskon10: (price) => price * 0.9,
  potong20rb: (price) => Math.max(price - 20000, 0),
}

function checkout(service, kg, promo) {
  return promo(hitungOngkir(service, kg))
}

console.log(checkout("kilat", 2, PROMO.diskon10))   // 32400
console.log(checkout("kilat", 2, PROMO.potong20rb)) // 16000

Tim marketing mengganti promo, checkout gak disentuh. JavaScript sendiri memakai bentuk ini di array.sort(compareFn): cara mengurutkan sudah ada, cara membandingkan kamu kirim. Tanpa function pembanding, [25000, 9000, 18000].sort() membandingkan sebagai teks dan menghasilkan [18000, 25000, 9000]. Dengan .sort((a, b) => a - b), hasilnya [9000, 18000, 25000].

Strategy di kelompok Behavioral

Ingat tiga kelompok pattern dari lesson pertama: Creational (cara object dibuat), Structural (cara object disusun), Behavioral (cara object berkomunikasi dan berbagi tanggung jawab). Strategy masuk Behavioral: dia mengatur siapa yang mengerjakan perhitungan dan algoritma mana yang dijalankan, bukan membuat atau menyusun object.

Bentuk paling kecilnya: const strategies = { add: (a, b) => a + b, multiply: (a, b) => a * b }. strategies["multiply"] mengambil function perkalian, dan strategies["multiply"](3, 4) menghasilkan 12.

Latihan: formatPrice

Latihan nanti memakai strategi untuk format harga. Starter sudah berisi object formatters dengan kunci id, us, dan compact, plus function formatPrice(amount, locale) yang masih kosong:

Kapan gak perlu strategy

⚠️ Jebakan yang sering ketemu

TL;DR: Strategy menaruh tiap cara menghitung di function sendiri dengan bentuk yang sama, lalu memilihnya lewat kunci (PRICING[service](kg)) atau menerimanya sebagai argumen (checkout(service, kg, promo), array.sort(compareFn)). Gak ada switch yang memanjang dan gak ada break yang terlupa (lupa break membuat kilat 2 kg jadi 60000). Kunci yang gak ada ditolak dengan throw new Error(...). Strategy termasuk kelompok Behavioral. Kalau variannya cuma beda angka, pakai tabel data. Kalau cuma satu cara, tulis langsung.

Yang akan kamu pelajari