Pemrograman · Pola desain
Pola Adapter dijelaskan dengan contoh
Jawaban singkat
Adapter adalah pola desain structural yang mengubah interface sebuah kelas yang ada menjadi interface yang diharapkan kode klien. Adapter membungkus objek yang tidak kompatibel dan menerjemahkan setiap panggilan, misalnya mengubah charge(amountCents) menjadi makePayment("12.50") milik sistem lama. Pola ini memungkinkan Anda memakai ulang komponen pihak ketiga atau legacy tanpa mengubahnya dan tanpa menyebarkan API yang canggung itu ke seluruh kode Anda. Pola ini juga dikenal sebagai Wrapper.
Tujuan
Mengubah interface sebuah kelas menjadi interface lain yang diharapkan klien. Pola Adapter memungkinkan kelas-kelas yang sebelumnya tidak bisa bekerja bersama karena interface tidak kompatibel menjadi dapat bekerja bersama. Pola ini salah satu dari tujuh pola structural dalam buku Gang of Four (GoF).
Masalah yang diselesaikan
Kode toko Anda bekerja dengan interface kecil PaymentGateway: charge(amountCents, currency) mengembalikan hasil dengan flag ok dan ID transaksi. Sekarang bisnis menandatangani kontrak dengan penyedia pembayaran yang SDK-nya tampak sangat berbeda: jumlah berupa string desimal, mata uang menjadi bagian dari objek permintaan, dan error kembali sebagai kode status numerik.
Anda tidak bisa mengubah SDK, dan tidak ingin menulis ulang setiap pemanggil agar memakai bentuk baru. Jika gaya SDK merembes ke checkout, refund, dan laporan, berganti penyedia nanti akan mahal. Adapter menjaga penerjemahan di satu kelas.
Struktur dan peserta
| Peserta | Tanggung jawab | Dalam contoh |
|---|---|---|
| Target | Interface yang diharapkan klien. | PaymentGateway |
| Adaptee | Kelas yang sudah ada dengan interface yang tidak kompatibel. | LegacyPayClient |
| Adapter | Mengimplementasikan Target dan menerjemahkan panggilan ke Adaptee. | LegacyPayAdapter |
| Klien | Hanya bekerja dengan interface Target. | checkout |
Implementasi TypeScript
// Target: what our code expects.
interface PaymentGateway {
charge(amountCents: number, currency: string): { ok: boolean; id: string };
}
// Adaptee: a third-party SDK we cannot change.
class LegacyPayClient {
makePayment(req: { amount: string; curr: string }): { status: number; ref: string } {
return { status: req.curr === "USD" ? 200 : 422, ref: `TX-${req.amount}` };
}
}
// Adapter: implements the Target by translating to the Adaptee.
class LegacyPayAdapter implements PaymentGateway {
constructor(private client: LegacyPayClient) {}
charge(amountCents: number, currency: string) {
const res = this.client.makePayment({
amount: (amountCents / 100).toFixed(2),
curr: currency.toUpperCase(),
});
return { ok: res.status === 200, id: res.ref };
}
}
// Client code knows nothing about LegacyPayClient.
function checkout(gateway: PaymentGateway, totalCents: number): string {
const result = gateway.charge(totalCents, "usd");
return result.ok ? `paid ${result.id}` : "payment failed";
}
console.log(checkout(new LegacyPayAdapter(new LegacyPayClient()), 1250)); // paid TX-12.50Berganti ke penyedia lain nanti berarti menulis satu adapter lagi. checkout dan setiap pemanggil lain tetap sama, dan pengujian dapat meneruskan PaymentGateway tiruan.
Object adapter versus class adapter
Buku GoF menjelaskan dua bentuk:
| Bentuk | Cara kerja | Catatan |
|---|---|---|
| Object adapter | Menyimpan referensi ke adaptee dan mendelegasikan ke sana (composition). | Bekerja dengan adaptee dan semua subkelasnya. Pilihan umum di TypeScript, Java, dan C#. |
| Class adapter | Mewarisi dari adaptee dan mengimplementasikan interface target. | Membutuhkan inheritance dari adaptee, sehingga hanya mengadaptasi satu kelas itu. Umum di C++ dengan multiple inheritance. |
Di TypeScript dan JavaScript, mengadaptasi fungsi sering sudah cukup. Node.js menyertakan adapter kecil semacam itu: util.promisify() mengubah fungsi bergaya callback menjadi fungsi yang mengembalikan promise.
import { promisify } from "node:util";
// Adaptee: old callback style (error first, then result).
function readConfig(name: string, cb: (err: Error | null, value?: string) => void): void {
setTimeout(() => cb(null, `config for ${name}`), 10);
}
// Adapted: the same function, now with a promise-based interface.
const readConfigAsync = promisify(readConfig);
console.log(await readConfigAsync("app")); // config for appKapan memakainya, dan kapan tidak
Gunakan pola Adapter ketika:
- Anda ingin memakai kelas yang sudah ada, tetapi interface-nya tidak cocok dengan yang dibutuhkan kode Anda.
- Anda mengintegrasikan pustaka pihak ketiga atau sistem legacy dan ingin menjaga API-nya di tepi program Anda.
- Anda ingin beberapa implementasi berbeda, seperti dua penyedia pembayaran, di balik satu interface.
Hindari ketika:
- Anda memiliki kedua sisi. Mengubah salah satu interface biasanya lebih sederhana daripada menambah lapisan penerjemah.
- Adapter harus memalsukan bagian besar dari interface target yang sebenarnya tidak bisa didukung adaptee. Itu menyembunyikan ketidakcocokan nyata alih-alih menyelesaikannya.
Pemakaian di dunia nyata
- Node.js:
util.promisify()mengadaptasi fungsi berbasis callback ke promise. - Java:
Arrays.asList()memungkinkan array dipakai lewat interfaceList, danInputStreamReadermemungkinkanInputStreamberbasis byte dibaca sebagaiReaderberbasis karakter. - Pustaka database dan ORM memakai satu adapter atau driver per database, sehingga API query yang sama bekerja dengan PostgreSQL, MySQL, atau SQLite.
- Kode frontend sering membungkus API browser atau SDK analitik di balik interface kecil khusus proyek karena alasan yang sama.
Pola terkait
| Pola | Perbedaannya dengan Adapter |
|---|---|
| Decorator | Mempertahankan interface yang sama dan menambah perilaku. Adapter mengubah interface dan tidak menambah perilaku baru. |
| Proxy | Mempertahankan interface yang sama dan mengontrol akses ke objek (lazy loading, caching, izin). |
| Facade | Mendefinisikan interface baru yang lebih sederhana untuk seluruh subsistem berisi banyak kelas. Adapter membuat satu interface yang ada cocok dengan interface lain yang ada. |
| Bridge | Dirancang sejak awal agar abstraksi dan implementasinya bisa bervariasi terpisah. Adapter biasanya ditambahkan belakangan agar kelas yang ada cocok. |
FAQ
Apa beda pola Adapter dan pola Facade?
Adapter membuat satu kelas yang ada cocok dengan interface yang sudah diharapkan klien. Facade membuat interface baru yang lebih sederhana di depan subsistem kompleks berisi banyak kelas.
Apa beda pola Adapter dan pola Decorator?
Keduanya membungkus objek. Decorator mempertahankan interface yang sama dan menambah perilaku; adapter mengubah interface agar objek yang dibungkus bisa dipakai di tempat yang sebelumnya tidak mungkin.
Apakah Adapter sama dengan wrapper?
Wrapper adalah nama lain Adapter dalam buku GoF. Dalam percakapan sehari-hari, "wrapper" juga dipakai untuk decorator dan proxy, jadi tujuannya yang menentukan pola mana yang dimaksud.
Bisakah satu adapter mengadaptasi beberapa kelas?
Object adapter dapat bekerja dengan sebuah adaptee dan semua subkelasnya. Mengadaptasi kelas yang tidak berhubungan biasanya berarti satu adapter per kelas, semuanya mengimplementasikan interface target yang sama.