Pemrograman · Pola desain
Pola Decorator dijelaskan dengan contoh
Jawaban singkat
Decorator adalah pola desain structural yang menambah tanggung jawab pada objek secara dinamis dengan membungkusnya dalam objek lain yang memiliki interface sama. Setiap decorator meneruskan panggilan ke objek yang dibungkus dan menambah perilakunya sendiri sebelum atau sesudahnya, seperti logging, caching, atau kompresi. Karena decorator dapat ditumpuk dalam urutan apa pun, pola ini menjadi alternatif yang fleksibel daripada membuat subkelas untuk setiap kombinasi fitur.
Tujuan
Menempelkan tanggung jawab tambahan pada objek secara dinamis. Decorator menyediakan alternatif yang fleksibel dibanding subclassing untuk memperluas fungsionalitas. Pola Decorator adalah salah satu dari tujuh pola structural dalam buku Gang of Four (GoF), tempat pola ini juga dikenal sebagai Wrapper.
Masalah yang diselesaikan
Bayangkan penyimpanan data dengan get(key). Sebagian pemanggil ingin logging, sebagian ingin cache dalam memori, sebagian ingin keduanya, dan sebagian ingin percobaan ulang untuk back end jaringan yang lambat. Dengan inheritance Anda akan menulis LoggingStore, CachingStore, LoggingCachingStore, RetryingCachingStore, dan seterusnya: satu subkelas untuk setiap kombinasi, dan kombinasinya ditetapkan saat kompilasi.
Pola Decorator mengubah setiap fitur menjadi kelas pembungkus kecil. Anda membangun kombinasi yang dibutuhkan saat runtime dengan membungkus satu objek dengan objek lain, dan kode yang memakai store melihat interface yang sama berapa pun jumlah lapisannya.
Struktur dan peserta
| Peserta | Tanggung jawab | Dalam contoh |
|---|---|---|
| Component | Interface yang dibagi oleh objek asli dan decorator. | Store |
| ConcreteComponent | Objek asli yang mengerjakan tugas inti. | SlowStore |
| Decorator | Menyimpan referensi ke Component dan mengimplementasikan interface yang sama dengan meneruskan panggilan. | StoreDecorator |
| ConcreteDecorator | Menambah perilaku sebelum atau sesudah meneruskan. | LoggingStore, CachingStore |
Implementasi TypeScript
interface Store {
get(key: string): string;
}
class SlowStore implements Store {
get(key: string): string { console.log(` (database read: ${key})`); return key.toUpperCase(); }
}
// Base decorator: forwards everything to the wrapped store.
abstract class StoreDecorator implements Store {
constructor(protected inner: Store) {}
get(key: string): string { return this.inner.get(key); }
}
class LoggingStore extends StoreDecorator {
get(key: string): string {
console.log(`get ${key}`);
return super.get(key);
}
}
class CachingStore extends StoreDecorator {
private cache = new Map<string, string>();
get(key: string): string {
if (!this.cache.has(key)) this.cache.set(key, super.get(key));
return this.cache.get(key)!;
}
}
// Stack features in any order at run time.
const store: Store = new LoggingStore(new CachingStore(new SlowStore()));
console.log(store.get("ada"));
console.log(store.get("ada"));
// get ada
// (database read: ada)
// ADA
// get ada
// ADAPanggilan kedua dicatat tetapi dijawab dari cache, sehingga database tidak dibaca lagi. Jika Anda menukar urutannya, new CachingStore(new LoggingStore(...)), hanya cache miss yang dicatat. Urutan decorator mengubah perilaku, yang sekaligus merupakan fitur dan sumber kebingungan yang umum.
Decorator fungsi di TypeScript
Ketika komponennya berupa satu fungsi, decorator dapat berupa fungsi higher-order: menerima sebuah fungsi dan mengembalikan fungsi baru dengan signature yang sama dan perilaku tambahan.
function withTiming<A extends unknown[], R>(name: string, fn: (...args: A) => R) {
return (...args: A): R => {
const start = performance.now();
try {
return fn(...args);
} finally {
console.log(`${name} took ${(performance.now() - start).toFixed(1)} ms`);
}
};
}
const slowSquare = (n: number) => { for (let i = 0; i < 1e6; i++); return n * n; };
const timedSquare = withTiming("square", slowSquare);
console.log(timedSquare(12)); // square took ... ms, then 144Kapan memakainya, dan kapan tidak
Gunakan pola Decorator ketika:
- Anda ingin menambah fitur seperti logging, caching, percobaan ulang, validasi, kompresi, atau pemeriksaan akses pada objek tertentu, tanpa memengaruhi objek lain dari kelas yang sama.
- Fitur harus bisa digabungkan secara bebas, dan subclassing akan menghasilkan kelas untuk setiap kombinasi.
- Anda tidak bisa atau tidak sebaiknya mengubah kelas aslinya, misalnya karena berasal dari pustaka.
Hindari ketika:
- Anda hanya butuh satu perilaku tambahan yang tetap. Menambahkannya langsung, atau memakai satu subkelas, lebih sederhana.
- Kode bergantung pada identitas atau kelas konkret objek. Objek yang didekorasi adalah objek yang berbeda, sehingga pemeriksaan seperti
instanceof SlowStoreatau perbandingan===gagal. - Banyak lapisan kecil mempersulit debugging. Tumpukan pembungkus yang dalam menghasilkan call stack panjang dan membuat tidak jelas lapisan mana melakukan apa.
Middleware di framework web, seperti Express atau Koa, adalah gagasan yang terkait: setiap lapisan menerima permintaan, boleh bertindak atasnya, dan meneruskannya.
Pemakaian di dunia nyata
- Java I/O:
new BufferedInputStream(new FileInputStream("data.bin"))membungkus sebuah stream dengan stream lain yang menambah buffering, dan keduanya memiliki interfaceInputStream. Buku GoF sendiri membahas stream sebagai contoh khas. - Koleksi Java:
Collections.unmodifiableList(list)danCollections.synchronizedList(list)mengembalikan pembungkus yang menambah perilaku read-only atau penguncian di sekitar list yang ada. - Python: decorator fungsi seperti
functools.lru_cachemembungkus fungsi dan menambah caching, danfunctools.wrapsmembantu pembungkus mempertahankan nama dan docstring fungsi yang dibungkus.
Pola terkait
| Pola | Perbedaannya dengan Decorator |
|---|---|
| Adapter | Mengubah interface objek. Decorator mempertahankan interface dan menambah perilaku. |
| Proxy | Memiliki struktur yang sama tetapi tujuan berbeda: mengontrol akses (pembuatan lazy, akses jarak jauh, izin), dan biasanya mengelola siklus hidup objek asli sendiri. |
| Composite | Juga memakai composition rekursif, tetapi composite menyimpan banyak anak; decorator membungkus tepat satu. |
| Strategy | Mengubah bagian dalam objek dengan menukar algoritma. Decorator mengubah bagian luar dengan membungkusnya. |
| Chain of Responsibility | Juga meneruskan permintaan melalui serangkaian objek, tetapi setiap handler boleh menghentikan permintaan; decorator biasanya selalu meneruskan. |
FAQ
Apa beda pola Decorator dan inheritance?
Inheritance menambah perilaku ke seluruh kelas saat kompilasi. Decorator menambah perilaku ke satu objek saat runtime, dan beberapa decorator dapat digabungkan dalam urutan apa pun.
Apa beda pola Decorator dan pola Proxy?
Bentuknya sama. Decorator menambah fitur yang dipilih klien untuk ditumpuk, sedangkan proxy mengontrol akses ke objek asli, sering kali membuatnya atau memutuskan apakah akan memanggilnya sama sekali.
Apakah @decorator TypeScript adalah pola Decorator?
Tidak persis. Decorator @ adalah fitur bahasa yang mengubah kelas dan anggotanya saat didefinisikan. Decorator itu bisa dipakai untuk mengimplementasikan pembungkusan, tetapi pola GoF adalah tentang membungkus objek saat runtime.
Apakah urutan decorator berpengaruh?
Ya. Setiap decorator melihat panggilan sebelum objek yang dibungkusnya, jadi cache yang diletakkan di luar logger menyembunyikan panggilan berulang dari logger, sedangkan logger di luar cache melihat setiap panggilan.