Abstract Factory Pattern — Design Patterns

Abstract Factory membuat keluarga object yang saling berkaitan tanpa menyebutkan class konkretnya. Cocok saat kamu punya beberapa "varian" produk yang harus…

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

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

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

⚠️ Jebakan yang sering ketemu

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

Yang akan kamu pelajari