Be careful about using singleton patterns in Javascript classes. It may have unexpected side-effects and can be the most dreaded evil overlord you encounter on that day.
Let us take this example of an Account class that is used by all logged-in users. Users set a role and are shown accounts based on the role.
"use strict";
const role = "mortal";
class Account {
getAccounts() {
console.log("this.role: ", this.role);
if (this.role == "mortal") return "mortal accounts";
else if (this.role == "manager")
return "hail manager..! here's all accounts";
}
setRole(roleVal) {
this.role = roleVal;
}
}
module.exports = new Account();
Let’s consider the below test class that calls the Account class.
const Account = require("./Account");
const mgr = Account;
const rep = Account;
mgr.setRole("manager");
// set role
console.log("mgr.getAccounts(): ", mgr.getAccounts());
// hail manager..! here's all accounts
console.log("rep.getAccounts(): ", rep.getAccounts());
// hail manager..! here's all accounts
// wrong role
Once the role is set, it is applicable for all instances of the class.
While the truly global behaviour is useful in a few cases (e.g. get database connection string), it will prove detrimental to business interests. After all, not every mortal can be hailed as manager.
What are the attributes of a singleton pattern?
How can I know whether the given class that I inherited from a big bad company is Singleton?
Apart from the code structure (which is evident), singletons also exhibit these strange behaviours -
accessible globally via a static field
created once, and being used over and over across classes, objects and methods
cannot be instantiated (meaning no
let account1 = new Account())does not allow classes to extend from the singleton class
cannot be used in interfaces
variable updates (mutations) can be dangerous and cannot be controlled easily when multiple entities want to update same value
What’s the alternative?
Normally you would want to write classes like this -
"use strict";
class Account {
role = "mortal";
constructor() {}
getAccounts() {
console.log("this.role: ", this.role);
if (this.role == "mortal") return "mortal accounts";
else if (this.role == "manager")
return "hail manager..! here's all accounts";
}
setRole(roleVal) {
this.role = roleVal;
}
}
module.exports = Account;
You can then invoke Account class like so -
// test.js
const Account = require("./Account");
const mgr = new Account();
const rep = new Account();
mgr.setRole("manager");
console.log("mgr.getAccounts(): ", mgr.getAccounts());
console.log("rep.getAccounts(): ", rep.getAccounts());
The variable values are local to the object and never mixed.
What if you need global variables?
Well, you can always use a single “instance” of the class, initiate values, and pass that object around using constructors and such. That is what smarter ones call “dependency injection”.
I can’t believe it. Are you saying singleton patterns do not have any use?
Yes and no. I do not use them since I don’t understand where they have to be used.
Seriously, though. No don’t use them- there are better options.