Facade Pattern memberi satu pintu yang sederhana di depan beberapa bagian (subsystem) yang harus dipanggil dengan urutan tertentu. Pemanggil cukup memanggil satu function, dan facade yang mengatur urutannya di belakang. Bayangin resepsionis hotel. Kamu bilang "check-in, dua malam". Resepsionis yang mengecek reservasi, mencatat KTP, menyiapkan kartu kamar, dan mengabari housekeeping. Kamu gak perlu tahu ada empat bagian, apalagi urutannya. Tapi resepsionis gak menentukan harga kamar: dia cuma menyampaikan harga dari bagian reservasi.
🧪 Tebakan cepat: aturan baru "member gold dapat potongan 15%" ditaruh di dalam facade
KurirKancil.kirim. Halaman cek ongkir tetap memanggilhitungOngkir("kilat", 2)langsung. Untuk member gold, berapa harga yang tampil di cek ongkir, dan berapa yang ditagih saat checkout? Tebak dulu, jawabannya ada di bagian "Facade yang tumbuh jadi god object".
Masalahnya: tiap pemanggil harus tahu empat langkah
Mengirim paket di Kurir Kancil melibatkan subsystem dari lesson-lesson sebelumnya: hitungOngkir dan PROMO (Strategy), resiCounter (Singleton), dan tracker (Observer). Halaman checkout, aplikasi, dan panel admin masing-masing menulis langkah yang sama:
// checkout.js, aplikasi.js, dan admin.js menulis ini masing-masing
const harga = PROMO.diskon10(hitungOngkir("kilat", 2))
const resi = resiCounter.next()
const parcel = { resi, service: "kilat", kg: 2, harga, status: "dijemput" }
tracker.emit("status", parcel) // supaya muncul di halaman lacak
Tiga salinan dari empat langkah. Salinan di admin lupa baris tracker.emit? Kiriman KCL-0003 tetap dibuat dan ditagih, tapi halaman lacak menjawab "tidak ditemukan", tanpa error. Ada langkah baru, misalnya mendaftarkan tiap kiriman ke mitra asuransi? Tiga file diubah, dan satu pasti ketinggalan.
Facade: satu pintu untuk mengirim paket
const KurirKancil = {
kirim(service, kg, promo = "tanpa") {
const harga = PROMO[promo](hitungOngkir(service, kg)) // dicek dulu, bisa throw
const parcel = { resi: resiCounter.next(), service, kg, harga, status: "dijemput" }
tracker.emit("status", parcel)
return parcel
},
}
// checkout.js, aplikasi.js, admin.js cukup menulis:
const p = KurirKancil.kirim("kilat", 2, "diskon10")
console.log(p.resi + " Rp " + p.harga) // KCL-0001 Rp 32400
| tanpa facade | dengan facade | |
|---|---|---|
| yang harus dikenal pemanggil | hitungOngkir, PROMO, resiCounter, tracker, dan urutannya |
KurirKancil.kirim |
| langkah baru (asuransi) | diubah di 3 file | 1 baris di kirim |
| langkah yang bisa terlupa | di tiap salinan | gak ada, cuma satu salinan |
kirimcuma mengatur urutan: hitung harga, ambil resi, kabari tracker. Rumus harga tetap diPRICINGdanPROMO, nomor resi tetap diresiCounter.promo = "tanpa"adalah nilai bawaan parameter: kalau pemanggil gak mengirim promo, yang dipakai"tanpa".- Harga dihitung sebelum resi diambil. Layanan yang gak ada (
"ekspres") melemparUnknown servicelebih dulu, jadi gak ada nomor resi yang terbuang. - Subsystem-nya tetap bisa dipakai langsung. Halaman cek ongkir yang cuma butuh harga tetap memanggil
hitungOngkir.
Kamu sudah sering memakai facade: $("#resi").addClass("aktif") di jQuery menyembunyikan detail DOM di balik satu panggilan, fetch(url) menyembunyikan DNS, koneksi, dan header HTTP, dan Laravel bahkan menamai fiturnya Facades: Cache::get("key") atau Mail::to($user)->send(...) adalah satu nama pendek untuk layanan yang di belakangnya punya driver dan konfigurasi.
Facade yang tumbuh jadi god object
Facade gampang jadi tempat menaruh apa saja, karena semua orang sudah memanggilnya. Misalnya aturan member gold:
kirim(service, kg, promo = "tanpa", member = "biasa") {
let harga = PROMO[promo](hitungOngkir(service, kg))
if (member === "gold") harga = harga * 0.85 // aturan harga di dalam facade
// ...
}
Halaman cek ongkir memanggil hitungOngkir("kilat", 2) dan menampilkan Rp 36.000. Saat checkout, member gold ditagih 36.000 × 0,85 = Rp 30.600. Itu jawaban tebakannya: dua harga untuk barang yang sama, karena aturannya disembunyikan di tempat yang gak dilewati semua jalur. Tempatnya di PROMO, misalnya PROMO.gold = (price) => price * 0.85, yang juga bisa dipanggil halaman cek ongkir.
Setahun kemudian, facade seperti ini bisa punya 30 method: kirim, refund, gaji kurir, laporan bulanan, email promo. Itu disebut god object: satu object yang tahu dan mengerjakan semuanya. Tiap tim mengubah file yang sama, dan tiap test checkout harus menyiapkan semua subsystem. Facade yang sehat tetap tipis: cuma mengatur urutan, tanpa aturan bisnis sendiri. Kalau mulai gemuk, pecah per bidang: Pengiriman, Laporan, Penggajian.
Facade, adapter, dan decorator
- Adapter menerjemahkan satu bentuk ke bentuk lain:
adaptMerpatimengubah{ awb, weightGrams }jadi{ resi, kg }. - Decorator membungkus satu function dengan cara panggil yang sama, lalu menambah sesuatu:
withAsuransi(hitungOngkir). - Facade menyembunyikan banyak bagian di balik satu pintu yang lebih sederhana:
KurirKancil.kirim.
Latihan: VideoConverter
Latihan nanti memakai bentuk yang sama untuk konversi video. Starter sudah berisi tiga subsystem: readFile(path) mengembalikan "raw:" + path, decode(raw) mengembalikan "decoded:" + raw, dan encode(decoded, format) mengembalikan "encoded:" + decoded + ":" + format. Kamu mengisi VideoConverter.convert(path, format) supaya memanggil ketiganya berurutan, lalu mengembalikan hasil encode:
| langkah | hasil untuk convert("movie.avi", "mp4") |
|---|---|
readFile("movie.avi") |
raw:movie.avi |
decode(...) |
decoded:raw:movie.avi |
encode(..., "mp4") |
encoded:decoded:raw:movie.avi:mp4 |
Console-nya jadi dua baris: encoded:decoded:raw:movie.avi:mp4 dan encoded:decoded:raw:clip.mov:webm. Untuk convert("intro.mkv", "ogg"), hasilnya encoded:decoded:raw:intro.mkv:ogg.
Kapan gak perlu facade
- Subsystem-nya sudah sederhana. Kalau mengirim paket cuma satu function, facade di depannya cuma lapisan kosong.
- Pemanggil butuh tiap langkah. Laporan keuangan butuh harga sebelum dan sesudah promo secara terpisah. Biarkan dia memanggil subsystem langsung.
- Pemanggilnya cuma satu dan gak akan bertambah. Urutan yang ditulis sekali gak perlu disembunyikan.
⚠️ Jebakan yang sering ketemu
- Aturan bisnis di dalam facade. Jalur yang memanggil subsystem langsung gak ikut aturannya, dan pelanggan melihat dua harga.
- Facade yang menelan semuanya. 30 method di satu object adalah god object. Pecah per bidang.
- Urutan langkah yang salah di dalam facade. Mengambil resi sebelum harga dicek membuang satu nomor resi tiap kali layanan ditolak.
- Lupa
return.convertyang memanggil ketiga langkah tanpareturnmembuatconsole.logmencetakundefined.
TL;DR: Facade memberi satu pintu sederhana di depan beberapa subsystem yang harus dipanggil berurutan. KurirKancil.kirim(service, kg, promo) menghitung harga, mengambil resi, dan mengabari tracker, jadi checkout, aplikasi, dan admin gak perlu menyalin empat langkah (dan gak bisa lupa satu). Facade cuma mengatur urutan: rumus tetap di subsystem, dan subsystem tetap boleh dipanggil langsung. Aturan bisnis di dalam facade membuat dua harga (cek ongkir Rp 36.000, ditagih Rp 30.600), dan facade yang menelan semuanya jadi god object. Adapter menerjemahkan satu bentuk, decorator membungkus dengan cara panggil yang sama, facade menyederhanakan banyak bagian. Di latihan: return encode(decode(readFile(path)), format).