บางทีคุณอาจเคยได้ยินว่าซิงเกิลมอลต์สก๊อตช์วิสกี้ของ Singleton นั้นดี? แอลกอฮอล์ไม่ดีต่อสุขภาพของคุณ ดังนั้นวันนี้เราจะบอกคุณเกี่ยวกับรูปแบบการออกแบบซิงเกิลตันใน Java แทน

ก่อนหน้านี้เราได้ตรวจสอบการสร้างออบเจกต์ ดังนั้นเราจึงทราบว่าในการสร้างออบเจกต์ใน Java คุณต้องเขียนดังนี้:


Robot robot = new Robot(); 
    

แต่ถ้าเราต้องการให้แน่ใจว่ามีการสร้างคลาสเพียงหนึ่งอินสแตนซ์ล่ะ

คำ สั่ง Robot() ใหม่สามารถสร้างวัตถุได้มากมาย และไม่มีอะไรหยุดเราจากการทำเช่นนั้น นี่คือจุดที่รูปแบบซิงเกิลตันเข้ามาช่วย

สมมติว่าคุณต้องเขียนแอปพลิเคชันที่จะเชื่อมต่อกับเครื่องพิมพ์ — เครื่องพิมพ์เพียงเครื่องเดียว — และบอกให้พิมพ์:


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

ดูเหมือนเป็นคลาสธรรมดา...แต่! มีหนึ่ง "แต่": ฉันสามารถสร้างหลายอินสแตนซ์ของวัตถุเครื่องพิมพ์ของฉันและวิธีการเรียกในที่ต่างๆ สิ่งนี้อาจทำอันตรายหรือทำให้เครื่องพิมพ์ของฉันพังได้ ดังนั้นเราต้องแน่ใจว่าเครื่องพิมพ์ของเรามีอินสแตนซ์เพียงตัวเดียว และนั่นคือสิ่งที่ซิงเกิลตันจะทำเพื่อเรา!

วิธีสร้างซิงเกิลตัน

มีสองวิธีในการสร้างซิงเกิลตัน:

  • ใช้ตัวสร้างส่วนตัว
  • ส่งออกวิธีสแตติกสาธารณะเพื่อให้เข้าถึงอินสแตนซ์เดียว

ขั้นแรกให้พิจารณาการใช้ตัวสร้างส่วนตัว ในการทำเช่นนี้ เราจำเป็นต้องประกาศฟิลด์เป็นฟิลด์สุดท้ายในชั้นเรียนของเราและเริ่มต้นฟิลด์นั้น เนื่องจากเรากำหนดให้เป็นขั้นสุดท้ายเราจึงรู้ว่าจะไม่เปลี่ยนแปลง กล่าวคือเราไม่สามารถเปลี่ยนแปลงได้อีกต่อไป

คุณต้องประกาศตัวสร้างเป็นส่วนตัวเพื่อป้องกันการสร้างวัตถุนอกชั้นเรียน นี่เป็นการรับประกันสำหรับเราว่าจะไม่มีอินสแตนซ์อื่นของเครื่องพิมพ์ของเราในโปรแกรม ตัวสร้างจะถูกเรียกเพียงครั้งเดียวในระหว่างการเริ่มต้นและจะสร้างเครื่องพิมพ์ ของเรา :


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

เราใช้ตัวสร้างส่วนตัวเพื่อสร้าง PRINTER singleton — จะมีเพียงอินสแตนซ์เดียวเท่านั้น เดอะเครื่องพิมพ์ตัวแปรมีตัวแก้ไขแบบสแตติกเนื่องจากไม่ใช่ของวัตถุใด ๆ แต่เป็นของ คลาส Printerเอง

ตอนนี้ลองพิจารณาการสร้างซิงเกิลตันโดยใช้เมธอดแบบสแตติกเพื่อให้เข้าถึงอินสแตนซ์เดียวของคลาสของเรา (และโปรดทราบว่าฟิลด์นี้เป็นแบบส่วนตัว แล้ว ):


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

ไม่ว่าเราจะเรียกเมธอด getInstance()กี่ครั้งเราก็จะได้รับเหมือนเดิมเสมอเครื่องพิมพ์วัตถุ.

การสร้างซิงเกิลตันโดยใช้ ตัวสร้าง ส่วนตัวนั้นง่ายและกระชับกว่า ยิ่งไปกว่านั้น API ยังชัดเจน เนื่องจาก ฟิลด์ สาธารณะได้รับการประกาศเป็นขั้นสุดท้ายซึ่งรับประกันได้ว่าจะมีการอ้างอิงถึงวัตถุเดียวกันเสมอ

ตัวเลือกเมธอดสแตติกทำให้เรามีความยืดหยุ่นในการเปลี่ยนซิงเกิลตันเป็นคลาสที่ไม่ใช่ซิงเกิลตันโดยไม่ต้องเปลี่ยน API เมธอดgetInstance()ให้อินสแตนซ์เดียวของอ็อบเจ็กต์ของเรา แต่เราสามารถเปลี่ยนเพื่อให้ส่งคืนอินสแตนซ์แยกต่างหากสำหรับผู้ใช้แต่ละคนที่เรียกใช้

ตัวเลือกแบบสแตติกยังช่วยให้เราเขียนโรงงานซิงเกิลตันทั่วไปได้

ประโยชน์สุดท้ายของตัวเลือกแบบสแตติกคือคุณสามารถใช้กับการอ้างอิงเมธอดได้

หากคุณไม่ต้องการข้อดีใดๆ ข้างต้น เราขอแนะนำให้ใช้ตัวเลือกที่เกี่ยวข้องกับฟิลด์สาธารณะ

หากเราต้องการทำให้เป็นซีเรียลไลเซชัน การใช้อินเทอ ร์เฟซที่ ทำให้เป็น อนุกรมได้นั้นไม่เพียงพอ เราจำเป็นต้องเพิ่ม เมธอด readResolveด้วย มิฉะนั้น เราจะได้อินสแตนซ์ซิงเกิลตันใหม่ระหว่างการดีซีเรียลไลเซชัน

จำเป็นต้องทำให้ เป็นอนุกรมเพื่อบันทึกสถานะของวัตถุเป็นลำดับของไบต์ และ จำเป็นต้อง มีการดีซีเรียลไลเซชันเพื่อกู้คืนวัตถุจากไบต์เหล่านั้น คุณสามารถอ่านเพิ่มเติมเกี่ยวกับซีเรียลไลเซชันและดีซีเรียลไลเซชันได้ในบทความนี้

ตอนนี้มาเขียนซิงเกิลของเราใหม่:


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

ตอนนี้เราจะซีเรียลไลซ์และดีซีเรียลไลซ์

โปรดทราบว่าตัวอย่างด้านล่างเป็นกลไกมาตรฐานสำหรับการซีเรียลไลเซชันและดีซีเรียลไลเซชันใน Java ความเข้าใจอย่างสมบูรณ์เกี่ยวกับสิ่งที่เกิดขึ้นในโค้ดจะเกิดขึ้นหลังจากที่คุณศึกษา "I/O streams" (ในโมดูล Java Syntax) และ "Serialization" (ในโมดูล 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);
    

และเราได้ผลลัพธ์นี้:

Singleton 1 คือ: Printer@6be46e8f
Singleton 2 คือ: Printer@3c756e4d

ที่นี่เราเห็นว่าการดีซีเรียลไลเซชันทำให้เรามีซิงเกิลตันที่แตกต่างกัน ในการแก้ไขปัญหานี้ ให้เพิ่ม เมธอด readResolveให้กับคลาสของเรา:


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

ตอนนี้เราจะซีเรียลไลซ์ซิงเกิลตันของเราอีกครั้ง:


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

และเราได้รับ:

ซิงเกิลตัน 1 คือ: com.company.Printer@6be46e8f
ซิงเกิลตัน 2 คือ: com.company.Printer@6be46e8f

เมธอดreadResolve()ช่วยให้เราได้รับอ็อบเจกต์เดียวกันกับที่เราทำการดีซีเรียลไลซ์ ซึ่งจะเป็นการป้องกันการสร้างซิงเกิลตันอันธพาล

สรุป

วันนี้เราได้เรียนรู้เกี่ยวกับ singletons: วิธีสร้างและเวลาที่จะใช้ ใช้เพื่ออะไร และตัวเลือกใดบ้างที่ Java เสนอให้ในการสร้าง คุณสมบัติเฉพาะของทั้งสองตัวเลือกระบุไว้ด้านล่าง:

ตัวสร้างส่วนตัว วิธีคงที่
  • ง่ายและรัดกุมยิ่งขึ้น
  • API ที่ชัดเจนเนื่องจากฟิลด์ singleton เป็นสาธารณะขั้นสุดท้าย
  • ใช้ได้กับการอ้างอิงเมธอด
  • สามารถใช้เขียน Singleton Factory ทั่วไปได้
  • สามารถใช้เพื่อส่งคืนอินสแตนซ์แยกต่างหากสำหรับผู้ใช้แต่ละคน