Factory Method
A factory method in simple terms is a method for creating instances.
Definition
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
Frequency of use (in JavaScript):
●●●●●
medium high
⚡ I am intentionally not writing error handling code,to keep the discussion simple and to the point.
An important note
Please make a note that all patterns I will be using here will be demonstrated using core JavaScript language features only.
So it will not be a one on one mapping with original GOF’s c++ thought. But I will be leaving the code of original intention in the repository for reference only.
But don’t use it. Adapt the language specific implementation only, if you plan on adopting.
Motivation
- Object creation logic becomes too convoluted
- Initializer is not very descriptive
- Cannot overload with same sets of arguments with different names
- Can turn into optional parameter hell
- Offers a way for the customers of the factory to create objects without knowing the specific type (class) at compile time
- A separate method (Factory method)
- A separate class (Factory)
- Hierarchy of factories with Abstract Factory
What is a Factory?
A component/function responsible solely for the wholesale (not piecewise) creation of objects.
Applicability
Let’s take a look at where the factory method’s are useful.
- Toolkits and frameworks
- In MVC Frameworks
- Has some good uses with proxy pattern.
- Template methods uses factory methods
Classical Structure
NOTE: The Creator has a factory method as well it can abstract the creation by exposing additional method (anOperation-> which internally creates product and can work with it)
Participants
- Product
defines the interface of objects the factory method creates. - ConcreteProduct
implements the Product interface. - Creator
- declares the factory method, which returns an object of type Product. Creator may also define a default implementation of the factory method that returns a default ConcreteProduct object.
- may call the factory method to create a Product object.
- ConcreteCreator
overrides the factory method to return an instance of a ConcreteProduct.
Classical Structure Code (JavaScript)
Let’s take a look at the classical structure code. Now in vanilla JavaScript the Abstract Creator is not required due to dynamic nature of the language. But using TypeScript the scenario can be exactly simulated. But here in the first example, I will stick to vanilla JavaScript and in the implementation will remove the Abstract Creator.
class AbstractCreator {
constructor() {
// if (this.constructor === AbstractCreator) {
// throw new TypeError("Cannot construct abstract class.");
// }
}
factoryMethod() {
throw new TypeError("Do not call abstract method directly.");
}
// Some well established process/operation
anOperation() {
// Call the factoryMethod to create the product
let product = this.factoryMethod();
console.log("An internal process called: ");
// Do all well known things here which is common to all creators.
product.step1();
product.step2();
}
}
class AbstractProduct {
type = undefined
constructor() {
if (this.constructor === AbstractProduct) {
throw new TypeError("Cannot construct abstract class.");
}
}
step1() {
throw new TypeError("Do not call abstract method directly.");
}
step2() {
throw new TypeError("Do not call abstract method directly.");
}
}
class ConcreteProductA extends AbstractProduct {
constructor() {
super();
this.type = "TYPE A";
}
factoryMethod() {
return new ConcreteProductA();
}
step1() {
console.log(`Step1 processed for ${this.type}` );
}
step2() {
console.log(`Step2 processed for ${this.type}` );
}
}
class ConcreteProductB extends AbstractProduct {
constructor() {
super();
this.type = "TYPE B"
}
factoryMethod() {
return new ConcreteProductB();
}
step1() {
console.log(`Step1 processed for ${this.type}` );
}
step2() {
console.log(`Step2 processed for ${this.type}` );
}
}
class ConcreteCreatorA extends AbstractCreator {
factoryMethod() {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends AbstractCreator {
factoryMethod() {
return new ConcreteProductB();
}
}
Dry run
console.log("--->FACTORY METHOD DRY RUN");
let creators = [];
creators.push(new ConcreteCreatorA());
creators.push(new ConcreteCreatorB());
for(let creator of creators) {
// Call the method and you will get the product
let product = creator.factoryMethod();
// or call the anOperation
creator.anOperation(); // Well defined operators across creators
console.log(`
Created ${product.constructor.name}
of type ${product.type}`
);
}
console.log("<---FACTORY METHOD DRY RUN");
Classical Structure Code – Real Life
Assuming you are building a food delivery application (very simple one to understand structure).
Let’s apply the classical structure to this scenario. Let’s begin by creating our Abstract Creator.
AbstractFoodDelivery (Abstract creator)
class AbstractFoodDelivery {
constructor() {
}
deliveryPartner() {
throw new TypeError("Do not call abstract method directly.");
}
// Some well established process/operation
deliver() {
let partner = this.deliveryPartner();
console.log("Delivering food by " + partner.type);
partner.connect();
partner.payCommission();
}
}
AbstractDeliveryPartner (Abstract Product)
class AbstractDeliveryPartner {
type = undefined
constructor() {
if (this.constructor === AbstractProduct) {
throw new TypeError("Cannot construct abstract class.");
}
}
connect() {
throw new TypeError("Do not call abstract method directly.");
}
payCommission() {
throw new TypeError("Do not call abstract method directly.");
}
}
MumbaiPartner and ChennaiPartner (Concrete Product)
class MumbaiPartner extends AbstractDeliveryPartner {
constructor() {
super();
this.type = "Mumbai Partner";
}
connect() {
return new MumbaiPartner();
}
payCommission() {
console.log("Paying commission to " + this.type);
return 50;
}
}
class ChennaiPartner extends AbstractDeliveryPartner {
constructor() {
super();
this.type = "Chennai Partner"
}
connect() {
return new ChennaiPartner();
}
payCommission() {
console.log("Paying commission to " + this.type);
return 60;
}
}
MumbaiBranch and ChennaiBranch (Concrete Creators)
class MumbaiBranch extends AbstractFoodDelivery {
deliveryPartner() {
return new MumbaiPartner();
}
}
class ChennaiBranch extends AbstractFoodDelivery {
deliveryPartner() {
return new ChennaiPartner();
}
}
Dry Run (Client code)
// Dry run
let creator = new MumbaiBranch();
creator.deliver();
The expected output is
Delivering food by Mumbai Partner
Paying commission to Mumbai Partner
Classical Structure Code (TypeScript)
As TypeScript supports types the exact classical structure can be replicated as shown below.
abstract class AbstractCreator {
abstract factoryMethod(): AbstractProduct;
// Some well established process/operation
anOperation() {
// Call the factoryMethod to create the product
let product = this.factoryMethod();
console.log("An internal process called: ");
// Do all well known things here which is common to all creators.
product.step1();
product.step2();
}
}
abstract class AbstractProduct {
type: string|undefined;
abstract step1():void;
abstract step2():void;
}
class ConcreteProductA extends AbstractProduct {
constructor() {
super();
this.type = "TYPE A";
}
factoryMethod() {
return new ConcreteProductA();
}
step1() {
console.log(`Step1 processed for ${this.type}` );
}
step2() {
console.log(`Step2 processed for ${this.type}` );
}
}
class ConcreteProductB extends AbstractProduct {
constructor() {
super();
this.type = "TYPE B"
}
factoryMethod() {
return new ConcreteProductB();
}
step1() {
console.log(`Step1 processed for ${this.type}` );
}
step2() {
console.log(`Step2 processed for ${this.type}` );
}
}
class ConcreteCreatorA extends AbstractCreator {
factoryMethod() {
return new ConcreteProductA();
}
}
class ConcreteCreatorB extends AbstractCreator {
factoryMethod() {
return new ConcreteProductB();
}
}
Dry Run
// Dry run
console.log("--->FACTORY METHOD DRY RUN");
let creators = new Array<AbstractCreator>(2);
creators[0] = new ConcreteCreatorA();
creators[1] = new ConcreteCreatorB();
console.log("::CREATORS[ ]: ", creators);
for(let creator of creators) {
// Call the method and you will get the product
console.log("CREATORS: ", creator);
let product = creator.factoryMethod();
// or call the anOperation
creator.anOperation(); // Well defined operators across creators
console.log(`Created ${product.constructor.name} of type ${product.type}`
);
}
console.log("<---FACTORY METHOD DRY RUN");
Optimized Factory Structure for JavaScript
Here is a simplified concept diagram from JavaScript (Always remember to use the JavaScript features when implementing patterns).
Conceptual Code
Let’s create our so called abstract Product type (Use TypeScript for better workflow).
Abstract Product Class
Let’s churn out a quick abstract like psudo class for Product. ES6 doesn’t support abstract class yet. (Use TypeScript). We have one abstract method “getMake()” which we expect the derived class to implement.
class Product {
constructor() {
this.type = "Product";
if (this.constructor === Product) {
// Error Type 1. Abstract class can not be constructed.
throw new TypeError("Cannot construct abstract class.");
}
}
getMake() {
throw new TypeError("Do not call abstract method getMake from child.");
}
}
Let’s create concrete products.
Concrete Product 1
class Product1 extends Product {
constructor() {
super();
this.type = "Product1";
}
getMake() {
return this.type;
}
}
Concrete Product 2
class Product2 extends Product {
constructor() {
super();
this.type = "Product2";
}
getMake() {
return this.type;
}
}
Product Factory
Let’s create a basic product factory (A better version in in the practical example section). Recommendation, when build proof of concept, one off code, if/else, switch etc is OK. But when building production quality code try to avoid them or abstract them as much as possible.
class ProductFactory {
static getProduct(type) {
if (type === "product1") {
return new Product1();
} else if (type === "product2") {
return new Product2();
}
throw new Error("Product type undefined!");
}
}
Dry running/testing the code
let product1 = ProductFactory.getProduct("product1");
console.log(product1.getMake()); // Prints => product1
let product2 = ProductFactory.getProduct("product2");
console.log(product2.getMake()); // Prints => product2
Practical Example – Extensible Caching Library
Let’s design the structure for the design of an extensible cache library that follows, the OCP, SRP principles. Assume you have been tasked with a task to create a cache library that works with two storage types.
- In-Memory
- Disk
The management tells you that in near foreseeable future there should be provision to add more storage with minimum code change and regression. Let’s get the ball rolling, wear that “Architect Hat” 🎩 and begin the code layout for your awesome cache library.
As a good architect let’s put a small diagram in place for bird’s high view of the structure.
Concept Diagram
And now the code.
Cache Framework Code Structure
First let’s simulate an abstract base class. ES6 still doesn’t have abstract classes. Please feel free to use TypeScript.
Here is a poor man’s abstract base class in ES6. (May be once all patterns are covered will add TypeScript code as well)
Abstract Base Class – Cache
Here we define all the public methods that we expect the derived class to implement, for e.g. add, get, delete etc.
// Abstract
class Cache {
constructor() {
if (this.constructor === Cache) {
// Error Type 1. Abstract class can not be constructed.
throw new TypeError("Can not construct abstract class.");
}
}
add(key, value) {
throw new TypeError("Do not call abstract method add from child.");
}
get(key) {
throw new TypeError("Do not call abstract method key from child.");
}
remove(key) {
throw new TypeError("Do not call abstract method remove from child.");
}
values(fn) {
throw new TypeError("Do not call abstract method values from child.");
}
keys(fn) {
throw new TypeError("Do not call abstract method keys from child.");
}
}
Let’s begin with the implementation of InMemoryCache.
Concrete Class – InMemoryCache
The ImMemoryCache class inherits from the base Cache class and implements all the so called abstrat methods. The goal of InMemoryCache is to store all data within the running application (same process).
class InMemoryCache extends Cache {
constructor() {
super();
this.items = {};
this.count = 0;
}
// Add key/value pair
add(key, value) {
this.items[key] = value;
this.count++;
}
// Get the value by key
get(key) {
return this.items[key];
}
delete(key) {
delete this.items[key];
this.count--;
}
values(fn) {
for (let [key, value] of Object.entries(this.items)) {
fn(value);
}
}
keys(fn) {
for (let [key, value] of Object.entries(this.items)) {
fn(key);
}
}
}
Concrete Class – DiskCache
Here we are assuming we have an actual API to read/write to disk (but that is not the core of the discussion. The core is the abstraction.
const DISK_API = "put your api or if on node st up file system object api";
class DiskCache extends Cache {
constructor() {
super();
this.count = 0;
}
// Add key/value pair
add(key, value) {
DiSP_API.put(key, value);
this.count++;
}
// Get the value by key
get(key) {
return DiSP_API.get(key);;
}
delete(key) {
delete this.items[key];
DISK_API.delete(key);
}
values(fn) {
for (let [key, value] of Object.entries(DISK_API.get_items())) {
fn(value);
}
}
keys(fn) {
for (let [key, value] of Object.entries(DISK_API.get_items())) {
fn(key);
}
}
}
First Version of Factory
Now, let’s code a factory to create instances. The first version is typically what most people will code. But then you will observe that the code is very brittle, breaks OCP etc.
☠️ Beware (switch statements ahead). Don’t worry we will get rid of it.
class CacheFactory {
static getCache(type) {
switch(type) {
case "memory":
return new InMemoryCache();
break;
case "disk":
return new DiskCache();
break;
default:
return new InMemoryCache();
break;
}
}
}
Let’s see how to use this factory and create instances. Assume one part of the application needs to use “memory” cache.
let cacheMemory = CacheFactory.getCacheStore("memory");
console.log("Cache: ", cacheMemory);
cacheMemory.add("key1", "value1");
cacheMemory.get("key"); // Prints => value1
Ok. That works. But now assume some other part or some other application needs to use the “disk” storage.
let cacheDisk = CacheFactory.getCacheStore("disk");
console.log("Cache: ", cacheDisk);
cacheDisk.add("key3", "value3");
Problem with the above Factory
The above factory works, but has the following disadvantages.
- If a new store, say “webcache” has to be introduced, then you have to modify the factory code (It breaks Open Close Principle aka OCP)
- Switch statements tends to create bugs due to incorrect usage many times. Even, if you use if/else the code will become unmaintainable over a period of time.
Let’s fix the problem.
A Better Factory
Let’s take a look at a more concise version of the factory using JavaScript feature which allows to dynamically create instance (You can also use eval, but many people will say eval is bad. Take it with a pinch of salt)
class BetterCacheFactory {
// Creates and returns and instance of specified 'type'
// Returns the default InMemoryCache (if invalid type)
// You can throw exceptions or handle invalid type validation as needed
static getCacheStore(type) {
/* Need to add validations. Also, for now the the cache name to passed
exactly as the name of the class
(except don't prefix the Cache word.)
*/
if (type === undefined) return new InMemoryCache();
let obj = (Function('return new ' + type + "Cache"))();
return obj;
}
}
Please note that I have avoided putting in validations etc. to avoid polluting the core code logic.
The heart of the method is the following block of code which creates an instance of class dynamically. If you are using .NET(C#), you can use the Reflection API to achieve similar effect.
let obj = (Function('return new ' + type + "Cache"))();
Client Code
Lets see how the consumer of our library uses the code.
let cacheMemory = BetterCacheFactory.getCacheStore("InMemory");
console.log("Cache: ", cacheMemory);
let cacheDisk = BetterCacheFactory.getCacheStore("Disk");
console.log("Cache: ", cacheDisk);
Now, if future if you add any new type of storage none of the existing code is required to be changed. (OCP) but you can always create new types. Just follow the naming conventions any other convention that you deem fit as per your requirement.
Related Patterns + Principles
- Template Methods
- Abstract Factory
- Prototype
- Builder
Source Code
https://github.com/rajeshpillai/designpatterns-code-js
If you like the article, do take a moment to vote below.