cv.cm

Pemrograman · Pola desain

Pola Decorator dijelaskan dengan contoh

4 menit baca

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

PesertaTanggung jawabDalam contoh
ComponentInterface yang dibagi oleh objek asli dan decorator.Store
ConcreteComponentObjek asli yang mengerjakan tugas inti.SlowStore
DecoratorMenyimpan referensi ke Component dan mengimplementasikan interface yang sama dengan meneruskan panggilan.StoreDecorator
ConcreteDecoratorMenambah 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
// ADA

Panggilan 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 144

Kapan 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 SlowStore atau 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 interface InputStream. Buku GoF sendiri membahas stream sebagai contoh khas.
  • Koleksi Java: Collections.unmodifiableList(list) dan Collections.synchronizedList(list) mengembalikan pembungkus yang menambah perilaku read-only atau penguncian di sekitar list yang ada.
  • Python: decorator fungsi seperti functools.lru_cache membungkus fungsi dan menambah caching, dan functools.wraps membantu pembungkus mempertahankan nama dan docstring fungsi yang dibungkus.

Pola terkait

PolaPerbedaannya dengan Decorator
AdapterMengubah interface objek. Decorator mempertahankan interface dan menambah perilaku.
ProxyMemiliki struktur yang sama tetapi tujuan berbeda: mengontrol akses (pembuatan lazy, akses jarak jauh, izin), dan biasanya mengelola siklus hidup objek asli sendiri.
CompositeJuga memakai composition rekursif, tetapi composite menyimpan banyak anak; decorator membungkus tepat satu.
StrategyMengubah bagian dalam objek dengan menukar algoritma. Decorator mengubah bagian luar dengan membungkusnya.
Chain of ResponsibilityJuga 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.

Semua tutorial pemrograman