Adapter Pattern adalah function (atau object) kecil yang menerjemahkan bentuk data atau method milik pihak lain ke bentuk yang dipakai kodemu, tanpa mengubah keduanya. Bayangin charger laptop yang kamu beli di luar negeri. Kakinya gak cocok dengan stop kontak di rumah. Kamu gak membongkar charger-nya dan gak mengganti stop kontaknya. Kamu pasang adaptor colokan di antaranya. Listriknya sama, cuma bentuk sambungannya yang diterjemahkan.
🧪 Tebakan cepat: kurir mitra Merpati mengirim data paket
{ awb: "MTR-0088", weightGrams: 1500 }. Functionlabel(parcel)milik Kurir Kancil membacaparcel.residanparcel.kg. Apa yang tercetak kalau data Merpati langsung dikirim kelabel? Tebak dulu, jawabannya ada di bagian "Masalahnya: nama field yang beda".
Masalahnya: nama field yang beda
Seluruh kode Kurir Kancil memakai bentuk { resi, kg }. Merpati memakai nama dan satuannya sendiri:
function label(parcel) {
return parcel.resi + " · " + parcel.kg + " kg"
}
function hitungOngkir(kg) {
return kg * 10000 // reguler
}
const fromMerpati = { awb: "MTR-0088", weightGrams: 1500 }
console.log(label(fromMerpati)) // undefined · undefined kg
console.log(hitungOngkir(fromMerpati.kg)) // NaN
Object Merpati gak punya resi dan kg, jadi yang terbaca undefined: label undefined · undefined kg. Itu jawaban tebakannya. undefined * 10000 menghasilkan NaN, dan gak ada error yang memperingatkan.
Menambal tiap function dengan parcel.resi ?? parcel.awb membuat semua function harus tahu format Merpati. Mitra kedua berarti tambalan kedua di tiap tempat.
Adapter: terjemahkan di pintu masuk
function adaptMerpati(p) {
return { resi: p.awb, kg: p.weightGrams / 1000 }
}
const parcel = adaptMerpati(fromMerpati)
console.log(label(parcel)) // MTR-0088 · 1.5 kg
console.log("Rp " + hitungOngkir(parcel.kg)) // Rp 15000
| Merpati | Kurir Kancil |
|---|---|
awb: "MTR-0088" |
resi: "MTR-0088" |
weightGrams: 1500 |
kg: 1.5 |
adaptMerpatimenerima bentuk Merpati dan mengembalikan bentuk Kurir Kancil.labeldanhitungOngkirgak diubah, data Merpati juga gak diubah.- Adapter dipanggil sekali, di tempat data mitra masuk. Sesudah itu semua kode cuma melihat
{ resi, kg }. - Cuma satu function yang tahu nama field Merpati. Kalau Merpati mengganti nama field-nya, cuma
adaptMerpatiyang diubah. - Satuan ikut diterjemahkan dengan tepat: gram dibagi 1000 jadi kilogram.
Adapter juga bisa membungkus object yang method-nya beda nama. Kode baru memanggil getFullName(), object lama cuma punya getName(): adapter-nya object baru dengan getFullName() { return oldUser.getName() }.
Adapter yang diam-diam mengubah arti
Adapter seharusnya cuma mengubah bentuk. Lihat yang terjadi kalau dia ikut membulatkan berat dengan Math.round(p.weightGrams / 1000):
weightGrams |
/ 1000 |
Math.round |
ongkir sebenarnya | ongkir tertagih |
|---|---|---|---|---|
| 1500 | 1.5 | 2 | 15000 | 20000 |
| 2450 | 2.45 | 2 | 24500 | 20000 |
| 800 | 0.8 | 1 | 8000 | 10000 |
Paket 2.450 gram ditagih Rp 20.000, bukan Rp 24.500: Rp 4.500 hilang per paket. Paket 800 gram kelebihan Rp 2.000. Gak ada yang curiga, karena adapter dianggap "cuma menerjemahkan". Kalau Kurir Kancil memang menagih per kilo dibulatkan ke atas (Math.ceil), itu aturan tarif: tulis di perhitungan ongkir, tempat orang mencarinya, bukan di dalam adapter.
Adapter, decorator, dan facade
- Decorator: bentuknya sama dengan yang dibungkus, perilakunya bertambah.
- Adapter: bentuknya beda, diterjemahkan ke bentuk yang diharapkan kodemu. Artinya tetap sama.
- Facade (beberapa lesson lagi): menyembunyikan banyak bagian di balik satu function yang sederhana.
Latihan: createUserAdapter
Latihan nanti membungkus API lama yang gak boleh diubah. Starter sudah berisi object oldUser dengan getName(), getEmail(), dan getPhone(), plus pemanggilannya. Kamu menulis createUserAdapter(oldUser) yang mengembalikan object baru:
getFullName()mengembalikanoldUser.getName()apa adanya.getContact()mengembalikanoldUser.getEmail() + " / " + oldUser.getPhone(): pemisahnya garis miring dengan spasi di kedua sisi.
Hasilnya Budi Santoso, [email protected] / 08123456789, dan untuk user kedua [email protected] / 0811. Baca selalu dari object yang dikirim ke parameter, supaya adapter-nya bekerja untuk user siapa pun.
Kapan gak perlu adapter
- Kamu memegang kedua sisi. Kalau
getResi()lama ada di kodemu sendiri, ganti saja namanya di semua tempat. Adapter cuma menambah lapisan yang harus dirawat. - Bentuknya sudah sama. Mitra yang sudah mengirim
{ resi, kg }gak butuh penerjemah. - Adapter-nya mulai berisi aturan bisnis. Itu bukan menerjemahkan lagi. Pindahkan aturannya ke tempat yang namanya jelas.
Adapter pas untuk sisi yang gak bisa kamu ubah: API mitra, library pihak ketiga, sistem lama, atau migrasi bertahap saat API lama dan baru dipakai bersamaan.
⚠️ Jebakan yang sering ketemu
- Satu field terlupa.
{ resi: p.awb }tanpakgmembuathitungOngkir(parcel.kg)menghasilkanNaN. - Konversi yang membuang informasi. Pembulatan di dalam adapter mengubah tagihan tanpa ada yang melihat.
- Adapter dipanggil di banyak tempat. Format mitra bocor ke seluruh kode. Panggil sekali di pintu masuk.
- Membaca nilai tetap, bukan parameter. Adapter yang mengembalikan
"Budi Santoso"langsung lulus untuk Budi dan gagal untuk Sri.
TL;DR: Adapter menerjemahkan bentuk milik pihak lain ke bentuk yang dipakai kodemu, tanpa mengubah keduanya: adaptMerpati(p) mengubah { awb, weightGrams } jadi { resi, kg } dengan weightGrams / 1000, dipanggil sekali di pintu masuk. Tanpa adapter, yang terbaca undefined dan NaN. Adapter cuma mengubah bentuk dan satuan dengan tepat; pembulatan adalah aturan tarif, bukan tugasnya. Untuk object, adapter adalah object baru dengan method yang diharapkan (getFullName, getContact) yang memanggil method lama. Kalau kamu memegang kedua sisi, ganti langsung saja.