Talán hallottad már, hogy a Singleton single malt skót whisky jó? Nos, az alkohol káros az egészségre, ezért ma inkább a Java-ban használt singleton tervezési mintáról fogunk beszélni.

Korábban áttekintettük az objektumok létrehozását, így tudjuk, hogy egy objektum Java nyelven történő létrehozásához valami ilyesmit kell írnia:


Robot robot = new Robot(); 
    

De mi van akkor, ha biztosítani akarjuk, hogy az osztályból csak egy példány jöjjön létre?

Az új Robot() utasítás sok objektumot hozhat létre, és ebben semmi sem akadályoz meg bennünket. Itt jön a megmentésre a singleton minta.

Tegyük fel, hogy írnod ​​kell egy alkalmazást, amely csatlakozik egy nyomtatóhoz – csak EGY nyomtatóhoz –, és meg kell mondanod, hogy nyomtasson:


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

Ez egy átlagos osztálynak tűnik... DE! Van egy "de": több példányt is létrehozhatok a nyomtatóobjektumomból, és különböző helyeken hívhatok rajtuk metódusokat. Ez károsíthatja vagy akár el is törheti a nyomtatómat. Gondoskodnunk kell tehát arról, hogy a nyomtatónknak csak egy példánya legyen, és ez az, amit egy singleton megtesz nekünk!

A szingli létrehozásának módjai

Kétféleképpen hozhat létre szinglit:

  • használjon magánépítőt;
  • exportál egy nyilvános statikus metódust, hogy hozzáférést biztosítson egyetlen példányhoz.

Először fontoljuk meg egy privát konstruktor használatát. Ehhez egy mezőt véglegesnek kell nyilvánítanunk az osztályunkban, és inicializálnunk kell. Mivel véglegesnek jelöltük , tudjuk, hogy megváltoztathatatlan lesz , azaz már nem tudjuk megváltoztatni.

A konstruktort privátként is deklarálnia kell, hogy megakadályozza az osztályon kívüli objektumok létrehozását . Ez garantálja számunkra, hogy nyomtatónknak nem lesz más példánya a programban. A konstruktort csak egyszer hívják meg az inicializálás során, és létrehozza a nyomtatónkat :


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

Privát konstruktort használtunk a PRINTER szingliton létrehozásához – mindig csak egy példány lesz. ANYOMTATÓváltozó rendelkezik a statikus módosítóval , mert nem tartozik egy objektumhoz, hanem magához a Printer osztályhoz.

Most fontoljuk meg, hogy statikus metódussal hozzunk létre egy szinglit, hogy hozzáférést biztosítsunk osztályunk egyetlen példányához (és vegye figyelembe, hogy a mező immár privát ):


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

Nem számít, hányszor hívjuk meg itt a getInstance() metódust, mindig ugyanazt kapjukNYOMTATÓtárgy.

A szingli létrehozása privát konstruktor segítségével egyszerűbb és tömörebb. Ráadásul az API nyilvánvaló, mivel a nyilvános mező véglegesnek van deklarálva , ami garantálja, hogy mindig ugyanarra az objektumra fog hivatkozni.

A statikus metódus opció lehetővé teszi számunkra, hogy az API-jának megváltoztatása nélkül módosítsuk az egytagú osztályt egy nem szingleton osztályúvá. A getInstance() metódus egyetlen példányt ad az objektumunkból, de megváltoztathatjuk úgy, hogy minden meghívó felhasználó számára külön példányt adjon vissza.

A statikus opció azt is lehetővé teszi, hogy általános singleton gyárat írjunk.

A statikus opció végső előnye, hogy metódushivatkozással is használhatja.

Ha nincs szüksége a fenti előnyök egyikére sem, akkor javasoljuk a nyilvános mezőt tartalmazó opció használatát.

Ha szerializálásra van szükségünk, akkor nem lesz elég a Serializable felület megvalósítása . A readResolve metódust is hozzá kell adnunk , különben a deserializálás során egy új singleton példányt kapunk.

Sorozatosításra van szükség az objektum állapotának bájtok sorozatakénti mentéséhez, és a szerializálásra az objektum ezekből a bájtokból történő visszaállításához. A szerializálásról és a deszerializálásról ebben a cikkben olvashat bővebben .

Most írjuk át a szingliünket:


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

Most szerializáljuk és deszerializáljuk.

Vegye figyelembe, hogy az alábbi példa a Java szerializálásának és deszerializálásának szabványos mechanizmusa. A kódban zajló események teljes megértése az "I/O adatfolyamok" (a Java szintaxis modulban) és a "szerializálás" (a Java Core modulban) tanulmányozása után következik be.

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

És ezt az eredményt kapjuk:

A Singleton 1 a következő: Printer@6be46e8f
A Singleton 2: Printer@3c756e4d

Itt azt látjuk, hogy a deserializáció egy másik példát adott a szingliunknak. Ennek kijavításához adjuk hozzá a readResolve metódust az osztályunkhoz:


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

Most ismét szerializáljuk és deszerializáljuk a szinglitonunkat:


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

És kapunk:

A Singleton 1 a következő: com.company.Printer@6be46e8f
A Singleton 2: com.company.Printer@6be46e8f

A readResolve() metódus lehetővé teszi, hogy ugyanazt az objektumot kapjuk meg, amelyet deszerializáltunk, ezáltal megakadályozva a hibás szinglitonok létrehozását.

Összegzés

Ma megismerkedtünk a singletonokkal: hogyan kell létrehozni és mikor kell használni, mire valók, és milyen lehetőségeket kínál a Java létrehozásukhoz. Mindkét lehetőség sajátosságait az alábbiakban ismertetjük:

Magán kivitelező Statikus módszer
  • Könnyebb és tömörebb
  • Nyilvánvaló API, mivel a singleton mező nyilvános végleges
  • Metódushivatkozással használható
  • Használható általános singleton gyár írásához
  • Használható egy külön példány visszaadására minden felhasználó számára