JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.
To organize our code, we should use some basic design patterns. In this article, we’ll look at some basic object creation patterns that we may use.
Singleton
The singleton pattern is where we create a single instance of a class.
Since we can create object literals without a constructor in JavaScript, we can create an object easily as follows:
const obj = {
foo: 'bar'
};
obj
will also be a standalone object, so defining an object literal follows the singleton pattern.
If we create a new object that has the same structure, we’ll get a different object.
For instance, if we create a new object:
const obj2 = {
foo: 'bar'
};
Then when we write:
obj2 === obj
to compare 2 objects, then that’ll return false
since they’re different references.
Using new
We can also create a singleton object even if we create them from constructors or classes.
For instance, we can write:
let instance;
class Foo {
constructor() {
if (!instance) {
instance = {
foo: 'bar'
};
}
return instance;
}
}
Then if we create 2 Foo
instances:
const foo1 = new Foo();
const foo2 = new Foo();
Then when we compare them:
console.log(foo1 === foo2);
We see true
logged.
We check if the instance
is created and then return it as is if it is. Otherwise, we set it to an object.
Since instance
won’t change after it’s set, all instances are the same.
We can make the instance
variable private by putting it in an IIFE.
For instance, we can write:
const Foo = (() => {
let instance;
return class {
constructor() {
if (!instance) {
instance = {
foo: 'bar'
};
}
return instance;
}
}
})()
Now we can only access the returned instance rather than looking at the constructor.
An Instance in a Static Property
Alternatively, we can put the instance of the singleton in a static property of the constructor or class.
For instance, we can write:
class Foo {
constructor() {
if (!Foo.instance) {
Foo.instance = {
foo: 'bar'
};
}
return Foo.instance;
}
}
This way, we set the public instance
property of the class Foo
to return the instance if it exists. Otherwise, it’ll create it first.
Now if we write:
const foo1 = new Foo();
const foo2 = new Foo();
console.log(foo1 === foo2);
We get the same result as before.
Instance in a Closure
We can also put the singleton instance in a closure.
For instance, we can write:
function Foo() {
const instance = this;
this.foo = 'bar';
Foo = function() {
return instance;
}
}
We can write a constructor function that gets reassigned to itself at the end of the function.
This will let us return the instance
while using this
to assigning instant variables.
This won’t work with the class syntax since it’s created as a constant, so we can’t reassign it to a new value.
Factory
We can create a factory function to create an object.
A factory function is just a regular function that we can use to create objects.
It’s useful for performing repeated operations when setting up similar objects.
It also offers a way for users of the factory to create objects without the specific class at compile time.
For instance, we can create our own factory function as follows:
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Bird extends Animal {}
const AnimalMaker = type => {
if (type === 'dog') {
return new Dog()
} else if (type === 'cat') {
return new Dog()
} else if (type === 'bird') {
return new Dog()
}
}
The code above has the AnimalMaker
factory function.
It takes the type
parameter which allows us to create different subclasses given the type
.
So we can call it as follows:
const animal = AnimalMaker('dog');
to create a Dog
instance.
We can also add methods as we wish into the classes:
class Animal {
walk() {}
}
class Dog extends Animal {
bark() {}
}
class Cat extends Animal {}
class Bird extends Animal {}
const AnimalMaker = type => {
if (type === 'dog') {
return new Dog()
} else if (type === 'cat') {
return new Dog()
} else if (type === 'bird') {
return new Dog()
}
}
This will also work.
Conclusion
We can create objects with a single instance y using object literals or checking if the instance of the class exists and create it if it doesn’t exist.
Factory functions are useful for creating similar objects.