Penggunaan design pattern ini cukup populer. Benefit dari Singleton adalah kita tidak perlu membuat objek baru di setiap penggunaan, dari sisi performa tentu sedikit lebih cepat dan penggunaan memory jadi lebih efisien. Di lain sisi, penggunaan Singleton juga mengundang kontroversi karena global variables, anti-pattern dan menyalahi kodrat OOP. Walaupun begitu Java juga mengimplementasi Singleton seperti pada Runtime class.
Singleton Design Pattern adalah Creational Design Pattern yang memastikan hanya ada satu jenis instance suatu objek dalam memory yang dapat digunakan secara global.
Design Pattern
Use Case
Kita ingin membuat objek User Gateway yang menghububungkan antara objek dengan dengan service User.
Contoh Code
UserGateway Class
public class UserGateway{
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Contoh penggunaan
public static void main(String[] args){
UserGateway userGateway = new UserGateway();
System.out.println("userGateway.getUserNameById(1) = " + userGateway.getUserNameById(1));
System.out.println("userGateway.getUserById(2) = " + userGateway.getUserById(2));
System.out.println(userGateway == new UserGateway()); //this must be false
}
Masalah
Setiap pemakaian UserGateway, kita selalu membuat objek baru. Jika objek tersebut sering di-akses, maka tentu saja akan ada beberapa objek baru yang dibuat tiap pemakaian.
Solusi
Untuk permasalahan di atas, dapat diselesaikan dengan Singleton Design Pattern. Secara umum kita hanya akan membuat objek yang hanya bisa dibuat sekali dan dapat digunakan di berbagai code. Untuk itu kita perlu membuat constructor objek menjadi private untuk mencegah terjadinya pembuatan instance lain by default. Singleton Pattern dapat dibuat menggunakan beberapa cara, yaitu Eager Singleton, Enum Singleton, Lazy Singleton, Thread Safe Singleton, dan Bill Pugh Singleton.
Eager Singleton
public class UserGateway{
private static final UserGateway INSTANCE = new UserGateway();
private UserGateway(){
}
public static UserGateway getInstance(){
return INSTANCE;
}
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Pada Eager Singleton, kita bikin objek UserGateway itu seperti constant. Hal baiknya adalah objeknya final dan thread-safe. Tapi, masalahnya adalah objek tersebut akan dibuat saat Class Loading, bukan saat dibutuhkan. Kalau misalkan kita belum butuh objek tersebut dan terjadi Class Loading pada class tersebut, tentu akan mubazir.
Enum Singleton
public enum UserGateway{
INSTANCE;
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Pada Enum Singleton, kita menjadikan objek UserGateway itu enum. Ini versi lebih simple yang diperkenalkan oleh Joshua Bloch. Kita ga perlu bikin private constructor ataupun getter, tinggal pakai enum instance saja. Keunggulan lainnya, enum itu aman dari Reflection API. Singleton berbasis class walaupun ada private constructor tetap masih bisa diakali untuk membuat objek baru menggunakan Reflection API di Java. Selain itu, by default enum di Java udah serializable. Kalau butuh singleton yang serializable kita ga perlu setup tambahan jika menggunakan enum. Masalahnya, Enum itu ga fleksibel karena class-nya otomatis final, ga bisa di-extend oleh class lain, dan juga ga bisa meng-extend class lain. Ini menyulitkan unit test, objeknya jadi susah untuk di-spy atau di-inject. Enum Singleton juga sama dengan Eager Singleton di atas, objeknya dibuat saat Class Loading, bukan saat pertama kali dibutuhkan. Walaupun di internet ada juga yang bilang Enum Singleton itu Lazy, tapi faktanya objek Enum itu dibuat saat Class Loading.
Lazy Singleton
public class UserGateway{
private static UserGateway userGateway;
private UserGateway(){
}
public static UserGateway getInstance(){
if(userGateway == null){
userGateway = new UserGateway();
}
return userGateway;
}
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Dengan Lazy Singleton, kita melakukan pengecekan setiap mengakses getInstance() method. Sekarang objek tersebut hanya tercipta saat pertama kali digunakan. Namun, singleton seperti ini juga memiliki permasalahan, yaitu race condition karena objeknya static dan tidak final. Misalkan ada lebih dari satu thread mengakses method getInstance() secara bersamaan, maka ada kemungkinan akan tercipta lebih dari satu singleton objek yang kekal dalam memory selama runtime😲.
Thread-Safe Singleton
public class UserGateway{
private static volatile UserGateway userGateway;
private UserGateway(){
}
public static UserGateway getInstance(){
if(userGateway == null){
synchronized(UserGateway.class){
if(userGateway == null){
userGateway = new UserGateway();
}
}
}
return userGateway;
}
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Lanjut dengan Thread-Safe Singleton, di sini kita perlu tambahkan synchronized block sehingga setiap beberapa thread yang sama-sama pertama kali mengakses secara bersamaan dan menemukan instance-nya masih null, maka akan synchronized terlebih dahulu. Hanya satu thread yang boleh lanjut saat itu, sedangkan yang lainnya akan menunggu. Ketika thread pertama sudah selesai, maka thread lainnya yang masih saling tunggu tadi akan menemukan validasi bahwa instance sudah ada value-nya saat melanjutkan aktivitas. Agar semua thread yang mengakses terhindar dari pembacaan value secara parsial di memory, kita perlu tambahkan keyword "volatile" pada variable. Sekarang objeknya dibuat sekali saat pertama kali dipakai dan juga udah Thread-Safe.
Bill Pugh Singleton
public class UserGateway{
static class UserGatewayHolder{
private static final UserGateway INSTANCE = new UserGateway();
}
private UserGateway(){
}
public static UserGateway getInstance(){
return UserGatewayHolder.INSTANCE;
}
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Ini sebenarnya termasuk 'life-hacks', karena kita memanfaatkan inner static class sebagai holder untuk menampung Singleton Object. Inner static class tersebut tidak akan terpengaruh Class Loading ketika UserGateway diakses, kecuali saat mengakses getInstance() method. Ide tersebut diperkenalkan oleh Bill Pugh. Benefit dari singleton ini adalah objeknya hanya dibuat sekali saat pertama kali dipakai, objeknya final, Thread-Safe, mudah di-mock, dan juga tidak butuh synchronize block ataupun volatile keyword sehingga code-nya ga terlalu verbose. UUID pada Java juga menggunakan trik ini saat bikin SecureRandom. Menurut gw, ini metode yang cukup efektif, IMHO🙏.
Contoh penggunaan
public static void main(String[] args){
UserGateway userGateway = UserGateway.getInstance();
System.out.println("userGateway.getUserNameById(1) = " + userGateway.getUserNameById(1));
System.out.println("userGateway.getUserById(2) = " + userGateway.getUserById(2));
System.out.println(userGateway == UserGateway.getInstance()); //this must be true
}
Setiap pemanggilan method UserGateway.getInstance()
hasilnya instance objek yang sama.
Kenapa menggunakan Singleton Design Pattern?
Singleton Pattern cukup membantu dalam efisiensi memory. Meski begitu, tetap saja tidak semua kasus bisa diselesaikan dengan Singleton. Biasanya hanya untuk objek immutable yang memang didesain untuk sering diakses secara masif, sehingga cost-nya lebih gede jika harus bikin objek baru terus-terusan setiap pemakaian. Kadang Singleton juga dibutuhkan pada framework atau library agar objeknya lebih mudah dikontrol secara terpusat. Contoh objek yang biasanya dibuat Singleton adalah objek configuration, repository, gateway, spring component, dan objek sejenisnya.
Verdict
Secara definisi, Singleton Pattern ini memang global variables sehingga ditentang oleh ahli OOP garis keras. Singleton Pattern walaupun memiliki mixed reputation, tapi cukup membantu dalam efisiensi. Singleton Pattern memastikan hanya ada satu instance untuk objek tersebut yang dapat digunakan di berbagai code tanpa harus dibuatkan objek baru setiap pemakaian. Spring Bean pada Spring Framework juga mengimplementasi Singleton, bedanya Singleton pada Spring Bean dihandle oleh framework. Tidak semua objek cocok digunakan sebagai Singleton, apalagi jika objek tersebut mutable, karena dapat berakibat race condition🤯. Jadi, pastikan objek itu immutable jika ingin menggunakannya. By default jangan gunakan Singleton Pattern kecuali dirasa perlu seperti pada gateway, repository, configuration, spring component atau objek sejenisnya yang dirasa perlu.