Måske har du hørt, at Singleton single malt skotsk whisky er god? Nå, alkohol er dårligt for dit helbred, så i dag vil vi fortælle dig om singleton-designmønsteret i Java i stedet for.

Vi har tidligere gennemgået oprettelsen af ​​objekter, så vi ved, at for at oprette et objekt i Java, skal du skrive noget som:


Robot robot = new Robot(); 
    

Men hvad nu hvis vi vil sikre, at der kun oprettes én forekomst af klassen?

Den nye Robot()- sætning kan skabe mange objekter, og intet forhindrer os i at gøre det. Det er her, singleton-mønsteret kommer til undsætning.

Antag, at du skal skrive et program, der vil oprette forbindelse til en printer - kun EN printer - og bede den udskrive:


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

Det her ligner en almindelig klasse... MEN! Der er et "men": Jeg kan oprette flere forekomster af mit printerobjekt og kalde metoder på dem forskellige steder. Dette kan skade eller endda ødelægge min printer. Så vi skal sikre os, at der kun er én forekomst af vores printer, og det er, hvad en singleton vil gøre for os!

Måder at skabe en singleton på

Der er to måder at oprette en singleton på:

  • brug en privat konstruktør;
  • eksportere en offentlig statisk metode for at give adgang til en enkelt instans.

Lad os først overveje at bruge en privat konstruktør. For at gøre dette skal vi erklære et felt som endeligt i vores klasse og initialisere det. Da vi har markeret det som endeligt , ved vi, at det vil være uforanderligt , dvs. vi kan ikke længere ændre det.

Du skal også erklære konstruktøren som privat for at forhindre oprettelse af objekter uden for klassen . Dette garanterer for os, at der ikke vil være andre forekomster af vores printer i programmet. Konstruktøren kaldes kun én gang under initialiseringen og vil oprette vores printer :


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

Vi brugte en privat konstruktør til at oprette en PRINTER-singleton - der vil kun være en enkelt forekomst. DetPRINTERvariabel har den statiske modifikator , fordi den ikke tilhører noget objekt, men til selve printerklassen .

Lad os nu overveje at oprette en singleton ved hjælp af en statisk metode for at give adgang til en enkelt forekomst af vores klasse (og bemærk, at feltet nu er privat ):


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

Uanset hvor mange gange vi kalder getInstance() metoden her, vil vi altid få den sammePRINTERobjekt.

At oprette en singleton ved hjælp af en privat konstruktør er enklere og mere kortfattet. Desuden er API'et indlysende, da det offentlige felt er erklæret som endeligt , hvilket garanterer, at det altid vil indeholde en reference til det samme objekt.

Den statiske metode giver os fleksibiliteten til at ændre singleton til en ikke-singleton klasse uden at ændre dens API. Metoden getInstance () giver os en enkelt forekomst af vores objekt, men vi kan ændre den, så den returnerer en separat forekomst for hver bruger, der kalder den.

Den statiske mulighed lader os også skrive en generisk singleton-fabrik.

Den sidste fordel ved den statiske mulighed er, at du kan bruge den med en metodehenvisning.

Hvis du ikke har brug for nogen af ​​ovenstående fordele, så anbefaler vi at bruge den mulighed, der involverer et offentligt felt.

Hvis vi har brug for serialisering, vil det ikke være nok kun at implementere Serializable- grænsefladen. Vi skal også tilføje readResolve- metoden, ellers får vi en ny singleton-instans under deserialisering.

Serialisering er nødvendig for at gemme et objekts tilstand som en sekvens af bytes, og deserialisering er nødvendig for at gendanne objektet fra disse bytes. Du kan læse mere om serialisering og deserialisering i denne artikel .

Lad os nu omskrive vores singleton:


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

Nu vil vi serialisere og deserialisere det.

Bemærk, at eksemplet nedenfor er standardmekanismen for serialisering og deserialisering i Java. En fuldstændig forståelse af, hvad der sker i koden, vil komme efter du har studeret "I/O-streams" (i Java Syntax-modulet) og "Serialisering" (i Java Core-modulet).

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 resultat:

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

Her ser vi, at deserialisering gav os et andet eksempel på vores singleton. For at løse dette, lad os tilføje readResolve- metoden til vores klasse:


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

Nu vil vi serialisere og deserialisere vores singleton igen:


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 lader os få det samme objekt, som vi deserialiserede, og forhindrer derved oprettelsen af ​​useriøse singletons.

Resumé

I dag lærte vi om singletons: hvordan man opretter dem og hvornår man bruger dem, hvad de er til, og hvilke muligheder Java tilbyder for at skabe dem. De specifikke funktioner ved begge muligheder er angivet nedenfor:

Privat konstruktør Statisk metode
  • Nemmere og mere kortfattet
  • Indlysende API, da singleton-feltet er offentligt endeligt
  • Kan bruges med en metodehenvisning
  • Kan bruges til at skrive en generisk singleton fabrik
  • Kan bruges til at returnere en separat instans for hver bruger