Belki de Singleton tek malt Scotch viskinin iyi olduğunu duymuşsunuzdur? Pekala, alkol sağlığınız için kötü, bu yüzden bugün size bunun yerine Java'daki singleton tasarım modelinden bahsedeceğiz.

Daha önce nesnelerin oluşturulmasını inceledik, bu nedenle Java'da bir nesne oluşturmak için aşağıdaki gibi bir şey yazmanız gerektiğini biliyoruz:


Robot robot = new Robot(); 
    

Ancak, sınıfın yalnızca bir örneğinin oluşturulmasını sağlamak istiyorsak ne olur?

Yeni Robot() ifadesi birçok nesne yaratabilir ve hiçbir şey bizi bunu yapmaktan alıkoyamaz. Singleton modelinin kurtarmaya geldiği yer burasıdır.

Bir yazıcıya - yalnızca BİR yazıcıya - bağlanacak bir uygulama yazmanız ve ona şunu yazdırmasını söylemeniz gerektiğini varsayalım:


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

Bu sıradan bir sınıf gibi görünüyor... AMA! Bir "ama" var: Yazıcı nesnemin birden çok örneğini oluşturabilir ve bunlar üzerinde farklı yerlerde yöntemler çağırabilirim. Bu, yazıcıma zarar verebilir ve hatta bozabilir. Bu nedenle, yazıcımızın yalnızca bir örneğinin olduğundan emin olmalıyız ve tek bir kopya bizim için bunu yapacaktır!

Bir singleton oluşturmanın yolları

Singleton oluşturmanın iki yolu vardır:

  • özel bir kurucu kullanın;
  • tek bir örneğe erişim sağlamak için genel bir statik yöntemi dışa aktarın.

Önce özel bir kurucu kullanmayı düşünelim. Bunu yapmak için sınıfımızda bir alanı final olarak ilan etmemiz ve onu başlatmamız gerekiyor . final olarak işaretlediğimiz için değişmez olacağını biliyoruz , yani artık değiştiremeyiz.

Ayrıca, sınıfın dışında nesneler oluşturulmasını önlemek için yapıcıyı özel olarak bildirmeniz gerekir . Bu, programda yazıcımızın başka hiçbir örneğinin olmayacağını garanti eder. Yapıcı, başlatma sırasında yalnızca bir kez çağrılacak ve Yazıcımızı oluşturacaktır :


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

Bir PRINTER singleton oluşturmak için özel bir oluşturucu kullandık — her zaman yalnızca bir örnek olacak. buYAZICIdeğişkeni statik değiştiriciye sahiptir , çünkü herhangi bir nesneye değil, Yazıcı sınıfının kendisine aittir.

Şimdi sınıfımızın tek bir örneğine erişim sağlamak için statik bir yöntem kullanarak bir tekil oluşturmayı düşünelim (ve alanın artık private olduğunu unutmayın ):


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

Burada getInstance() yöntemini ne kadar çağırırsak çağıralım , hep aynı sonucu elde edeceğiz.YAZICInesne.

Özel bir oluşturucu kullanarak bir tekil oluşturmak daha basit ve daha özlüdür. Dahası, ortak alan final olarak bildirildiğinden API açıktır , bu da her zaman aynı nesneye bir başvuru içereceğini garanti eder.

Statik yöntem seçeneği bize API'sini değiştirmeden singleton'ı singleton olmayan bir sınıfa değiştirme esnekliği sağlar. getInstance () yöntemi bize nesnemizin tek bir örneğini verir, ancak onu çağıran her kullanıcı için ayrı bir örnek döndürecek şekilde değiştirebiliriz.

Statik seçeneği aynı zamanda jenerik bir singleton fabrika yazmamıza izin verir.

Statik seçeneğin son yararı, onu bir yöntem referansıyla kullanabilmenizdir.

Yukarıdaki avantajlardan herhangi birine ihtiyacınız yoksa, ortak alan içeren seçeneği kullanmanızı öneririz.

Serileştirmeye ihtiyacımız varsa, sadece Serializable arayüzünü uygulamak yeterli olmayacaktır . Ayrıca readResolve yöntemini de eklememiz gerekiyor , aksi takdirde seri hale getirme sırasında yeni bir tekil örnek elde ederiz.

Bir nesnenin durumunu bir bayt dizisi olarak kaydetmek için seri hale getirme gerekir ve nesneyi bu baytlardan geri yüklemek için seri hale getirme gerekir. Bu makalede seri hale getirme ve seri hale getirme hakkında daha fazla bilgi edinebilirsiniz .

Şimdi singleton'umuzu yeniden yazalım:


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

Şimdi seri hale getireceğiz ve serisini kaldıracağız.

Aşağıdaki örneğin, Java'da serileştirme ve seriden kaldırma için standart mekanizma olduğunu unutmayın. "G/Ç akışları" (Java Sözdizimi modülünde) ve "Serileştirme" (Java Core modülünde) çalıştıktan sonra kodda neler olup bittiğinin tam olarak anlaşılması sağlanacaktır.

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

Ve şu sonucu elde ederiz:

Tekil 1: Yazıcı@6be46e8f
Tekil 2: Yazıcı@3c756e4d

Burada seri kaldırmanın bize singleton'umuzun farklı bir örneğini verdiğini görüyoruz. Bunu düzeltmek için sınıfımıza readResolve yöntemini ekleyelim :


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

Şimdi singleton'umuzu tekrar seri hale getireceğiz ve serisini kaldıracağız:


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

Ve şunu elde ederiz:

Tekil 1: com.company.Printer@6be46e8f
Tekil 2: com.company.Printer@6be46e8f

readResolve () yöntemi, seri durumundan çıkardığımız aynı nesneyi almamıza izin verir, böylece sahte tekillerin oluşturulmasını önler.

Özet

Bugün singleton'ları öğrendik: bunların nasıl oluşturulacağı ve ne zaman kullanılacağı, ne için oldukları ve Java'nın bunları oluşturmak için hangi seçenekleri sunduğu. Her iki seçeneğin de belirli özellikleri aşağıda verilmiştir:

Özel inşaatçı Statik yöntem
  • Daha kolay ve daha özlü
  • Singleton alanı genel final olduğundan bariz API
  • Bir yöntem referansı ile kullanılabilir
  • Genel bir tekil fabrika yazmak için kullanılabilir
  • Her kullanıcı için ayrı bir örnek döndürmek için kullanılabilir