Mungkin Anda pernah mendengar bahwa wiski single malt Scotch baik? Ya, alkohol tidak baik untuk kesehatan Anda, jadi hari ini kami akan memberi tahu Anda tentang pola desain singleton di Jawa.

Kami sebelumnya meninjau pembuatan objek, jadi kami tahu bahwa untuk membuat objek di Java, Anda perlu menulis sesuatu seperti:


Robot robot = new Robot(); 
    

Tetapi bagaimana jika kita ingin memastikan bahwa hanya satu instance dari class yang dibuat?

Pernyataan Robot() baru dapat membuat banyak objek, dan tidak ada yang menghentikan kita untuk melakukannya. Di sinilah pola tunggal datang untuk menyelamatkan.

Misalkan Anda perlu menulis sebuah aplikasi yang akan terhubung ke printer — hanya SATU printer — dan memerintahkannya untuk mencetak:


public class Printer { 
 
	public Printer() { 
	} 
     
	public void print() { 
    	… 
	} 
}
    

Ini terlihat seperti kelas biasa... TAPI! Ada satu "tetapi": Saya dapat membuat banyak contoh objek printer saya dan memanggil metode di tempat yang berbeda. Ini dapat membahayakan atau bahkan merusak printer saya. Jadi kita perlu memastikan bahwa hanya ada satu instance dari printer kita, dan itulah yang akan dilakukan singleton untuk kita!

Cara membuat singleton

Ada dua cara untuk membuat singleton:

  • gunakan konstruktor pribadi;
  • ekspor metode statis publik untuk menyediakan akses ke satu instance.

Mari pertama-tama pertimbangkan untuk menggunakan konstruktor pribadi. Untuk melakukan ini, kita perlu mendeklarasikan sebuah field sebagai final di kelas kita dan menginisialisasinya. Karena kita menandainya sebagai final , kita tahu itu akan tetap , yaitu kita tidak bisa lagi mengubahnya.

Anda juga perlu mendeklarasikan konstruktor sebagai private untuk mencegah pembuatan objek di luar class . Ini menjamin bagi kami bahwa tidak akan ada contoh lain dari printer kami dalam program ini. Konstruktor akan dipanggil sekali saja selama inisialisasi dan akan membuat Printer :


public class Printer { 
     
	public static final Printer PRINTER = new Printer(); 
     
	private Printer() { 
	} 
 
	public void print() { 
        // Printing... 
 
	} 
}
    

Kami menggunakan konstruktor pribadi untuk membuat PRINTER singleton — hanya akan ada satu instance. ItuPENCETAKvariabel memiliki pengubah statis , karena itu bukan milik objek apa pun, tetapi milik kelas Printer itu sendiri.

Sekarang mari pertimbangkan untuk membuat singleton menggunakan metode statis untuk menyediakan akses ke satu instance kelas kita (dan perhatikan bahwa bidangnya sekarang adalah private ):


public class Printer { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
     
	public void print() { 
        // Printing... 
	} 
} 
    

Tidak peduli berapa kali kita memanggil metode getInstance() di sini, kita akan selalu mendapatkan hasil yang samaPENCETAKobyek.

Membuat singleton menggunakan konstruktor pribadi lebih sederhana dan ringkas. Terlebih lagi, APInya jelas, karena bidang publik dideklarasikan sebagai final , yang menjamin bahwa itu akan selalu berisi referensi ke objek yang sama.

Opsi metode statis memberi kita fleksibilitas untuk mengubah singleton menjadi kelas non-singleton tanpa mengubah API-nya. Metode getInstance () memberi kita satu instance dari objek kita, tetapi kita dapat mengubahnya sehingga mengembalikan instance terpisah untuk setiap pengguna yang memanggilnya.

Opsi statis juga memungkinkan kita menulis pabrik singleton generik.

Manfaat terakhir dari opsi statis adalah Anda dapat menggunakannya dengan referensi metode.

Jika Anda tidak memerlukan keuntungan di atas, kami sarankan untuk menggunakan opsi yang melibatkan bidang publik .

Jika kita membutuhkan serialisasi, maka tidak cukup hanya mengimplementasikan antarmuka Serializable . Kita juga perlu menambahkan metode readResolve , jika tidak, kita akan mendapatkan instance singleton baru selama deserialisasi.

Serialisasi diperlukan untuk menyimpan status objek sebagai urutan byte, dan deserialisasi diperlukan untuk memulihkan objek dari byte tersebut. Anda dapat membaca lebih lanjut tentang serialisasi dan deserialisasi di artikel ini .

Sekarang mari kita menulis ulang singleton kita:


public class Printer implements Serializable { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
} 
    

Sekarang kita akan membuat serial dan deserialize.

Perhatikan bahwa contoh di bawah ini adalah mekanisme standar untuk serialisasi dan deserialisasi di Java. Pemahaman lengkap tentang apa yang terjadi dalam kode akan muncul setelah Anda mempelajari "I/O streams" (dalam modul Java Syntax) dan "Serialization" (dalam modul Java Core).

var printer = Printer.getInstance(); 
var fileOutputStream = new FileOutputStream("printer.txt"); 
var objectOutputStream = new ObjectOutputStream(fileOutputStream); 
objectOutputStream.writeObject(printer); 
objectOutputStream.close(); 
 
var fileInputStream = new FileInputStream("printer.txt"); 
var objectInputStream = new ObjectInputStream(fileInputStream); 
var deserializedPrinter =(Printer) objectInputStream.readObject(); 
objectInputStream.close(); 
 
System.out.println("Singleton 1 is: " + printer); 
System.out.println("Singleton 2 is: " + deserializedPrinter);
    

Dan kami mendapatkan hasil ini:

Singleton 1 adalah: Printer@6be46e8f
Singleton 2 adalah: Printer@3c756e4d

Di sini kita melihat bahwa deserialisasi memberi kita contoh berbeda dari singleton kita. Untuk memperbaikinya, mari tambahkan metode readResolve ke kelas kita:


public class Printer implements Serializable { 
 
	private static final Printer PRINTER = new Printer(); 
 
	private Printer() { 
	} 
 
	public static Printer getInstance() { 
    	return PRINTER; 
	} 
 
	public Object readResolve() { 
    	return PRINTER; 
	} 
}
    

Sekarang kita akan membuat serial dan deserialize singleton kita lagi:


var printer = Printer.getInstance(); 
var fileOutputStream = new FileOutputStream("printer.txt"); 
var objectOutputStream = new ObjectOutputStream(fileOutputStream); 
objectOutputStream.writeObject(printer); 
objectOutputStream.close(); 
 
var fileInputStream = new FileInputStream("printer.txt"); 
var objectInputStream = new ObjectInputStream(fileInputStream); 
var deserializedPrinter=(Printer) objectInputStream.readObject(); 
objectInputStream.close(); 
 
System.out.println("Singleton 1 is: " + printer); 
System.out.println("Singleton 2 is: " + deserializedPrinter); 
    

Dan kami mendapatkan:

Singleton 1 adalah: com.company.Printer@6be46e8f
Singleton 2 adalah: com.company.Printer@6be46e8f

Metode readResolve() memungkinkan kita mendapatkan objek yang sama dengan yang kita deserialisasi, sehingga mencegah pembuatan singleton jahat.

Ringkasan

Hari ini kita belajar tentang lajang: cara membuatnya dan kapan menggunakannya, untuk apa mereka, dan opsi apa yang ditawarkan Java untuk membuatnya. Fitur spesifik dari kedua opsi diberikan di bawah ini:

Pembangun pribadi Metode statis
  • Lebih mudah dan ringkas
  • API yang jelas karena bidang singleton adalah final publik
  • Dapat digunakan dengan referensi metode
  • Dapat digunakan untuk menulis pabrik singleton generik
  • Dapat digunakan untuk mengembalikan instance terpisah untuk setiap pengguna