![Contoh Singleton Design Pattern](https://images.ctfassets.net/7wjlcr9d5ii8/zTAQbmWJLTmInzV2KDSW4/4ed39052161166257c908907910f64cc/meditation-yoga.jpg?w=670&h=377&fl=progressive&q=75&fm=jpg)
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 tersebut dalam memory yang dapat diakses di berbagai code.
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));
}
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 shareable di berbagai code. Untuk itu kita perlu membuat default constructor menjadi private untuk mencegah terjadinya pembuatan instance lain by default. Singleton Pattern dapat dibuat menggunakan beberapa cara, yaitu Eager Singleton, Lazy Singleton, Thread Safe Singleton, dan Bill Pugh Singleton.
Eager Singleton
public class UserGateway{
private static final UserGateway userGateway = new UserGateway();
private UserGateway(){
}
public static UserGateway getInstance(){
return userGateway;
}
public String getUserNameById(int id){
return "";
}
public User getUserById(int id){
return new User();
}
}
Pada Eager Singleton, kita menjadikan objek UserGateway itu seperti constant. Hal baiknya adalah objeknya final dan thread-safe. Tapi, masalahnya adalah objek tersebut akan dibuat saat Class Loading. Objeknya sudah tercipta sebelum kita butuhkan. Kalau misalkan kita belum butuh objek tersebut, tentu akan mubazir.
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 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 sehinggga 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. Sekarang objeknya dibuat sekali saat pertama kali dipakai dan juga udah Thread-Safe. Objeknya sudah lebih baik dari dua jenis Singleton sebelumnya.
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, Thread-Safe, dan juga tidak butuh synchronized code sehingga code-nya juga ga terlalu verbose. UUID pada Java juga menggunakan trik ini saat inisialisasi 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));
}
Kenapa menggunakan Singleton Design Pattern?
Singleton Pattern cukup membantu dalam efisiensi. Meski begitu, tetap saja tidak semua kasus bisa diselesaikan dengan Singleton. Biasanya hanya objek besar yang kemungkinan sering diakses secara massive dan objek tersebut immutable, sehingga cost-nya lebih gede jika harus bikin objek baru terus-terusan setiap pemakaian. Contohnya seperti objek repository, gateway, service, spring component, use case classes dan objek sejenisnya.
Verdict
Secara definisi, Singleton Pattern ini memang global variables. 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 inisialisasi objek baru setiap pemakaian. Spring Bean sendiri mengimplementasi Singleton pada objeknya, bedanya Singleton pada Spring Bean dihandle oleh framework. Tidak semua objek bisa digunakan sebagai Singleton, apalagi jika objek tersebut mutable, karena dapat berakibat race condition. By default jangan gunakan Singleton Pattern kecuali dirasa perlu seperti pada gateway, repository, service object, spring component atau objek sejenisnya yang dirasa perlu.