The time has come for the first design pattern I have described, namely Singleton. It is a creative design pattern whose assumptions are very simple. Nevertheless, using this pattern we get a very convenient tool. Why? I encourage you to read the article.
Spis Treści
Let's build a Pattern from scratch
Let's start by presenting the very idea and the purpose of using this pattern. This pattern is to control that only one object of a given class is created during the operation of the application. The programmer does not have to focus on creating mechanisms referring to the same object by himself, because the class itself makes sure that no more objects are created.
By default, when creating an object, we use the constructor of a given class, which, even if we do not create it ourselves, is created by default. Usually, creating an object looks like this (I will describe patterns using Java as an example):
MyClass object = new Myclass();
To prevent the creation of many objects, let's start by prohibiting the possibility of creating them at all. How to do it? It is enough that we will not be able to use the constructor, i.e. we will change the access modifier from public to private. Then our MyClass class will look like this:
public class MyClass {
private static MyClass exampleObject;
private MyClass() {}
}
Okay, but why do I need a class from which you can't create any object at all…. Well, now it's time to implement a very important basic mechanism for creating an object based on a static method in our class.
public class MyClass {
private static MyClass exampleObject;
private MyClass() {}
public static MyClass getExampleObject(){
if (exampleObject == null) {
exampleObject = new MyClass();
}
return exampleObject;
}
}
The conditional statement tells us that if an object has been created, the method will return a ready, previously created object, and if it has not been created, it will create it. To use a class created in this way, it should be called through a static method as follows:
MyClass object = Myclass.getExampleObject();
At this point, it can already be said that the pattern has been implemented. To test it, we will implement additional methods in our class that increment the number, and then we will try to create several objects of our class and call the method of incrementing the number on them. Our class will be as follows:
public class MyClass {
private static MyClass exampleObject;
private int number;
private MyClass() {
number = 0;
}
public static MyClass getExampleObject() {
if (exampleObject == null) {
exampleObject = new MyClass();
}
return exampleObject;
}
public void incrementNumber() {
number++;
}
public int getNumber() {
return number;
}
}
Now let's use the created class as follows:
public class Main {
public static void main(String[] args) {
MyClass objectOne = MyClass.getExampleObject();
System.out.println("Wartosc z obiektu 1 wynosi " + objectOne.getNumber());
objectOne.incrementNumber();
System.out.println("Wartosc z obiektu 1 wynosi " + objectOne.getNumber());
MyClass objectTwo = MyClass.getExampleObject();
System.out.println("Wartosc z obiektu 2 wynosi " + objectTwo.getNumber());
objectTwo.incrementNumber();
System.out.println("Wartosc z obiektu 2 wynosi " + objectTwo.getNumber());
}
}
The program returns the following values:
Wartosc z obiektu 1 wynosi 0
Wartosc z obiektu 1 wynosi 1
Wartosc z obiektu 2 wynosi 1
Wartosc z obiektu 2 wynosi 2
The first call to object 1 creates an object for us, and then the incerementNumber () method performs an operation on the number. Trying to create a second object means that we do not get a new object with a zero value, but assign to the objectTwo object in fact the same object as in the case of objectOne. This solution makes it possible to easily use the same object in different places in the application, which significantly facilitates the creation of more flexible software.
Example of use
As an example, I wrote a simple console game where the application generates a random number from a set range. The user's task is to guess this number and enter it into the terminal. The game continues until the user guesses the drawn number. Then the application displays an appropriate message and indicates how many errors were made before the user correctly guessed the number.
The Zgadula class was created first. All application logic is stored in it. The class looks like this:
package com.company;
import java.util.Random;
public class Zgadula {
private final int randomNumber;
private boolean win;
//podaj minimalną i maksymalną liczbę z zakresu z którego ma być wylosowana liczba.
public Zgadula(int min, int max) {
win = false;
Random random = new Random();
randomNumber = random.nextInt(max - min + 1) + min;
}
//zwraca wylosowaną liczbę
public int getRandomNumber() {
return randomNumber;
}
public boolean isWin() {
return win;
}
//sprawdza czy podana liczba jest tą wylosowaną
public void checkNumber(int number) {
if (this.randomNumber == number) win = true;
else {
LicznikBledow licznik = LicznikBledow.getLicznik();
licznik.addMistake();
}
}
}
The constructor of this class takes two values: maximum and minimum from the range from which the number is to be drawn, and then assigns these values to the variable randomNumber. Two other methods are helper methods, but let's pay attention to the last method, checkNumber.
This method checks if the number given by the user is the same as the number randomNumber. If the number is correct then the wine flag is set to true. Otherwise, the counter object calls addMistake (). Note how the counter object is declared. Through the static method of the CounterBledow class. Note that the object is created only when the user makes a mistake. Let's move on to this class of the Bledow Counter, and it looks like this:
package com.company;
public class LicznikBledow {
private static LicznikBledow licznik;
private int numberOfMistakes;
//prywatny konstruktor uniemożliwiający utworzenie nowego obiektu wprost
private LicznikBledow() {
numberOfMistakes = 0;
}
//metoda statyczna, która tworzy i kontroluje ilość obiektów i ogranicza je do 1
public static LicznikBledow getLicznik() {
if (licznik == null) {
licznik = new LicznikBledow();
}
return licznik;
}
//inkrementuje ilosc bledow
public void addMistake() {
numberOfMistakes++;
}
//zwraca ilosc bledow
public int getNumberOfMistakes() {
return numberOfMistakes;
}
}
The most important in this class is the constructor, which is private, and the static method getCounter (), which controls the creation and return of the counter object. Right here we have implemented the singleton pattern. It is not possible to create more than one error counter (although that's not entirely true, but more on that later). Okay, but the question may arise why use this design pattern if you could create a regular Bledow Counter class with a public constructor if we only refer to the counter object once in the Guess class and declare it as follows:
package com.company;
public class LicznikZwykly {
private int numberOfMistakes;
//klasyczny publiczny konstruktor
public LicznikZwykly() {
numberOfMistakes = 0;
}
//inkrementuje ilosc bledow
public void addMistake() {
numberOfMistakes++;
}
//zwraca ilosc bledow
public int getNumberOfMistakes() {
return numberOfMistakes;
}
}
The code is shorter, simpler and seems cleaner. The answer can be seen when we implement our main to create the Zgadula class object. So, let's go:
package com.company;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Zgadula zgadula = new Zgadula(2, 6);
Scanner scanner = new Scanner(System.in);
while (!zgadula.isWin()) {
System.out.println("Podaj liczbe: ");
zgadula.checkNumber(scanner.nextInt());
}
LicznikBledow licznik = LicznikBledow.getLicznik();
System.out.println("BRAWO! Szukana liczba to " + zgadula.getRandomNumber());
System.out.println("Popelniles " + licznik.getNumberOfMistakes() + " bledow.");
}
}
After creating the object guessing and scanner for the purposes of reading data to the console, a loop was saved, which is broken only after the correct guessing of the drawn number. And now let's pay attention to the line:
LicznikBledow licznik = LicznikBledow.getLicznik();
It looks like creating a new object based on the CounterBledow class through a static method, but in fact we assign to the value a counter already created in the Guesses class to the object with information about the number of errors made during the game. An example game turn in the console then looks like this:
Podaj liczbe:
2
Podaj liczbe:
3
Podaj liczbe:
4
Podaj liczbe:
5
BRAWO! Szukana liczba to 5
Popelniles 3 bledow.
Notice how easy it is to refer to the previously created object based on the CounterBledow class. In this way, you can implement further classes, such as the menu of this application, where you could display, for example, the number of errors from the previously played round of the game. In the new class, we would create a new object to which we would actually reassign the previously created object via the static method. But it's simple and convenient, isn't it?
Summary
The wisely used singleton pattern can significantly make the programmer's work easier and simplify the application code. At one point I mentioned that despite the use of a mechanism that prevents the creation of more objects, this can happen. This can happen if the application is multi-threaded, then there is a risk of creating 2 or more singletons simultaneously. There are ways to prevent this, but it is a topic for a separate article that may be written one day.
I hope that I have introduced as much as possible the ideas of this simple but very useful template and maybe someone will get a revelation and will be convinced of its use in their application.