Marahil ay narinig mo na ang Singleton single malt Scotch whisky ay mabuti? Well, ang alkohol ay masama para sa iyong kalusugan, kaya ngayon ay sasabihin namin sa iyo ang tungkol sa singleton na pattern ng disenyo sa Java sa halip.

Sinuri namin dati ang paglikha ng mga bagay, kaya alam namin na upang lumikha ng isang bagay sa Java, kailangan mong magsulat ng isang bagay tulad ng:


Robot robot = new Robot(); 
    

Ngunit paano kung gusto nating tiyakin na isang instance lang ng klase ang nilikha?

Ang bagong Robot() na pahayag ay maaaring lumikha ng maraming bagay, at walang pumipigil sa atin na gawin ito. Ito ay kung saan ang singleton pattern dumating sa rescue.

Ipagpalagay na kailangan mong magsulat ng isang application na kumonekta sa isang printer — ISANG printer lamang — at sabihin itong mag-print:


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

Parang ordinaryong klase ito... PERO! Mayroong isang "ngunit": Maaari akong lumikha ng maraming mga pagkakataon ng aking printer object at mga pamamaraan ng tawag sa mga ito sa iba't ibang lugar. Maaari itong makapinsala o masira ang aking printer. Kaya kailangan nating tiyakin na mayroon lamang isang instance ng ating printer, at iyon ang gagawin ng isang singleton para sa atin!

Mga paraan upang lumikha ng singleton

Mayroong dalawang paraan upang lumikha ng singleton:

  • gumamit ng pribadong tagabuo;
  • mag-export ng pampublikong static na paraan upang magbigay ng access sa isang pagkakataon.

Isaalang-alang muna natin ang paggamit ng isang pribadong constructor. Para magawa ito, kailangan nating ideklara ang isang field bilang pinal sa ating klase at simulan ito. Dahil minarkahan namin ito bilang pangwakas , alam naming hindi na ito mababago , ibig sabihin, hindi na namin ito mababago.

Kailangan mo ring ideklara ang constructor bilang pribado upang maiwasan ang paglikha ng mga bagay sa labas ng klase . Tinitiyak nito para sa amin na wala nang iba pang mga pagkakataon ng aming printer sa programa. Isang beses lang tatawagin ang constructor sa panahon ng initialization at gagawa ng aming Printer :


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

Gumamit kami ng pribadong constructor para gumawa ng PRINTER singleton — magkakaroon lang ng isang instance. AngPRINTERAng variable ay may static na modifier , dahil hindi ito kabilang sa anumang bagay, ngunit sa mismong klase ng Printer .

Ngayon isaalang-alang natin ang paglikha ng isang singleton gamit ang isang static na paraan upang magbigay ng access sa isang solong instance ng aming klase (at tandaan na ang field ay pribado na ngayon ):


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

Gaano man karaming beses nating tawagan ang getInstance() na pamamaraan dito, palagi nating makukuha ang parehoPRINTERbagay.

Ang paggawa ng singleton gamit ang pribadong constructor ay mas simple at mas maigsi. Higit pa rito, ang API ay halata, dahil ang pampublikong field ay idineklara bilang final , na ginagarantiyahan na ito ay palaging naglalaman ng isang reference sa parehong bagay.

Ang opsyon na static na pamamaraan ay nagbibigay sa amin ng kakayahang umangkop upang baguhin ang singleton sa isang non-singleton na klase nang hindi binabago ang API nito. Ang getInstance() method ay nagbibigay sa amin ng isang instance ng aming object, ngunit maaari naming baguhin ito upang ito ay magbalik ng hiwalay na instance para sa bawat user na tumatawag dito.

Hinahayaan din kami ng static na opsyon na magsulat ng generic na singleton factory.

Ang panghuling benepisyo ng static na opsyon ay magagamit mo ito sa isang sanggunian ng pamamaraan.

Kung hindi mo kailangan ang alinman sa mga pakinabang sa itaas, pagkatapos ay inirerekomenda namin ang paggamit ng opsyon na nagsasangkot ng pampublikong larangan.

Kung kailangan namin ng serialization, hindi ito magiging sapat na ipatupad lamang ang Serializable interface. Kailangan din nating idagdag ang paraan ng readResolve , kung hindi, makakakuha tayo ng bagong singleton instance sa panahon ng deserialization.

Kailangan ang serialization upang i-save ang estado ng isang object bilang isang sequence ng mga byte, at kailangan ang deserialization upang maibalik ang object mula sa mga byte na iyon. Maaari kang magbasa nang higit pa tungkol sa serialization at deserialization sa artikulong ito .

Ngayon ay muling isulat natin ang ating singleton:


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

Ngayon ay gagawin natin itong serialize at deserialize.

Tandaan na ang halimbawa sa ibaba ay ang karaniwang mekanismo para sa serialization at deserialization sa Java. Ang kumpletong pag-unawa sa kung ano ang nangyayari sa code ay darating pagkatapos mong pag-aralan ang "I/O streams" (sa Java Syntax module) at "Serialization" (sa Java Core module).

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);
    

At nakuha namin ang resultang ito:

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

Dito natin makikita na ang deserialization ay nagbigay sa atin ng ibang instance ng ating singleton. Upang ayusin ito, idagdag natin ang paraan ng readResolve sa ating klase:


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; 
	} 
}
    

Ngayon ay muli naming i-serialize at i-deserialize ang aming singleton:


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); 
    

At nakukuha namin:

Ang Singleton 1 ay: com.company.Printer@6be46e8f
Ang Singleton 2 ay: com.company.Printer@6be46e8f

Ang readResolve() na pamamaraan ay nagbibigay-daan sa amin na makuha ang parehong bagay na aming na-deserialize, sa gayon ay pinipigilan ang paglikha ng mga rogue singleton.

Buod

Ngayon natutunan namin ang tungkol sa mga singleton: kung paano likhain ang mga ito at kailan gagamitin ang mga ito, para saan ang mga ito, at kung anong mga opsyon ang inaalok ng Java para sa paggawa ng mga ito. Ang mga partikular na tampok ng parehong mga pagpipilian ay ibinigay sa ibaba:

Pribadong tagabuo Static na pamamaraan
  • Mas madali at mas maigsi
  • Obvious na API dahil ang singleton field ay public final
  • Maaaring gamitin sa isang sanggunian ng pamamaraan
  • Maaaring gamitin para magsulat ng generic na singleton factory
  • Maaaring gamitin upang magbalik ng hiwalay na instance para sa bawat user