MWAN MOBILE

×
mwan_logo
blog-banner

Javascript Design Patterns

UI/UX 19-Dec-2022

Singleton Pattern | Factory Pattern | Constructor Pattern

Topics

  • Singleton Pattern
  • Factory Pattern
  • Factory Pattern vs Constructor Pattern

Singleton Pattern (uses Redux or React Context)

  • Singleton design pattern exposes a single instance that can be used by multiple components
  • Singleton Pattern can be considered the basics of global state management libraries such Redux or React Context.
  • It is a pattern that restricts the class to create only one instance.
  • They can be accessed globally and acts as a single access point for accessing the global state.
let instance;
let globalState = {
color: ""
};

class StateUtility {
constructor() {
if (instance) {
throw new Error("New instance cannot be created!!");
}
instance = this;
}
getPropertyByName(propertyName) {
return globalState[propertyName];
}
setPropertyValue(propertyName, propertyValue) {
globalState[propertyName] = propertyValue;
}
}
let stateUtilityInstance = Object.freeze(new StateUtility());
export default stateUtilityInstance;
  • We make sure that we don’t expose the globalState. We expose them using the class methods of StateUtility. In this way, we protect the global state from being altered directly.
  • Finally, we create the instance of the class as follows: let stateUtilityInstance = Object.freeze(new StateUtility());.
  • We have used Object.freeze so that no other class/component/module is able to modify the exposed stateUtilityInstance.

Factory Pattern (Factory pattern vs Constructors) —

  • A factory is an object or class or a function in a functional programming paradigm for creating objects.
  • Factory Method creates new objects as instructed by the client. One way to create objects in JavaScript is by invoking a constructor function with the new operator
  • Factory design pattern is used when we have a superclass with multiple sub-classes and based on input, we need to return one of the sub-class. This pattern takes out the responsibility of the instantiation of a class from the client program to the factory class.
  • Classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate
  • Factory Methods are frequently used in applications that manage, maintain, or manipulate collections of objects that are different but at the same time have many characteristics (i.e. methods and properties) in common.
  • Factory pattern can use a constructor or class inside But the factory pattern is preferred in cases where the object creation process depends on dynamic factors — such as when we want to dynamically create subclasses.
  • Factory gives us an object creation mechanism that is both flexible and reusable.
// creates factory function
function vehicleFactory (manufacturer, plateNO) {
return {
manufacturer,
plateNO,
startEngine () {
console.log("reving engine")
},
drive () {
console.log("driving car...")
}
}
}

const Vehicle1 = vehicleFactory("Toyota", 12345);
console.log(Vehicle1)
// prints
//{
// manufacturer: 'Toyota',
// plateNO: 12345,
// startEngine: [Function: startEngine],
// drive: [Function: drive]
//}

const Vehicle2 = vehicleFactory("Ford", 13345);
console.log(Vehicle2)
// prints
// {
// manufacturer: 'Ford',
// plateNO: 13345,
// startEngine: [Function: startEngine],
// drive: [Function: drive]
// }

Examples,

  • Factory pattern promotes code reusability by reusing the StartEnginedriveVehicle, and stopEngine methods.
class Car {
constructor(options) {
this.wheels = options.wheels || 4;
this.doors = options.doors || 4;
this.color = options.color || "silver";
}
}

class Truck {
constructor(options) {
this.wheels = options.wheels || 6;
this.doors = options.doors || 2;
this.color = options.color || "red";
}
}


class Factory {

create = (options, vehicleType) => {

if(!vehicleType) {
return "unable to make vehicle. Please specify a vehicle type and tryagain!"
}

let vehicle;

if (vehicleType === "car") {
vehicle = new Car(options);
} else if (vehicleType === "truck") {
vehicle = new Truck(options);
}


vehicle.vehicleType = vehicleType;

vehicle.startEngine = ()=> console.log(`Reving ${vehicleType} engine`);

vehicle.driveVehicle = ()=> console.log(`Driving ${vehicleType}...`);

vehicle.stopEngine = ()=> console.log(`Stop ${vehicleType} engine`);

return vehicle;
}

};

const vehicleFactory = new Factory();

const car = vehicleFactory.create({
wheels: 4,
doors: 2,
color: "black",
}, "car");

console.log(car)
console.log(car.startEngine())
console.log(car.driveVehicle())

// prints:
//Car {
// wheels: 4,
// doors: 4,
// color: 'silver',
// vehicleType: 'car',
// startEngine: [Function],
// driveVehicle: [Function],
// stopEngine: [Function]
//}

// Reving car engine
// Driving car...

const truck = vehicleFactory.create({
wheels: 4,
doors: 2,
color: "yellow",
}, "truck")

console.log(truck)
console.log(truck.startEngine())
console.log(truck.stopEngine())
// prints
//Truck {
// wheels: 4,
// doors: 2,
// color: 'yellow',
// vehicleType: 'truck',
// startEngine: [Function],
// driveVehicle: [Function],
// stopEngine: [Function]
//}

// Reving truck engine
// Stop truck engine
  • By using the factory pattern our code is also easily maintainable, so if the business expands and the factory starts producing new kinds of vehicles, we can easily refractory our implementation to handle this as seen below:
class Car {
constructor(options) {
this.wheels = options.wheels || 4;
this.doors = options.doors || 4;
this.color = options.color || "silver";
}
}

class Truck {
constructor(options) {
this.wheels = options.wheels || 6;
this.doors = options.doors || 2;
this.color = options.color || "red";
}
}

class Bus {
constructor(options) {
this.wheels = options.wheels || 4;
this.doors = options.doors || 4;
this.color = options.color || "white";
}
}

class Motorcycle {
constructor(options) {
this.wheels = options.wheels || 2;
this.doors = options.doors || 0;
this.color = options.color || "Black";
}
}


class Factory {

create = (options, vehicleType) => {

if(!vehicleType) {
return "unable to make vehicle. Please specify a vehicle type and tryagain!"
}

let vehicle;

if (vehicleType === "car") {
vehicle = new Car(options);
} else if (vehicleType === "truck") {
vehicle = new Truck(options);
} else if (vehicleType === "bus") {
vehicle = new Bus(options);
} else if (vehicleType === "motorcycle") {
vehicle = new Motocycle(options);
}


vehicle.vehicleType = vehicleType;

vehicle.startEngine = ()=> console.log(`Reving ${vehicleType} engine`);

vehicle.driveVehicle = ()=> console.log(`Driving ${vehicleType}...`);

vehicle.stopEngine = ()=> console.log(`Stop ${vehicleType} engine`);

return vehicle;
}

};

const vehicleFactory = new Factory();

const bus = vehicleFactory.create({
wheels: 4,
doors: 4,
color: "yellow",
}, "bus");

console.log(bus)
console.log(bus.startEngine())
console.log(bus.driveVehicle())

// prints:
// Bus {
// wheels: 4,
// doors: 4,
// color: 'yellow',
// vehicleType: 'bus',
// startEngine: [Function],
// driveVehicle: [Function],
// stopEngine: [Function]
//}

// Reving bus engine
// Driving bus...

const bike = vehicleFactory.create({
wheels: 2,
doors: 0,
color: "red",
}, "motorcycle")

console.log(bike)
console.log(bike.startEngine())
console.log(bike.stopEngine())
// prints
// Motorcycle {
// wheels: 2,
// doors: 0,
// color: 'red',
// vehicleType: 'bike',
// startEngine: [Function],
// driveVehicle: [Function],
// stopEngine: [Function]
//}

// Reving motorcycle engine
// Stop motorcycle engine

More examples,

var Factory = function () {
this.createEmployee = function (type) {
var employee;

if (type === "fulltime") {
employee = new FullTime();
} else if (type === "parttime") {
employee = new PartTime();
} else if (type === "temporary") {
employee = new Temporary();
} else if (type === "contractor") {
employee = new Contractor();
}

employee.type = type;

employee.say = function () {
console.log(this.type + ": rate " + this.hourly + "/hour");
}

return employee;
}
}

var FullTime = function () {
this.hourly = "$12";
};

var PartTime = function () {
this.hourly = "$11";
};

var Temporary = function () {
this.hourly = "$10";
};

var Contractor = function () {
this.hourly = "$15";
};

function run() {

var employees = [];
var factory = new Factory();

employees.push(factory.createEmployee("fulltime"));
employees.push(factory.createEmployee("parttime"));
employees.push(factory.createEmployee("temporary"));
employees.push(factory.createEmployee("contractor"));

for (var i = 0, len = employees.length; i < len; i++) {
employees[i].say();
}
}

Factory pattern vs Constructors

  • The constructor pattern and factory pattern are similar because they are object creation patterns that return a new object
  • Factory method can return an already created object, unlike a constructor, which always creates a new instance.
  • Factory methods promote the idea of coding using Interface then implementation which results in more flexible code, but constructor ties your code to a particular implementation.
  • Reasons to use constructors: We use constructors to initialize the object with the default or initial state. The default values for primitives may not be what are you looking for. Another reason to use constructor is that it informs about dependencies.
  • Reason to use Factory: Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code.
const vehicleOptions = {type: "cars", color: "white", doors: 4, wheels: 4}

// factory pattern
function Factory(options) {
let factory = {};
factory.type = options.type;
factory.color = options.color;
factory.wheels = options.wheels;
factory.doors = options.doors;

return factory;
}

const vehicle = Factory(vehicleOptions);

// constructor pattern
function ConstructorPattern(options) {
this.type = options.type;
this.color = options.color
this.doors = options.doors;
this.wheels = options.wheels;
}

const vehicle2 = new ConstructorPattern(vehicleOptions);

console.log("factory car", vehicle)
// prints { type: 'cars', color: 'white', wheels: 4, doors: 4 }

console.log("constructor car", vehicle2)
// prints { type: 'cars', color: 'white', wheels: 4, doors: 4 }

When to Use the Factory Pattern

The Factory pattern can be especially useful when applied to the following situations:

  • When our object or component setup involves a high level of complexity.
  • When we need to easily generate different instances of objects depending on the environment we are in.
  • When we’re working with many small objects or components that share the same properties.
  • When composing objects with instances of other objects that need only satisfy an API contract (a.k.a., duck typing) to work. This is useful for decoupling.

Source: Levelup