Kanskje du har hørt at Singleton single malt skotsk whisky er bra? Vel, alkohol er dårlig for helsen din, så i dag vil vi fortelle deg om singleton-designmønsteret i Java i stedet.

Vi har tidligere gjennomgått opprettelsen av objekter, så vi vet at for å lage et objekt i Java, må du skrive noe sånt som:


Robot robot = new Robot(); 
    

Men hva om vi ønsker å sikre at bare én forekomst av klassen blir opprettet?

Den nye Robot() -setningen kan lage mange objekter, og ingenting stopper oss fra å gjøre det. Det er her singleton-mønsteret kommer til unnsetning.

Anta at du må skrive en applikasjon som vil koble til en skriver - bare EN skriver - og be den skrive ut:


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

Dette ser ut som en vanlig klasse... MEN! Det er ett "men": Jeg kan opprette flere forekomster av skriverobjektet mitt og kalle metoder på dem på forskjellige steder. Dette kan skade eller ødelegge skriveren min. Så vi må sørge for at det bare er én forekomst av skriveren vår, og det er det en singleton vil gjøre for oss!

Måter å lage en singleton på

Det er to måter å lage en singleton på:

  • bruk en privat konstruktør;
  • eksportere en offentlig statisk metode for å gi tilgang til en enkelt forekomst.

La oss først vurdere å bruke en privat konstruktør. For å gjøre dette, må vi erklære et felt som endelig i klassen vår og initialisere det. Siden vi merket den som endelig , vet vi at den vil være uforanderlig , dvs. at vi ikke lenger kan endre den.

Du må også erklære konstruktøren som privat for å forhindre å lage objekter utenfor klassen . Dette garanterer for oss at det ikke vil være andre forekomster av skriveren vår i programmet. Konstruktøren kalles bare én gang under initialisering og vil lage vår skriver :


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

Vi brukte en privat konstruktør for å lage en PRINTER-singleton - det vil bare være én forekomst. DeSKRIVERvariabelen har den statiske modifikatoren , fordi den ikke tilhører noe objekt, men til selve skriverklassen .

La oss nå vurdere å lage en singleton ved å bruke en statisk metode for å gi tilgang til en enkelt forekomst av klassen vår (og merk at feltet nå er privat ):


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

Uansett hvor mange ganger vi kaller getInstance()- metoden her, vil vi alltid få den sammeSKRIVERgjenstand.

Å lage en singleton ved hjelp av en privat konstruktør er enklere og mer kortfattet. Dessuten er API-en åpenbar, siden det offentlige feltet er erklært som endelig , noe som garanterer at det alltid vil inneholde en referanse til det samme objektet.

Alternativet for statisk metode gir oss fleksibiliteten til å endre singleton til en ikke-singleton klasse uten å endre API. GetInstance () -metoden gir oss en enkelt forekomst av objektet vårt, men vi kan endre det slik at det returnerer en separat forekomst for hver bruker som kaller det.

Det statiske alternativet lar oss også skrive en generisk singleton-fabrikk.

Den siste fordelen med det statiske alternativet er at du kan bruke det med en metodereferanse.

Hvis du ikke trenger noen av fordelene ovenfor, anbefaler vi å bruke alternativet som involverer et offentlig felt.

Hvis vi trenger serialisering, vil det ikke være nok å bare implementere Serializable- grensesnittet. Vi må også legge til readResolve- metoden, ellers får vi en ny singleton-forekomst under deserialisering.

Serialisering er nødvendig for å lagre tilstanden til et objekt som en sekvens av byte, og deserialisering er nødvendig for å gjenopprette objektet fra disse bytene. Du kan lese mer om serialisering og deserialisering i denne artikkelen .

La oss nå omskrive vår singleton:


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

Nå skal vi serialisere og deserialisere det.

Merk at eksemplet nedenfor er standardmekanismen for serialisering og deserialisering i Java. En fullstendig forståelse av hva som skjer i koden vil komme etter at du har studert "I/O-strømmer" (i Java Syntax-modulen) og "Serialisering" (i Java Core-modulen).

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

Og vi får dette resultatet:

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

Her ser vi at deserialisering ga oss et annet eksempel på vår singleton. For å fikse dette, la oss legge til readResolve- metoden til klassen vår:


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

Nå skal vi serialisere og deserialisere singletonen vår igjen:


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

Og vi får:

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

ReadResolve () -metoden lar oss få det samme objektet som vi deserialiserte, og forhindrer dermed opprettelsen av useriøse singletons.

Sammendrag

I dag lærte vi om singletons: hvordan lage dem og når de skal brukes, hva de er for, og hvilke alternativer Java tilbyr for å lage dem. De spesifikke egenskapene til begge alternativene er gitt nedenfor:

Privat konstruktør Statisk metode
  • Enklere og mer kortfattet
  • Åpenbart API siden singleton-feltet er offentlig endelig
  • Kan brukes med en metodereferanse
  • Kan brukes til å skrive en generisk singleton-fabrikk
  • Kan brukes til å returnere en separat forekomst for hver bruker