Abstract Factory adalah satu object yang membuat sekeluarga object yang harus cocok satu sama lain. Pemanggilnya memilih keluarganya sekali, lalu semua bagian datang dari keluarga yang sama, jadi gak mungkin tercampur. Bayangin Kurir Kancil bekerja sama dengan dua ekspedisi mitra. Tiap mitra memberi satu map: stiker resi mereka, cara melacak paket mereka, dan tabel tarif mereka. Kamu ambil satu map, dan isinya pasti cocok. Stiker Merpati yang dilacak di sistem Jalak gak akan ketemu. Abstract factory adalah map itu di dalam kode.
🧪 Tebakan cepat: Kurir Kancil pindah dari mitra Merpati ke mitra Jalak. Label, pelacakan, dan tarif semuanya dibuat lewat satu
partnerKit. Berapa baris di checkout yang harus diubah? Tebak dulu, jawabannya ada di bagian "Abstract factory: satu kit per mitra".
Masalahnya: tiap bagian dipilih sendiri-sendiri
Paket yang diteruskan ke mitra butuh tiga hal: label (nomor resi format mitra), tracker (cara melacak status), dan pricer (tarif yang ditagih mitra). Kalau tiap file memilih mitranya sendiri, cepat atau lambat ada yang beda:
// checkout.js: label masih Merpati
const label = merpatiLabel(42, 2.5) // { resi: "MTR-0042", kg: 2.5 }
// lacak.js: sudah diganti ke Jalak
console.log(jalakTracker().track(label.resi))
// Jalak: resi MTR-0042 tidak dikenal
// tagihan.js: juga Jalak
console.log(jalakPricer().price(label.kg))
// 20000, padahal Merpati menagih 27000
Pelanggan gak bisa melacak paketnya, dan Kurir Kancil menagih Rp 20.000 untuk paket yang ditagih mitra Rp 27.000. Rugi Rp 7.000 per paket, tanpa satu error pun.
Abstract factory: satu kit per mitra
const PARTNERS = {
merpati: {
createLabel: (n, kg) => ({ resi: "MTR-" + String(n).padStart(4, "0"), kg }),
createTracker: () => ({
track: (resi) => resi.startsWith("MTR-") ? "Merpati: " + resi + " transit" : "Merpati: resi " + resi + " tidak dikenal",
}),
createPricer: () => ({ price: (kg) => Math.ceil(kg) * 9000 }),
},
jalak: {
createLabel: (n, kg) => ({ resi: "JLK" + String(n).padStart(6, "0"), kg }),
createTracker: () => ({
track: (resi) => resi.startsWith("JLK") ? "Jalak: " + resi + " transit" : "Jalak: resi " + resi + " tidak dikenal",
}),
createPricer: () => ({ price: (kg) => Math.max(kg, 1) * 8000 }),
},
}
function partnerKit(name) {
if (!Object.hasOwn(PARTNERS, name)) throw new Error("Unknown partner")
return PARTNERS[name]
}
const kit = partnerKit("merpati") // satu-satunya tempat mitra dipilih
const label = kit.createLabel(42, 2.5)
console.log(label.resi) // MTR-0042
console.log(kit.createTracker().track(label.resi)) // Merpati: MTR-0042 transit
console.log(kit.createPricer().price(label.kg)) // 27000
- Tiap mitra adalah satu object dengan tiga method pembuat yang namanya sama:
createLabel,createTracker,createPricer. Itu kontraknya. Isinya beda per mitra. partnerKit(name)mengembalikan kit satu mitra, dan menolak nama yang gak ada denganUnknown partner.- Sesudah itu kode cuma bicara dengan
kit. Dia gak tahu sedang memakai mitra yang mana, jadi gak bisa mengambil label dari satu mitra dan tracker dari mitra lain.
Hasilnya untuk paket nomor 42 seberat 2,5 kg:
partnerKit("merpati") |
partnerKit("jalak") |
|
|---|---|---|
resi |
MTR-0042 |
JLK000042 |
track(resi) |
Merpati: MTR-0042 transit |
Jalak: JLK000042 transit |
price(2.5) |
Math.ceil(2.5) * 9000 = 27000 |
Math.max(2.5, 1) * 8000 = 20000 |
Pindah ke Jalak cukup mengganti partnerKit("merpati") jadi partnerKit("jalak"): 1 baris. Ketiga bagian ikut pindah bersama. Itu jawaban tebakannya.
Factory biasa vs abstract factory
- Factory (lesson sebelumnya) membuat satu jenis produk dalam beberapa varian:
createShipment(service, kg)selalu membuat kiriman, entah reguler atau kilat. Bentuk satu-method-satu-produk ini sering disebut Factory Method. - Abstract factory membuat keluarga produk yang saling berkaitan: label, tracker, dan pricer dari mitra yang sama. Yang dijamin bukan cuma "object-nya jadi", tapi "semua object-nya sekeluarga".
Contoh lain yang sering ketemu: tema UI terang dan gelap. Tiap tema punya Button, Input, dan Card. Satu factory per tema memastikan gak ada Button gelap di sebelah Input terang dalam satu form.
Latihan nanti memakai bentuk itu. createThemeFactory(theme) mengembalikan object { createButton, createInput }:
"light" |
"dark" |
|
|---|---|---|
createButton(label) |
{ kind: "button", label, bg: "#fff", color: "#000" } |
{ kind: "button", label, bg: "#000", color: "#fff" } |
createInput(placeholder) |
{ kind: "input", placeholder, bg: "#fff", border: "#ccc" } |
{ kind: "input", placeholder, bg: "#111", border: "#444" } |
Tema lain langsung ditolak dengan throw new Error("Unknown theme"), sebelum ada komponen yang dibuat.
Kapan gak perlu abstract factory
- Cuma ada satu keluarga. Kalau Kurir Kancil cuma bermitra dengan Merpati, panggil function Merpati langsung.
- Bagian-bagiannya boleh dicampur. Kalau label dan tarif gak saling bergantung, cukup factory biasa atau function biasa untuk masing-masing.
- Produknya sering bertambah. 2 mitra × 3 produk = 6 function. Mitra ketiga gampang: 3 function baru di satu tempat. Tapi produk keempat, misalnya
createInsurance, harus ditulis di setiap mitra. Lupa satu, dan kit itu berhenti denganTypeError: kit.createInsurance is not a function.
⚠️ Jebakan yang sering ketemu
- Melewati kit. Satu file menulis
PARTNERS.jalak.createTracker()langsung, dan pencampuran kembali. Pilih kit sekali di satu tempat, lalu operkit-nya ke yang butuh. - Produk baru cuma ditambah di satu keluarga. Tiap keluarga harus punya method yang sama persis.
- Nama keluarga yang gak dikenal diam-diam jadi
undefined. Tolak di factory dengan pesan yang jelas, sepertiUnknown theme. - Class bertingkat yang gak perlu. Di JavaScript, object berisi function sudah cukup jadi factory.
abstract class UIFactorydengan subclass per tema baru berguna kalau timmu memang memakai class.
TL;DR: Abstract Factory membuat keluarga object yang harus cocok satu sama lain (label, tracker, dan tarif dari mitra yang sama; Button dan Input dari tema yang sama). Tiap keluarga punya method pembuat yang namanya sama, kode memilih keluarganya sekali (partnerKit("merpati"), createThemeFactory("dark")), lalu cuma bicara dengan kit itu, jadi gak bisa mencampur. Pindah keluarga cukup 1 baris. Bedanya dengan factory biasa: factory membuat satu jenis produk, abstract factory membuat sekeluarga produk. Harganya: produk baru harus ditulis di setiap keluarga. Nama yang gak dikenal ditolak dengan throw new Error("Unknown theme").