Mungkin anda pernah mendengar bahawa wiski Scotch singleton malt tunggal bagus? Nah, alkohol tidak baik untuk kesihatan anda, jadi hari ini kami akan memberitahu anda tentang corak reka bentuk tunggal di Jawa sebaliknya.

Kami sebelum ini menyemak penciptaan objek, jadi kami tahu bahawa untuk mencipta objek di Jawa, anda perlu menulis sesuatu seperti:


Robot robot = new Robot(); 
    

Tetapi bagaimana jika kita ingin memastikan bahawa hanya satu contoh kelas dibuat?

Pernyataan Robot() baharu boleh mencipta banyak objek, dan tiada apa yang menghalang kami daripada berbuat demikian. Di sinilah corak singleton datang untuk menyelamatkan.

Katakan anda perlu menulis aplikasi yang akan menyambung ke pencetak — hanya SATU pencetak — dan menyuruhnya mencetak:


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

Ini nampak macam kelas biasa... TETAPI! Terdapat satu "tetapi": Saya boleh mencipta beberapa contoh objek pencetak saya dan memanggil kaedah pada mereka di tempat yang berbeza. Ini boleh membahayakan atau merosakkan pencetak saya. Jadi kita perlu memastikan bahawa hanya terdapat satu contoh pencetak kita, dan itulah yang akan dilakukan oleh singleton untuk kita!

Cara untuk membuat singleton

Terdapat dua cara untuk membuat singleton:

  • gunakan pembina persendirian;
  • eksport kaedah statik awam untuk menyediakan akses kepada satu contoh.

Mari kita pertimbangkan dahulu menggunakan pembina persendirian. Untuk melakukan ini, kami perlu mengisytiharkan medan sebagai akhir dalam kelas kami dan memulakannya. Memandangkan kami menandakannya sebagai muktamad , kami tahu ia tidak boleh diubah , iaitu kami tidak boleh mengubahnya lagi.

Anda juga perlu mengisytiharkan pembina sebagai peribadi untuk mengelakkan penciptaan objek di luar kelas . Ini menjamin kami bahawa tidak akan ada contoh lain pencetak kami dalam program ini. Pembina akan dipanggil sekali sahaja semasa permulaan dan akan mencipta Pencetak kami :


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

Kami menggunakan pembina persendirian untuk mencipta satu PRINTER — hanya akan ada satu contoh sahaja. TheMESIN PENCETAKpembolehubah mempunyai pengubah suai statik , kerana ia bukan milik mana-mana objek, tetapi kelas Pencetak itu sendiri.

Sekarang mari kita pertimbangkan untuk mencipta singleton menggunakan kaedah statik untuk menyediakan akses kepada satu contoh kelas kami (dan ambil perhatian bahawa medan itu kini peribadi ):


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

Tidak kira berapa kali kita memanggil kaedah getInstance() di sini, kita akan sentiasa mendapat yang samaMESIN PENCETAKobjek.

Mencipta singleton menggunakan pembina persendirian adalah lebih mudah dan lebih ringkas. Lebih-lebih lagi, API adalah jelas, kerana medan awam diisytiharkan sebagai final , yang menjamin bahawa ia akan sentiasa mengandungi rujukan kepada objek yang sama.

Pilihan kaedah statik memberi kita fleksibiliti untuk menukar singleton kepada kelas bukan singleton tanpa mengubah APInya. Kaedah getInstance () memberi kita satu contoh objek kita, tetapi kita boleh mengubahnya supaya ia mengembalikan contoh berasingan untuk setiap pengguna yang memanggilnya.

Pilihan statik juga membolehkan kami menulis kilang tunggal generik.

Manfaat terakhir pilihan statik ialah anda boleh menggunakannya dengan rujukan kaedah.

Jika anda tidak memerlukan mana-mana kelebihan di atas, maka kami mengesyorkan menggunakan pilihan yang melibatkan medan awam .

Jika kita memerlukan penyirian, maka ia tidak mencukupi untuk hanya melaksanakan antara muka Serializable . Kami juga perlu menambah kaedah readResolve , jika tidak, kami akan mendapat contoh tunggal baharu semasa penyahserikatan.

Pensirian diperlukan untuk menyimpan keadaan objek sebagai jujukan bait, dan penyahsiran diperlukan untuk memulihkan objek daripada bait tersebut. Anda boleh membaca lebih lanjut tentang penyiaran dan penyahserilan dalam artikel ini .

Sekarang mari kita tulis semula 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 bersiri dan menyahsirikannya.

Ambil perhatian bahawa contoh di bawah ialah mekanisme piawai untuk bersiri dan penyahserikatan dalam Java. Pemahaman lengkap tentang perkara yang berlaku dalam kod akan datang selepas anda mempelajari "strim I/O" (dalam modul Sintaks Java) 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 mendapat keputusan ini:

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

Di sini kita melihat bahawa penyahserialisasian memberi kita contoh yang berbeza bagi singleton kita. Untuk membetulkannya, mari tambahkan kaedah readResolve ke kelas kami:


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 kami akan mensiri dan menyahsiri singleton kami sekali 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 mendapat:

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

Kaedah readResolve() membolehkan kami mendapatkan objek yang sama yang kami desiri, dengan itu menghalang penciptaan penyangak tunggal.

Ringkasan

Hari ini kita belajar tentang singleton: cara menciptanya dan masa untuk menggunakannya, untuk kegunaannya dan pilihan yang ditawarkan Java untuk menciptanya. Ciri khusus kedua-dua pilihan diberikan di bawah:

Pembina persendirian Kaedah statik
  • Lebih mudah dan ringkas
  • API yang jelas memandangkan medan tunggal adalah muktamad awam
  • Boleh digunakan dengan rujukan kaedah
  • Boleh digunakan untuk menulis kilang singleton generik
  • Boleh digunakan untuk mengembalikan contoh berasingan untuk setiap pengguna