Factory Pattern menaruh pembuatan object di satu function (disebut factory) yang memilih jenis object dari data yang diberikan, jadi pemanggilnya cukup bilang jenisnya tanpa tahu cara membuatnya. Bayangin loket ekspedisi. Kamu cukup bilang "kilat, 2 kilo". Petugas loket yang tahu tarifnya, estimasinya, dan stiker apa yang ditempel. Kamu gak perlu hafal tabel tarif. Factory adalah petugas loket itu di dalam kode.
🧪 Tebakan cepat: Kurir Kancil sudah membuat semua kiriman lewat
createShipmentyang membaca objectSERVICES. Layanan barusamedayditambahkan sebagai satu entri diSERVICES. Berapa file pemanggil (checkout, admin, API) yang harus ikut diubah? Tebak dulu, jawabannya ada di bagian "Versi lookup: satu entri per layanan".
Masalahnya: if yang disalin ke tiap pemanggil
Kurir Kancil membuat object kiriman di tiga tempat: halaman checkout, halaman admin, dan API untuk toko online. Tiap tempat menulis sendiri cabang per layanan:
// checkout.js (admin.js dan api.js punya salinan yang sama)
let shipment
if (service === "reguler") {
shipment = { service: "reguler", kg, price: kg * 10000, eta: "3 hari" }
} else if (service === "kilat") {
shipment = { service: "kilat", kg, price: kg * 18000, eta: "1 hari" }
}
Waktu layanan kargo datang, tiga file harus diubah. Satu terlupa, dan pesanan kargo dari file itu berhenti dengan TypeError: Cannot read properties of undefined (reading 'price'), karena shipment gak pernah diisi.
Factory function: satu tempat yang membuat object
function createShipment(service, kg) {
let shipment
if (service === "reguler") {
shipment = { service: "reguler", kg, price: kg * 10000, eta: "3 hari" }
} else if (service === "kilat") {
shipment = { service: "kilat", kg, price: kg * 18000, eta: "1 hari" }
} else if (service === "kargo") {
shipment = { service: "kargo", kg, price: Math.max(kg, 10) * 6000, eta: "5 hari" }
} else {
throw new Error("Unknown service")
}
shipment.describe = function () {
return this.service + " " + this.kg + " kg: Rp " + this.price
}
return shipment
}
console.log(createShipment("kilat", 2).describe()) // kilat 2 kg: Rp 36000
console.log(createShipment("kargo", 4).describe()) // kargo 4 kg: Rp 60000
- Tiap layanan punya rumusnya sendiri. Kargo ditagih minimal 10 kg:
Math.max(kg, 10)memilih yang lebih besar, jadi 4 kg tetap ditagih 10 kg. - Jenis yang gak dikenal ditolak dengan
throw new Error("Unknown service"). Pemanggil yang salah ketik langsung tahu, bukan dapatundefinedyang meledak jauh di tempat lain. describeditambahkan sekali, ke object apa pun yang dibuat. Saat dipanggil sebagaishipment.describe(),thisadalah object sebelum titik, jadithis.serviceterbaca.- Checkout, admin, dan API sekarang cuma menulis
createShipment(service, kg). Mereka gak tahu cabang mana yang dipilih: jenis object ditentukan saat program berjalan, dari data seperti pilihan layanan di form. Itu keuntungan utama factory dibanding menulisnewatau object literal sendiri di tiap tempat.
Latihan nanti memakai bentuk yang sama dengan bangun datar: createShape(type, size) membuat lingkaran, persegi, atau segitiga, masing-masing dengan rumus luasnya, plus describe(). Untuk lingkaran, luasnya dibulatkan dengan toFixed(2): (Math.PI * 5 * 5).toFixed(2) menghasilkan teks "78.54", dua angka di belakang titik.
Versi lookup: satu entri per layanan
Rantai if masih harus diubah tiap ada layanan baru. Versi yang lebih ringkas menyimpan cara membuat tiap jenis di sebuah object, lalu memilih dengan kuncinya:
const SERVICES = {
reguler: (kg) => ({ service: "reguler", kg, price: kg * 10000, eta: "3 hari" }),
kilat: (kg) => ({ service: "kilat", kg, price: kg * 18000, eta: "1 hari" }),
kargo: (kg) => ({ service: "kargo", kg, price: Math.max(kg, 10) * 6000, eta: "5 hari" }),
sameday: (kg) => ({ service: "sameday", kg, price: kg * 25000, eta: "hari ini" }), // baru
}
function createShipment(service, kg) {
if (!Object.hasOwn(SERVICES, service)) throw new Error("Unknown service")
return SERVICES[service](kg)
}
SERVICES[service]mengambil function pembuat dari object, sesuai kuncinya.(kg)sesudahnya langsung memanggil function itu.Object.hasOwn(SERVICES, service)mengecek kuncinya benar-benar ditulis diSERVICES. Cekif (!SERVICES[service])saja kurang aman: tiap object biasa mewarisitoString, jadicreateShipment("toString", 2)lolos dan mengembalikan teks"[object Object]".- Layanan baru cukup satu baris di
SERVICES. Checkout, admin, dan API gak diubah sama sekali: 0 file. Itu jawaban tebakannya.
Kapan gak perlu factory
- Cuma ada satu jenis. Kalau Kurir Kancil hanya punya layanan reguler,
createShipment("reguler", kg)cuma menambah parameter yang gak pernah berubah. Tulis object-nya langsung. - Membuat object-nya sepele dan sama di mana-mana. Titik koordinat
{ lat, lng }gak butuh factory. - Jenisnya gak pernah dipilih dari data. Kalau tiap tempat selalu tahu persis object apa yang dibuat, panggil langsung saja.
⚠️ Jebakan yang sering ketemu
- Jenis tak dikenal diam-diam jadi
undefined. Factory yang lupathrowmembuat error muncul jauh dari penyebabnya. Tolak di factory dengan pesan yang jelas, misalnya"Unknown shape". describeditulis sebagai arrow function. Arrow function gak punyathissendiri, jadithis.typedi dalamnya gak membaca object yang dibuat. Pakaifunction () { ... }.- Huruf besar-kecil.
createShipment("Kilat", 2)ditolak karena kuncinyakilat. Rapikan input di pintu masuk denganservice.toLowerCase(). - Lupa
toFixed. Luas lingkaran berjari-jari 5 tanpa pembulatan adalah78.53981633974483, bukan78.54.
TL;DR: Factory Pattern menyembunyikan detail pembuatan object di satu function, dan memilih jenis object saat program berjalan dari data yang diberikan. createShipment(service, kg) punya satu cabang per layanan dengan rumusnya sendiri, menambahkan describe() (pakai function, supaya this adalah object-nya), dan menolak jenis yang gak dikenal dengan throw new Error(...). Versi lookup menyimpan tiap jenis sebagai entri di object, lalu memanggil SERVICES[service](kg): layanan baru cukup satu baris, pemanggil gak berubah. Cek kunci dengan Object.hasOwn. Kalau cuma ada satu jenis, factory belum perlu.