![Contoh Flyweight Design Pattern](http://images.ctfassets.net/7wjlcr9d5ii8/4YZK9qA9Fmgo9qvFfKIldh/a5d234118ccf82db29ace7673c3be0b6/22557511.54ada5537d697.jpg?w=404&h=316&fl=progressive&q=75&fm=jpg)
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 setahun terakhir. 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 object terhadap jenis object yang sama, tanpa harus membuat ulang objek yang telah digunakan.
Design Pattern
Use Case
Untuk use case nya kita pakai class yang udah disediakan Java aja. Kita ingin menggunakan object DateTimeFormatter untuk mencetak String Tanggal dalam format tertentu.
Contoh Code
public static void main(String[] args){
LocalDate localDate = LocalDate.of(1956, Month.JANUARY, 31);
DateTimeFormatter yyyyMMddFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
String yyyyMMddStr = yyyyMMddFormatter.format(localDate);
System.out.println("yyyyMMdd = " + yyyyMMddStr); // will print 19560131
DateTimeFormatter ddMMMMyyyyFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy");
String ddMMMMyyyy = ddMMMMyyyyFormatter.format(localDate);
System.out.println("ddMMMMyyyy = " + ddMMMMyyyy); // will print 31 January 1956
DateTimeFormatter yyyyMMddFormatter2 = DateTimeFormatter.ofPattern("yyyyMMdd");
String yyyyMMddStr2 = yyyyMMddFormatter2.format(localDate);
System.out.println("yyyyMMdd = " + yyyyMMddStr2); // will print 19560131 again!
}
Problem
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😵.
Solusi
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.get(formatPattern);
if(timeFormatter == null){
timeFormatter = DateTimeFormatter.ofPattern(formatPattern);
FlyweightHolder.HOLDER.put(formatPattern, timeFormatter);
}
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, 31);
String yyyyMMddStr = DateTimeFormatFlyweight.INSTANCE.format(localDate, "yyyyMMdd");
System.out.println("yyyyMMdd = " + yyyyMMddStr); // will print 19560131
String ddMMMMyyyy = DateTimeFormatFlyweight.INSTANCE.format(localDate, "dd MMMM yyyy");
System.out.println("ddMMMMyyyy = " + ddMMMMyyyy); // will print 31 January 1956
String yyyyMMddStr2 = DateTimeFormatFlyweight.INSTANCE.format(localDate, "yyyyMMdd");
System.out.println("yyyyMMdd = " + yyyyMMddStr2); // will print 19560131 again!
}
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. 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.
Kenapa menggunakan Flyweight Design Pattern?
Flyweight Design Pattern cocok untuk aplikasi yang minim memory agar optimal, sehingga objek tertentu yang 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 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.
Verdict
Dengan Flyweight Design Pattern kita bisa meminimalisir pemakaian memory karena beberapa object immutable bisa kita cache tanpa harus bikin object lagi di setiap penggunaan. Ini bermanfaat untuk aplikasi yang spesifikasinya minim memory. Sekilas, Design Pattern ini ada kemiripan Singleton Design Pattern karena sama-sama global variables yang shareable ke berbagai tempat. Bedanya, kalau Singleton instance shareable object tersebut sudah pasti hanya satu, sedangkan kalau Flyweight instance-nya bisa lebih dari satu. 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, contohnhya 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, karena itu sudah pasti akan membuat object baru. Begitu juga dengan Wrapper lainnya seperti Boolean, Short, Character, dll. 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 inisiasi object Wrapper dan String adalah dengan TIDAK menggunakan 'new' keyword. Biasanya akan muncul warning dari IDE yang kita gunakan jika menggunakan 'new' keyword.