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
switchtarif Kurir Kancil,case "kilat"lupa ditutupbreak. Berapa hasilhitungOngkir("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 |
- Tiap strategi punya bentuk yang sama: menerima
kg, mengembalikan angka. Itu yang membuatnya bisa ditukar. PRICING[service]mengambil function lewat kuncinya, danstrategy(kg)memanggilnya.hitungOngkirgak tahu rumus mana yang jalan.- Gak ada
breakyang bisa terlupa. Layanan baru cukup satu baris diPRICING. - Tekniknya mirip lookup
SERVICESdi Factory, tapi tugasnya beda: factory membuat object, strategy memilih cara mengerjakan sesuatu.
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:
id:"Rp " + amount.toLocaleString("id-ID"). Untuk 1500000 hasilnyaRp 1.500.000.us:"quot; + amount.toLocaleString("en-US"). Hasilnya$1,500,000.compact: kalauamount >= 1000000,amount / 1000000 + "jt", jadi1.5jt. Kalauamount >= 1000,amount / 1000 + "rb", jadi 2500 menjadi2.5rb. Selain ituString(amount), jadi500. JavaScript menulis desimal dengan titik, jadi1.5jt, bukan1,5jt.formatPricemengambil strategi dariformatters[locale]. Kalau gak ada,throw new Error("Unknown locale").
Kapan gak perlu strategy
- Cuma ada satu cara.
const ongkir = kg * 10000sudah cukup. Object berisi satu function cuma menambah lapisan. - Variannya cuma beda angka. Kalau semua layanan sekadar
kg * tarif, simpan tarifnya sebagai data:const RATE = { reguler: 10000, kilat: 18000 }, lalukg * RATE[service]. Function baru perlu saat rumusnya beda, seperti kargo dengan minimal 10 kg. - Dua cabang yang gak akan bertambah.
if/elsebiasa lebih mudah dibaca.
⚠️ Jebakan yang sering ketemu
PRICING.service(kg)alih-alihPRICING[service](kg). Titik membaca kunci yang benar-benar bernamaservice, jadi hasilnyaTypeError: PRICING.service is not a function. Kunci dari variabel butuh kurung siku.- Kunci yang gak ada.
PRICING["ekspres"](2)berhenti denganTypeError: PRICING[service] is not a function. Tolak lebih dulu dengan pesan yang jelas, sepertiUnknown localedi latihan. - Bentuk strategi yang beda-beda. Kalau satu menerima
(kg)dan yang lain(service, kg), pemanggilnya harus tahu yang mana, dan strateginya gak bisa ditukar lagi. - Urutan cek di
compact. Kalau>= 1000dicek duluan, 1500000 jadi1500rb. Cek yang terbesar dulu.
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.