Design Patterns: Creational Design Patterns (JavaScript Implementation)
In programming, patterns refer to an existing template used to solve recurring problems.
Introduction
In programming, patterns refer to an existing template used to solve recurring problems. Notably, patterns are backed by solid programming principles deeming them fundamental in ensuring clean, maintainable, and scalable code. There exists three categories of design patterns including creational design patterns, structural design patterns, and behavioral design patterns. In this article, we will focus on creational design patterns.
Introduction to Creational Design Patterns
Creational design patterns as their name suggests refer to design patterns relied on to ensure optimized mechanisms of object creation. Creational design patterns provide a seamless model of object creation while promoting reusability and encapsulation. Existing creational design patterns include the Factory method, Abstract Factory Pattern, Singleton Pattern, Prototype Pattern, and Builder Pattern.
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
The Factory Pattern
As an object creation pattern, the Factory Pattern does not require the use of constructors. The pattern provides an interface for creating objects while allowing subclasses to alter the type of object created. A common implementation of a Factory Pattern is the use of a class or static methods of a class. The purpose of the Factory pattern is to abstract out repetitive operations when creating similar objects and to allow factory consumers to create objects without understanding the object internals.
class Car {
constructor(brand, make) {
this.brand = brand;
this.make = make;
}
}
class CarFactory {
createCar(brand,make) {
return new Car(brand, make);
}
}
const factory = new CarFactory()
const fordCar = factory.createCar("Ford", "Mustang");
console.log(fordCar);
//Car { brand: 'Ford', make: 'Mustang' }
In the above code block, we create a class Car that is then used to create a class CarFactory. The Factory class CarFactory instance is then used to create a new Car instance fordCar. fordCar consumes Car through CarFactory not needing to understand Car internals.
Abstract Factory Pattern
The Abstract Factory Pattern is a design pattern that provides an interface that lets you create families of related and dependent objects without specifying their concrete classes. For instance, a family of related objects could be a chair and a table. There exist multiple variants of these objects i.e. Victorian and Modern. So to create a family that can be used together, we need to create individual objects that match each other within the different variants.
Abstract Factory Pattern solves this challenge by suggesting that we declare interfaces that represent each distinct product family (variant i.e. modern). Following defining the interface for a specific item in the family, we set the other objects to follow the same interface within the same family.
// Abstract clas
class FurnitureFactory {
createSofa() {}
createTable() {}
}
//Concrete Factory
class ModernFurnitureFactory extends FurnitureFactory {
createSofa() {
console.log("create a Modern Sofa");
}
createTable() {
console.log("Created a Modern Table");
}
}
//Concrete Factory
class VintageFurnitureFactory extends FurnitureFactory {
createSofa() {
console.log("Created a Vintage Sofa");
}
createTable() {
console.log("Created a Vintage Table");
}
}
const vintageFurniture= new VintageFurnitureFactory();
const modernFurniture = new ModernFurnitureFactory();
const modernTable = modernFurniture.createTable();
const modernSofa = modernFurniture.createSofa();
const vintageTable = vintageFurniture.createTable();
const vintageSofa = vintageFurniture.createSofa();
/*
Created a Modern Table
create a Modern Sofa
Created a Vintage Table
Created a Vintage Sofa
*/
In the above code block, we are creating an abstract factory FurnitureFactory which defines the blueprint for creating concrete factories ModernFurniture and VintageFurniture. It has the methods createSofa() and createTable() which are then implemented by the concrete families to create distinct products.
The Abstract Factory class is used when one needs to create product families with similar functionalities yet different implementations, as is the case with the FurnitureFactory above.
The Singleton Pattern
The Singleton Pattern is used to ensure that a class only has a single instance, and can be accessed globally. Singletons are particularly important in managing the global application state. In such scenarios, only a single object is required to coordinate actions across the system. It is worth noting that the Singleton pattern is not modifiable. A common application of the Singleton pattern is in the state management of a React application. Changing the value of a variable updates all other elements consuming the value.
class Database {
constructor(data) {
if (Database.exists) {
return Database.instance;
}
this.data = data;
Database.instance = this;
Database.exists = true;
return this;
}
displayData() {
return this.data;
}
}
const database1 = new Database('Instance 1 Data');
console.log(database1.displayData());
const database2 = new Database('Instance 2 Data');
console.log(database2.displayData());
console.log(database1 === database2);
/*
Instance 1 Data
Instance 1 Data
true
*/
In the above snippet, we create a class Database whose constructor takes data, and its method displayData() returns the data. When we create instances of the class Database, both the first and the second instances; database1 and database2 return the first instance; Instance 1 Data.
Note: In the above code, we feed different data to the instances database1 and database2, yet the output is similar, data fed into database1. Once data is fed through the database1 instance, the data cannot be modified by a second instance. The strict equality operator is used to compare database1 and database2. The output being true confirms that indeed it is the same instance of the class Database.
Prototype Pattern
The Prototype pattern is a fundamental creational design pattern that is used to create new objects by copying existing object implementations. Prototype patterns can be considered prototypal inheritance in which we create objects that act as prototypes for other objects. The implication is the prototype object itself is used as the blueprint for all objects created by the constructor. The prototype pattern is a quick implementation of inheritance, and it allows sharing of properties among many objects.
class Car {
constructor(model, make) {
this.make = make;
this.model = model
}
drive() {
return "Vroom"
}
}
const fordCar = new Car("Ford", "Musang");
console.log(Car.prototype.drive());
console.log(fordCar.drive());
Car.prototype.break = () => console.log("Stop car");
fordCar.break();
In the above snippet, we create a class Car with a constructor function that accepts model and make. The class also has a method drive. We then create an instance of the Car class fordCar which accepts Ford and Mustang for the constructor values.
Note that we can log the result of calling the drive() method on the car class through the prototype object Car.prototype.drive(). We then extend the car prototype with the method break() which we can now call in the fordCar instance.
Builder Pattern
The Builder Design Pattern as a creational design pattern is used to create complex objects in steps by specifying the type and content only. The pattern separates the construction process from the object representation, making the process more manageable.
class Car {
constructor() {
this.car = {};
}
setMake(make) {
this.car.make = make;
return this;
}
setModel(model) {
this.car.model = model;
return this;
}
setYear(year) {
this.car.year = year;
return this;
}
build() {
return `We have built a ${this.car.make} ${this.car.model} made in ${this.car.year} `;
}
}
const fordCar = new Car()
.setMake('Ford')
.setModel('Mustang')
.setYear(1996)
.build();
console.log(fordCar);
//We have built a Ford Mustang made in 1996
In the above code block, we define a Car class which will be used to construct subsequent Car objects. The Car class has methods used to define the properties of the car being constructed such as make, model, and year. The build() method is used to retrieve the constructed Car. Builder Pattern provides method chaining provided through the use of this in the setter methods. When building a car instance, we create a new Car class instance and use method changing to set the properties. Finally, we call build to retrieve the constructed car.
Conclusion
Design Patterns are fundamental in building scalable and maintainable systems. It is important to evaluate suitable scenarios to ensure they call for the different design patterns as overuse can lead to over-engineering.