Toko daring Budi menjual tiga jenis produk digital. Ebook punya format unduhan, kursus punya daftar materi, dan webinar punya jadwal — semuanya produk, tetapi bentuk objectnya berbeda. Tanpa aturan main, logika pembuatan tersebar ke mana-mana: satu if di halaman katalog, satu lagi di halaman checkout, satu lagi di modul pengiriman. Setiap tempat memutuskan sendiri, dan lama-kelamaan satu jenis produk punya bentuk yang sedikit berbeda di setiap tempat.

Factory pattern menghentikan penyebaran itu dengan satu aturan: semua keputusan tentang cara membuat object dikumpulkan di satu function bernama factory (pabrik). Pemanggil cukup menyebut jenis yang diinginkan beserta detailnya; factory yang menjamin hasilnya konsisten. Chapter ini membangun factory untuk ketiga varian produk Budi, lalu menimbang kapan factory lebih tepat daripada class.

Aturan: Pemanggil Memilih, Factory Menyusun #

Sebelum menulis factory, periksa dulu apakah masalahnya benar membutuhkan. Tiga tanda yang paling sering muncul:

  • Literal object ditulis berulang — bentuk object produk yang sama disalin ke beberapa tempat dengan sedikit perbedaan, dan perbedaan itu tidak disengaja.
  • Validasi pembuatan diulang — beberapa tempat memeriksa hal yang sama sebelum membuat object: “formatnya valid?”, “jadwalnya terisi?” — padahal pemeriksaan itu seharusnya milik tempat pembuatannya.
  • Varian baru menyentuh banyak berkas — menambah satu jenis produk berarti mengubah halaman katalog, checkout, dan modul pengiriman sekaligus, karena setiap berkas punya if versinya sendiri.

Bila ketiganya muncul, logika pembuatan yang tersebar itu adalah satu keberatan yang belum punya rumah — dan factory adalah rumahnya.

Mekanismenya sederhana: factory menerima jenis (jenis) dan detail (detail), menyusun bagian dasar yang dimiliki semua varian, menambahkan bagian khusus sesuai jenis, lalu mengembalikan object yang sudah jadi. Pemanggil tidak pernah menulis object produk dengan tangan — ia selalu lewat pintu yang sama.

Pintu tunggal ini membawa tiga jaminan:

  • Konsisten — semua ebook, di mana pun dibuat, selalu punya format dan unduh(). Tidak ada ebook yang kehilangan method karena dibuat dengan cara berbeda.
  • Satu tempat berubah — bila ebook perlu field baru, hanya factory yang disunting; semua pemanggil ikut mendapatkannya.
  • Kesalahan lebih awal — jenis yang tidak dikenal ditolak di factory, bukan ditemukan rusak saat dipakai di ujung aplikasi.

Nama factory biasanya diawali buat (atau create bila memakai istilah Inggris): buatProdukDigital, buatPengguna, buatKoneksi. Nama ini sudah menjadi kebiasaan luas sehingga pembaca langsung mengerti bahwa function tersebut mengembalikan object.

Satu Factory, Tiga Varian #

Berkas berikut membangun ketiga varian dari satu factory, dengan bagian dasar bersama dan bagian khusus per jenis:

// pabrik-produk.js — satu factory untuk semua varian produk digital.
// Jalankan di terminal: node pabrik-produk.js

function buatProdukDigital(jenis, detail) {
  // Bagian dasar yang dimiliki semua varian.
  const dasar = {
    jenis,
    judul: detail.judul,
    harga: detail.harga,
    info() {
      return `${this.judul} (${this.jenis}) - Rp${this.harga}`;
    },
  };

  // Varian ditambahkan sesuai jenis yang diminta.
  if (jenis === "ebook") {
    return {
      ...dasar,
      format: detail.format ?? "PDF",
      unduh() {
        return `Mengunduh ${this.judul}.${this.format.toLowerCase()}`;
      },
    };
  }
  if (jenis === "kursus") {
    return {
      ...dasar,
      totalMateri: detail.totalMateri,
      aksesMateri(nomor) {
        return `Membuka materi ${nomor}/${this.totalMateri}: ${this.judul}`;
      },
    };
  }
  if (jenis === "webinar") {
    return {
      ...dasar,
      jadwal: detail.jadwal,
      jadwalSingkat() {
        return `${this.judul} berjadwal ${this.jadwal}`;
      },
    };
  }

  // Jenis tak dikenal ditolak di pintu masuk, bukan saat dipakai.
  throw new Error(`Jenis produk tidak dikenal: ${jenis}`);
}

const produk = [
  buatProdukDigital("ebook", { judul: "Panduan Git", harga: 60000, format: "EPUB" }),
  buatProdukDigital("kursus", { judul: "JavaScript Dasar", harga: 250000, totalMateri: 12 }),
  buatProdukDigital("webinar", { judul: "Karier Developer", harga: 150000, jadwal: "17 Agustus 2026" }),
];

for (const item of produk) {
  console.log(item.info());
}
console.log(produk[0].unduh());
console.log(produk[1].aksesMateri(3));
console.log(produk[2].jadwalSingkat());

try {
  buatProdukDigital("voucher", { judul: "Voucher Diskon", harga: 10000 });
} catch (error) {
  console.log(error.message);
}

Output yang Dihasilkan:

text
Panduan Git (ebook) - Rp60000
JavaScript Dasar (kursus) - Rp250000
Karier Developer (webinar) - Rp150000
Mengunduh Panduan Git.epub
Membuka materi 3/12: JavaScript Dasar
Karier Developer berjadwal 17 Agustus 2026
Jenis produk tidak dikenal: voucher

Mari kita telaah baris-baris pentingnya:

  1. Baris 6–13 (const dasar = { ... }): bagian dasar dikumpulkan sekali — jenis, judul, harga, dan info() yang dipakai semua varian. Perhatikan penulisan singkat jenis pada baris 7: nama properti sama dengan nama variabelnya.
  2. Baris 18 (...dasar,): setiap varian menyalin bagian dasar lalu menambahkan miliknya. Tidak ada pewarisan di sini; penyusunan object dengan spread adalah composition — object dibangun dari bagian-bagian.
  3. Baris 19 (format: detail.format ?? "PDF"): nilai bawaan lewat nullish coalescing membuat varian tetap lengkap meskipun pemanggil tidak mengirim format. Aturan bawaan ini hidup di factory, bukan di setiap pemanggil.
  4. Baris 20–22 (unduh() { ... }): method khusus ebook hanya ada pada object ebook. Kursus tidak punya unduh, webinar tidak punya unduh — bentuk tiap varian persis sebesar kebutuhannya, tidak ada method yang menunggu dipakai.
  5. Baris 44–45 (throw new Error(...)): jenis tak dikenal langsung ditolak. Tanpa baris ini, pemanggilan dengan jenis salah menghasilkan object setengah jadi yang baru rusak jauh dari tempat kesalahannya.
  6. Baris 61–64 (try { ... } catch): percobaan membuat varian “voucher” terbukti ditolak di pintu masuk, dengan pesan yang menyebut jenisnya — keluaran baris terakhir.

Factory vs Class #

Class dari section Object-Oriented JavaScript juga bisa membuat tiga varian ini — biasanya lewat subclass per jenis. Keduanya sah, dan pilihan bukan soal benar-salah melainkan soal bentuk masalahnya:

PertimbanganFactory functionClass
Varian berbeda bentuk dan methodCocok: tiap varian disusun sendiriPerlu hierarki subclass untuk tiap varian
Pembuatan dengan logika/nilai bawaanAlami: cukup function biasaPerlu constructor yang bertele-tele
Data dan method per instance banyak, satu bentuk tunggalTidak ada keunggulanLebih ringkas dengan class dan pewarisan
Pemakaian ulang bagian bersamaSpread/compositionextends dan super

Kaidah praktisnya: bila varian-varian itu satu bentuk yang sama dengan perilaku berbeda saat dijalankan, class dengan pewarisan cocok. Bila varian-varian itu bentuknya memang berbeda sejak awal — seperti ketiga produk di atas — factory dengan penyusunan object lebih langsung dan tidak menuntut pemahaman prototype untuk dibaca. Factory juga berdiri di atas function biasa, sehingga ia bisa dipakai di mana saja tanpa kata kunci new.

Sisi sebaliknya juga jujur untuk dicatat: object hasil factory menyimpan salinan methodnya di tiap instance, sementara class menaruh method di prototype yang dipakai bersama. Untuk object yang dibuat ribuan per detik dan puluhan method, class lebih hemat memori; untuk object aplikasi biasa yang dibuat sesekali, selisihnya tidak terasa. Factory juga bisa memakai closure untuk menyembunyikan data internal — bentuk yang dengan class ditulis sebagai private field — sehingga keduanya nyaris setara dalam daya ungkap; pilihannya lebih banyak soal keterbacaan tim daripada kemampuan bahasa.

Satu varian yang sering dilupakan: factory yang mengembalikan object dengan method punya cara menguji yang berbeda dari function biasa. Bila factory menerima bagian dunia luarnya — pengirim email, jam, sumber data — dari parameternya, tiap varian bisa diuji tanpa menyentuh bagian luar itu. Kebiasaan inilah yang dibahas chapter berikutnya: dependency injection.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

pabrik-produk.js

Unduh berkas
javascript
// pabrik-produk.js — satu factory untuk semua varian produk digital.
// Jalankan di terminal: node pabrik-produk.js

function buatProdukDigital(jenis, detail) {
  // Bagian dasar yang dimiliki semua varian.
  const dasar = {
    jenis,
    judul: detail.judul,
    harga: detail.harga,
    info() {
      return `${this.judul} (${this.jenis}) - Rp${this.harga}`;
    },
  };

  // Varian ditambahkan sesuai jenis yang diminta.
  if (jenis === "ebook") {
    return {
      ...dasar,
      format: detail.format ?? "PDF",
      unduh() {
        return `Mengunduh ${this.judul}.${this.format.toLowerCase()}`;
      },
    };
  }
  if (jenis === "kursus") {
    return {
      ...dasar,
      totalMateri: detail.totalMateri,
      aksesMateri(nomor) {
        return `Membuka materi ${nomor}/${this.totalMateri}: ${this.judul}`;
      },
    };
  }
  if (jenis === "webinar") {
    return {
      ...dasar,
      jadwal: detail.jadwal,
      jadwalSingkat() {
        return `${this.judul} berjadwal ${this.jadwal}`;
      },
    };
  }

  // Jenis tak dikenal ditolak di pintu masuk, bukan saat dipakai.
  throw new Error(`Jenis produk tidak dikenal: ${jenis}`);
}

const produk = [
  buatProdukDigital("ebook", { judul: "Panduan Git", harga: 60000, format: "EPUB" }),
  buatProdukDigital("kursus", { judul: "JavaScript Dasar", harga: 250000, totalMateri: 12 }),
  buatProdukDigital("webinar", { judul: "Karier Developer", harga: 150000, jadwal: "17 Agustus 2026" }),
];

for (const item of produk) {
  console.log(item.info());
}
console.log(produk[0].unduh());
console.log(produk[1].aksesMateri(3));
console.log(produk[2].jadwalSingkat());

try {
  buatProdukDigital("voucher", { judul: "Voucher Diskon", harga: 10000 });
} catch (error) {
  console.log(error.message);
}