![Contoh Flyweight Design Pattern](https://images.ctfassets.net/7wjlcr9d5ii8/4YZK9qA9Fmgo9qvFfKIldh/e620cd53c8bb41288ff3502f3ee5e247/flyweight.png?w=1024&h=768&q=100&fm=png&fit=thumb&f=faces)
Tadinya gw ga kepikiran buat bikin design pattern ini karena dulunya saat pertama kali bikin seri tentang design pattern, gw jarang banget memakai design pattern ini di dunia nyata, hanya tau teorinya saja. Gw baru menemukan kasus yang cocok menggunakan design pattern ini kurang lebih beberapa bulan yang lalu. Tapi minggu lalu gw liat analytics pencarian blog gw, ada beberapa yang searching keyword Flyweight. Ternyata ada juga peminatnya😁.
Flyweight Design Pattern adalah Structural Design Pattern yang meminimalisir penggunaan memory dengan cara melakukan sharing objek terhadap objek yang sama yang pernah digunakan tanpa harus membuat ulang objek-objek tersebut.
Design Pattern
Untuk use case nya kita pakai class yang udah disediakan Java aja. Kita ingin menggunakan object DateTimeFormatter untuk mencetak String Tanggal dalam format tertentu secara berulang.
public static void main(String[] args){
LocalDate localDate = LocalDate.of(1956, Month.JANUARY, 21);
print1(localDate); // will print 19560121
print2(localDate); // will print 21 January 1956
print3(localDate); // will print 19560121 again!
}
private static void print1(LocalDate localDate){
DateTimeFormatter yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String yyyyMMddStr = yyyyMMddFormatter.format(localDate);
System.out.println("yyyyMMdd = " + yyyyMMddStr);
}
private static void print2(LocalDate localDate){
DateTimeFormatter ddMMMMyyyyFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy");
String ddMMMMyyyy = ddMMMMyyyyFormatter.format(localDate);
System.out.println("ddMMMMyyyy = " + ddMMMMyyyy);
}
private static void print3(LocalDate localDate){
DateTimeFormatter yyyyMMddFormatter2 = DateTimeFormatter.ofPattern("yyyyMMdd");
String yyyyMMddStr2 = yyyyMMddFormatter2.format(localDate);
System.out.println("yyyyMMdd again = " + yyyyMMddStr2);
}
Code di atas sudah berjalan sesuai requirement yang kita inginkan. Cuma masalahnya di atas adalah jika format tanggal yang sama digunakan beberapa kali di berbagai tempat, method, maupun class berbeda, maka dia akan membuat object DateTimeFormatter berulang kali setiap pemakaian. Jika penggunaannya overuse dan terlalu sering dengan memory minimalis, tentu ini bisa jadi masalah😵.
Salah satu solusinya adalah dengan menggunakan Flyweight Design Pattern😎.
Class DateTimeFormatFlyweight
public enum DateTimeFormatFlyweight{
INSTANCE;
public String format(TemporalAccessor temporalAccessor, String formatPattern){
DateTimeFormatter timeFormatter = FlyweightHolder.HOLDER.computeIfAbsent(formatPattern, formatPatternKey -> DateTimeFormatter.ofPattern(formatPattern));
return timeFormatter.format(temporalAccessor);
}
private static class FlyweightHolder{
private static final Map<String, DateTimeFormatter> HOLDER = new ConcurrentHashMap<>();
}
}
Contoh penggunaan
public static void main(String[] args){
LocalDate localDate = LocalDate.of(1956, Month.JANUARY, 21);
print1(localDate); // will print 19560121
print2(localDate); // will print 21 January 1956
print3(localDate); // will print 19560121 again but the formatter is reused from cache
}
private static void print1(LocalDate localDate){
String yyyyMMddStr = DateTimeFormatFlyweight.INSTANCE.format(localDate, "yyyyMMdd");
System.out.println("yyyyMMdd = " + yyyyMMddStr);
}
private static void print2(LocalDate localDate){
String ddMMMMyyyy = DateTimeFormatFlyweight.INSTANCE.format(localDate, "dd MMMM yyyy");
System.out.println("ddMMMMyyyy = " + ddMMMMyyyy);
}
private static void print3(LocalDate localDate){
String yyyyMMddStr2 = DateTimeFormatFlyweight.INSTANCE.format(localDate, "yyyyMMdd");
System.out.println("yyyyMMdd = " + yyyyMMddStr2);
}
Kita menngunakan enum DateTimeFormatFlyweight agar instance Flyweight-nya singleton. Kita memakai inner class FlyweightHolder seperti solusi Bill Pugh yang pernah dibahas sebelumnya. Kita juga menggunakan ConcurrentHashMap sebagai holder agar thread-safe. Sekarang pembuatan object DateTimeFormatter udah di-cache di dalam inner class FlyweightHolder. Jadi ga setiap penggunaan harus bikin object baru lagi, akan tetapi akan melakukan pengecekan terhadap FlyweightHolder terlebih dahulu melalui method computeIfAbsent. Kalau ada, maka pakai yang dari cache tanpa bikin object baru, dan jika belum di-cache maka akan bikin object DateTimeFormatter lalu objectnya disimpan ke dalam FlyweightHolder agar nantinya jika ada format yang sama, maka akan menggunakan object yang sudah disimpan di FlyweightHolder. Object-nya jadi shareable dan bisa digunakan di berbagai tempat class maupun method. Oh ya, untuk Java 8 perlu pengecekan dulu pakai method get sebelum melakukan computeIfAbsent karena ada performance bugs. Bugs tersebut baru di-fix pada Java 9 ke atas.
Flyweight Design Pattern cocok untuk optimasi memori pada aplikasi, sehingga objek tertentu bisa di-reuse dan shareable tanpa harus bikin baru setiap pemakaian. Tentu saja object tersebut harus immutable, kalau mutable justru bakal buggy. Pada contoh di atas, kebetulan DateTimeFormatter itu immutable, makanya cocok. Lain halnya jika menggunakan SimpleDateFormat, itu mutable dan jangan sekali-kali dijadikan obejct yang shareable karena ga thread-safe. Bahaya dan sangat rentan terhadap bugs. Bayangkan nantinya jika ada dev yang melakukan mutasi pattern, bisa berubah format pattern-nya saat runtime🤯. Selain itu, jaman sekarang juga ada namanya Redis, jadi cache bisa dilakukan secara external, bukan dalam aplikasi lagi. Bedanya, kalau pakai Flyweight cache hanya bisa diakses oleh aplikasi itu sendiri, sedangkan Redis bisa terhubung dengan beberapa aplikasi lain. Jika aplikasi restart, cache masih aman di dalam Redis, berbeda dengan Flyweight yang kalau aplikasi restart, cache-nya juga ikut hilang. Dan yang paling utama, kalau pakai Redis kita bisa menentukan kapan value-nya bakal expire, sedangkan kalau Flyweight Pattern butuh beberapa code lagi yang agak ribet untuk bikin kayak gitu.
Dengan Flyweight Design Pattern kita bisa meminimalisir pemakaian memory karena beberapa object immutable bisa kita cache tanpa harus bikin object lagi di setiap penggunaan. Sekilas, Design Pattern ini ada kemiripan Singleton Design Pattern karena sama-sama global variables yang shareable ke berbagai tempat. Bedanya, kalau Singleton instance object tersebut sudah pasti hanya satu, sedangkan kalau Flyweight instance-nya bisa lebih dari satu objek. Jaman sekarang cache bisa dilakukan menggunakan aplikasi pihak ketiga seperti Redis DB dengan fitur yang lebih lengkap. Java sendiri juga menggunakan Flyweight Design Pattern pada class Wrapper, contohnya Integer. Integer Wrapper menyimpan cache angka dari -128 hingga 127. Jadi ketika kita memanggil method Integer.valueOf(1)
atau secara literal seperti Integer x = 1
, maka Java akan mengambil Integer dengan angka tersebut dari cache. Kecuali jika angka tersebut diluar range yang disebutkan tadi, maka Java akan membuat object baru. Cache tersebut tentu saja tidak berlaku jika kita membuat object Integer menggunakan 'new' keyword seperti Integer x = new Integer(1)
, karena itu sudah pasti akan membuat object baru. Begitu juga dengan Wrapper lainnya seperti Boolean, Short, Character, dll juga mengimplementasi Flyweight pattern. Termasuk String, ketika kita membuat String secara literal seperti String x = "hello world"
, maka JVM hanya akan membuat object String "hello world" saat pertama kali saja. Selanjutnya akan di-cache, sehingga ketika kita membuat object String dengan value yang sama secara literal seterusnya, maka tidak akan membuat object baru setiap pemakaian, melainkan ambil dari cache (kecuali String tersebut dihasilkan dari concatenation). Makanya best practice menggunakan object Wrapper dan String adalah dengan TIDAK menggunakan 'new' keyword. Biasanya akan muncul warning dari IDE yang kita gunakan jika menggunakan 'new' keyword.