Singleton pattern is used when code requires only one instance of something at all times.
In that case, if an instance of an object exists, that instance needs to be returned and worked with.
If no object of that type has been instanciated yet, then it needs to be instanciated and then returned.
Implementation recipe:
- Add a private static field to hold the instance of the object.
- Make the constructor of the class private.
- Create a public static method to (create if does not exist and then) return the instance of the object.
- Replace all calls in existing code use the method to get the instance of an object.
The following example illustrates a ticket checker. Imagine you came to see a show,
and you give your ticket to any attendants in a booth to verify that your ticket is legitimate
— haven't been used yet. And for some reasons there is no database available for use.
Whoever shows up first and shows a ticket with a certain number, can come in. Anyone else who
tries to come in using a ticket that's already been verified cannot come in.
When a ticket is validated, it's being stored in an internal store (an array of strings),
and if there is more than one machine checking the tickets, they need to operate on the same
subset of data. A singleton is handy in this matter because it maintains one store, and
"opening" any other line would not help the matter because they all work with the same
data store.
class TicketChecker {
private static instance: TicketChecker; //field to hold the instance of the class
private ticketsInUse: string[] = [];
private constructor() {}; //private constructor
public static getInstance(): TicketChecker { //method to return an instance of the object
if (!TicketChecker.instance) {
TicketChecker.instance = new TicketChecker();
}
return TicketChecker.instance;
}
public validateTicket(ticketNumber: string): string {
if (this.ticketsInUse.indexOf(ticketNumber) >= 0)
return `Ticket '${ticketNumber}' has already been used.`;
this.ticketsInUse.push(ticketNumber);
return `Ticket '${ticketNumber}' has not been used yet. Welcome to the show!`;
}
public ticketIsAlreadyInUse(ticketNumber: string): string {
if (this.ticketsInUse.indexOf(ticketNumber) >= 0)
return `Ticket '${ticketNumber}' has already been used.`;
return `Ticket '${ticketNumber}' has not been used yet.`;
}
public displayUsedTickets(): string {
if (this.ticketsInUse.length === 0) return "none";
return this.ticketsInUse
.slice(1, this.ticketsInUse.length)
.reduce((prev, current) => prev + ", " + current, this.ticketsInUse[0]);
}
}
And here's some test code:
const tc1 = TicketChecker.getInstance();
console.log("--- Using TicketChecker1: ---");
console.log("Attempting to use ticket 'abc': ", tc1.validateTicket("abc"));
console.log("Attempting to use ticket 'def': ", tc1.validateTicket("def"));
console.log(
"Has ticket 'abc' been used already? ",
tc1.ticketIsAlreadyInUse("abc")
);
console.log(
"Has ticket 'def' been used already? ",
tc1.ticketIsAlreadyInUse("def")
);
const tc2 = TicketChecker.getInstance();
console.log("--- Using TicketChecker2: ---");
console.log(
"Has ticket 'abc' been used already? ",
tc1.ticketIsAlreadyInUse("abc")
);
console.log(
"Has ticket 'def' been used already? ",
tc1.ticketIsAlreadyInUse("def")
);
console.log("Attempting to use ticket 'ghi': ", tc2.validateTicket("ghi"));
console.log("--- Switching back to TicketChecker1: ---");
console.log(
"Has ticket 'ghi' been used already? ",
tc1.ticketIsAlreadyInUse("ghi")
);
console.log(
"--- Just for kicks, let's instantiate another TicketChecker and check status: ---"
);
const tc3 = TicketChecker.getInstance();
console.log("Validated tickets, as tc3 reports: ", tc3.displayUsedTickets());
console.log(
"Are ticket checkers point to the same instance? Is tc2 === tc3?",
tc3 === tc2
);
/*
output:
--- Using TicketChecker1: ---
Attempting to use ticket 'abc': Ticket 'abc' has not been used yet. Welcome to the show!
Attempting to use ticket 'def': Ticket 'def' has not been used yet. Welcome to the show!
Has ticket 'abc' been used already? Ticket 'abc' has already been used.
Has ticket 'def' been used already? Ticket 'def' has already been used.
--- Using TicketChecker2: ---
Has ticket 'abc' been used already? Ticket 'abc' has already been used.
Has ticket 'def' been used already? Ticket 'def' has already been used.
Attempting to use ticket 'ghi': Ticket 'ghi' has not been used yet. Welcome to the show!
--- Switching back to TicketChecker1: ---
Has ticket 'ghi' been used already? Ticket 'ghi' has already been used.
--- Just for kicks, let's instantiate another TicketChecker and check status: ---
Validated tickets, as tc3 reports: abc, def, ghi
Are ticket checkers point to the same instance? Is tc2 === tc3? true
*/
The code above demonstrates how a simple toy factory can make the only toy they know about -- a car. But what if they learned to create other toys?