Pemrograman · Pola desain
Penjelasan pola Flyweight beserta contohnya
Jawaban singkat
Flyweight adalah pola desain struktural yang memungkinkan program mendukung objek serupa dalam jumlah sangat besar dengan membagi bagian yang sama. Setiap objek dipecah menjadi intrinsic state (dibagi bersama, immutable, disimpan sekali di dalam flyweight) dan extrinsic state (unik untuk tiap penggunaan, dikirim oleh pemanggil). Sebuah factory menyimpan flyweight dalam cache sehingga intrinsic state yang sama tidak pernah disimpan dua kali.
Tujuan dan masalah yang diselesaikan
Tujuan: memakai pembagian (sharing) agar objek berukuran kecil dalam jumlah besar bisa ditangani secara efisien. Pola ini berasal dari buku Gang of Four (GoF) dan termasuk kategori struktural.
Bayangkan sebuah peta yang menggambar satu juta pohon. Setiap pohon punya posisi (x, y) dan jenis: nama spesies, warna, dan gambar tekstur berukuran beberapa kilobyte. Jika setiap objek pohon menyimpan salinan teksturnya sendiri, pemakaian memori tumbuh sebanding dengan jumlah pohon, dan program bisa kehabisan memori. Padahal spesiesnya hanya beberapa. Tekstur, nama, dan warna sama untuk setiap pohon ek.
Flyweight memindahkan data yang berulang ke sejumlah kecil objek bersama. Sebuah pohon lalu hanya menyimpan posisinya ditambah referensi ke jenis pohon yang dibagi bersama. Satu juta pohon dengan lima spesies hanya butuh lima salinan tekstur, bukan satu juta.
State intrinsic dan extrinsic
Langkah kuncinya adalah memisahkan data setiap objek menjadi dua jenis:
| State | Arti | Contoh pohon |
|---|---|---|
| Intrinsic | Tidak bergantung pada konteks, identik untuk banyak objek, disimpan di dalam flyweight, dan tidak pernah berubah | Nama spesies, warna, tekstur |
| Extrinsic | Bergantung pada konteks, berbeda untuk setiap penggunaan, disimpan oleh klien dan dikirim ke method flyweight | Posisi x dan y |
Karena sebuah flyweight dibagi bersama, ia harus immutable. Jika satu klien bisa mengubah warna jenis pohon ek yang dibagi, setiap pohon ek di peta ikut berubah.
Struktur dan peserta
| Peran | Tanggung jawab |
|---|---|
| Flyweight | Menyimpan intrinsic state dan menyediakan operasi yang menerima extrinsic state sebagai parameter |
| Flyweight factory | Membuat dan menyimpan flyweight dalam cache; mengembalikan yang sudah ada saat intrinsic state yang sama diminta lagi |
| Context | Menyimpan extrinsic state ditambah referensi ke sebuah flyweight (satu context untuk tiap objek di dunia nyata) |
| Client | Meminta flyweight dari factory dan menyimpan atau menghitung extrinsic state |
Implementasi TypeScript
Factory memakai Map dengan kunci berupa nilai intrinsic. TreeType adalah flyweight, Tree adalah context, dan Forest adalah klien.
class TreeType {
constructor(
readonly name: string,
readonly color: string,
readonly texture: Uint8Array, // large, shared data
) {}
draw(x: number, y: number): string {
return this.name + " (" + this.color + ") at " + x + "," + y;
}
}
class TreeTypeFactory {
private static cache = new Map<string, TreeType>();
static get(name: string, color: string): TreeType {
const key = name + "|" + color;
let type = this.cache.get(key);
if (!type) {
type = new TreeType(name, color, new Uint8Array(4096));
this.cache.set(key, type);
}
return type;
}
}
class Tree {
constructor(readonly x: number, readonly y: number, readonly type: TreeType) {}
draw(): string {
return this.type.draw(this.x, this.y);
}
}
const forest: Tree[] = [];
for (let i = 0; i < 100_000; i++) {
const [name, color] = i % 2 === 0 ? ["Oak", "green"] : ["Birch", "white"];
forest.push(new Tree(i, i * 2, TreeTypeFactory.get(name, color)));
}
const types = new Set(forest.map((t) => t.type));
console.log(forest.length, "trees,", types.size, "tree types");
console.log(forest[3].draw());
// 100000 trees, 2 tree types
// Birch (white) at 3,6Tanpa factory, program akan mengalokasikan 100.000 buffer tekstur. Dengan factory, hanya ada dua. Objek Tree tetap ada, tetapi masing-masing hanya menyimpan dua angka dan satu referensi.
Kapan dipakai dan kapan tidak
Pakai Flyweight bila semua hal berikut terpenuhi:
- Program membuat objek serupa dalam jumlah sangat besar.
- Pemakaian memori adalah masalah yang terukur, bukan dugaan.
- Sebagian besar state tiap objek bisa dijadikan extrinsic atau dibagi bersama.
- Program tidak bergantung pada identitas objek (dua context dengan flyweight yang sama boleh berbagi flyweight itu).
Hindari pola ini bila jumlah objek sedikit, bila objek perlu mengubah data bersamanya secara mandiri, atau bila tambahan kerumitan karena harus mengirim extrinsic state ke mana-mana membuat kode lebih sulit dibaca daripada memori yang dihemat. Dalam banyak kasus ada solusi yang lebih sederhana: simpan data dalam array paralel atau typed array, atau lakukan intern pada string berulang di dalam Map.
Trade-off: Flyweight menukar memori dengan waktu CPU. Extrinsic state mungkin harus dihitung ulang atau dicari setiap kali sebuah method dipanggil.
Penggunaan di dunia nyata
- Java:
Integer.valueOf(int)mengembalikan objekIntegerdari cache untuk nilai -128 sampai 127, sehingga boxing angka kecil tidak mengalokasikan objek baru setiap kali. - Java:
String.intern()mengembalikan salinan string kanonik yang dibagi dari string pool. - Editor teks dan browser merender glyph dari cache font bersama: setiap karakter di layar menyimpan posisinya, sedangkan bentuk glyph disimpan sekali per font dan ukuran.
- Game engine memakai instanced rendering: satu mesh dan material dibagi bersama, dan hanya data per-instance seperti posisi dan rotasi yang dikirim untuk setiap salinan.
Pola terkait
| Pola | Perbedaannya dengan Flyweight |
|---|---|
| Singleton | Singleton menjamin satu instance dari sebuah class; Flyweight menyimpan satu instance per intrinsic state yang berbeda, jadi bisa ada banyak. |
| Factory Method | Flyweight factory adalah factory yang punya cache; Factory Method berfokus pada membiarkan subclass memilih class yang akan dibuat. |
| Composite | Node daun pada pohon Composite sering diimplementasikan sebagai flyweight agar daun yang berulang berbagi memori. |
| Prototype | Prototype menyalin objek untuk membuat yang baru; Flyweight menghindari penyalinan dengan membagi satu objek. |
FAQ
Apa perbedaan antara intrinsic dan extrinsic state?
Intrinsic state dibagi bersama dan identik untuk banyak objek, sehingga disimpan di dalam flyweight dan tidak pernah berubah. Extrinsic state berbeda untuk setiap penggunaan, sehingga klien yang menyimpannya dan mengirimkannya ke method flyweight.
Apakah Flyweight sama dengan caching?
Flyweight memang memakai cache, tetapi tujuannya berbeda. Cache menghindari penghitungan ulang suatu hasil, sedangkan Flyweight menghindari penyimpanan data immutable yang sama berkali-kali dengan membagi satu objek ke banyak pengguna.
Mengapa flyweight harus immutable?
Banyak context memegang referensi ke flyweight yang sama. Jika salah satunya mengubahnya, perubahan itu akan muncul di setiap context lain yang ikut berbagi.
Apakah Flyweight berguna di JavaScript dan TypeScript?
Ya, ketika Anda membuat ratusan ribu objek, misalnya partikel, tile peta, atau sel grid. Objek di JavaScript punya overhead per objek, sehingga membagi data yang berulang dapat mengurangi pemakaian memori dan beban garbage collection.