Classes were added to JavaScript in ES6 as a template for creating and managing objects in your application. They encapsulate data and the code inside each class helps us work with that data.
class Vehicle {
constructor(speed, weight) {
this.speed = speed;
this.weight = weight;
}
}
TypeScript is a superset of JavaScript which primarily provides optional static typing, classes and interfaces. Using TypeScript, you can convert the above example of Vehicle into
class Vehicle {
constructor(speed: number, weight: number) {
this.speed = speed;
this.weight = weight;
}
}
With that simplistic example, we know what classes are and what they help us with. But, you already knew this.
So, what are Singleton classes?
Singleton classes are a design pattern, popularly used in Java - and perhaps other languages as well. In object-oriented programming, a singleton class is a class that can have only one object (an instance of the class) at a time and provide your application a global point of access to that instance. Let me give you an example/use-case right after the basic syntax of writing singleton classes
class MySingletonClass {
private static INSTANCE: MySingletonClass;
private name: string;
private constructor() {
this.name = "noobmaster69";
}
public static getInstance(): MySingletonClass {
if (!this.INSTANCE) {
this.INSTANCE = new MySingletonClass();
}
return this.INSTANCE;
}
public getGreetingMessage(): string {
return `Hello, ${this.name}`;
}
}
Here, MySingletonClass
is a simple class that follows the singleton pattern with the help of the static members INSTANCE
and getInstance
. The INSTANCE
object is of type MySingletonClass
because it will can contain an instance of the class. The initiation of the object is done inside the getInstance
method. This is a personal preference but I like to make my constructors private so new objects of MySingletonClass
cannot be made outside the scope of the class.
Now that you have created your singleton class, how would you access the methods? You'd do it by
console.log(MySingletonClass.getInstance().getGreetingMessage());
That's all great, why would I use it?
That's a good question. Classes are essentially used to create new objects, meaning each object will encapsulate different - or even unique - data. Like mentioned at the beginning, singleton classes help us maintain a single instance of an object that can be accessed globally by different parts of your code. The best use-cases for singleton classes would be using them to maintain database services - which is the example I want to use. Here's a look at the code -
import mysql, { Connection, Query } from 'mysql';
import unnamed from 'named-placeholders';
const toUnnamed = unnamed();
export default class Mysql {
private connection!: Connection;
private static INSTANCE: Mysql;
public static getInstance(): Mysql {
if (!this.INSTANCE) {
this.INSTANCE = new Mysql();
}
return Mysql.INSTANCE;
}
private constructor() {
this.connection = mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_DB,
});
this.connect();
}
connect() {
this.connection.connect();
}
query(sqlString: string, values = []): Promise<Query> {
return new Promise(async (resolve, reject) => {
try {
return this.connection.query(sqlString, values, (err, results) => {
if (err) {
return reject(err);
}
return resolve(results);
});
} catch (error) {
reject(error);
}
});
}
namedQuery(sqlString: string, values: {}): Promise<Query> {
const [query, queryValues] = toUnnamed(sqlString, values);
return this.query(query, queryValues);
}
beginTransaction() {
return new Promise((resolve, reject) => {
try {
this.connection.beginTransaction((err) => {
if (err) {
reject(err);
} else {
resolve(true);
}
});
} catch (error) {
reject(error);
}
});
}
commit() {
return new Promise((resolve, reject) => {
try {
this.connection.commit();
resolve(true);
} catch (error) {
reject(error);
}
});
}
rollback() {
return new Promise((resolve, reject) => {
try {
this.connection.rollback();
resolve(true);
} catch (error) {
reject(error);
}
});
}
release() {
return new Promise((resolve, reject) => {
try {
if (this.connection) {
this.connection.end();
}
resolve(true);
} catch (error) {
reject(error);
}
});
}
}
If you were to use the above snippet for maintaining a connection to your MySQL database, your entire application would have access to the singular connection/instance that is required throughout the lifetime of your application, i.e. you won't have to create new objects each time you want to run MySQL queries in your application. To give you an example, if you wanted to run a MySQL query, you'd simple use
Mysql.getInstance().query(myQuery);
And, that's it! That's singleton classes for you - cleaner, meaner, and makes code maintenance simpler.
If you liked what you read, leave a <3 and a comment! :)
Top comments (0)