Mariusz Rajczakowski
Software Engineer
4 min read | 4 months ago

Factory Method Design Pattern

Factory Method Design Pattern

Factory Method is a creational design pattern which:

  • define an interface for creating an object
  • let subclasses decide which class to create

Factory Method allows to create object without exposing the creation logic to the client and refer to newly created object via common interface or abstract class signature.

What problems can factory solve?

When you have a logic for creating different types of objects in the client directly either as a switch or series of if/else if statement, code becomes less reusable and messy. Adding or removing types would require changes in the client code

What solution does the factory method design pattern describe?

It hides complexity of creating new objects and gives back the object which the client uses via ithe nterface, then adding/removing any types will not require client code to change.

Examples:

Procedural implementation with switch statement (breaking an open-close principle):

This the most simple implementation of factory method design pattern (often used as an introductory example).

The problem with it is, whenever a new concrete car implementation is added or removed then the factory class needs to be modified which violates open-close principle. Let's see the example:


interface Car {
   readonly drive: () => void;
}

class SportsCar implements Car {
  public drive(){
    console.log('driving sports car');
  }
}

class Ambulance implements Car {
  public drive(){
    console.log('driving ambulance');
  }
}

class CarFactory {
  createCar(type): Car{
    switch(type){
      case 'sports':
        return new SportsCar();
      case 'ambulance':
        return new Ambulance();
      //adding or removing new case will require class modification
      default: 
        throw new Error(`Unknown car type ${type}`);
   }
} 

//client code
const carFactory = new CarFactory();
const car: Car = carFactory('ambulance');
car.drive(); //prints driving ambulance

Cheese example with registering classes and not breaking the open-close principle:


abstract class Cheese { 
  public abstract stinks();
}

class Roquefort extends Cheese { 
  stinks() {
      console.log('stinks like Roquefort');
  } 
}

class Camembert extends Cheese { 
   stinks() {
      console.log('stinks like Camembert');
  }  
}

class CheeseFactory {
    private static registeredTypes = new Map();
    
    static register(classIndex, classValue) {
        if ((!CheeseFactory.registeredTypes.has(classIndex) &&
            classValue.prototype instanceof Cheese)) {
            CheeseFactory.registeredTypes.set(classIndex, classValue);
        }
    }

    static create(classIndex, ...options) {
        if (!CheeseFactory.registeredTypes.has(classIndex)) {
            throw new Error(`Class index ${classIndex} not found`);
        }
        let className = this.registeredTypes.get(classIndex);
        return new className(...options);
    }
}

CheeseFactory.register('roquefort', Roquefort);
CheeseFactory.register('camembert', Camembert);

const cheese: Cheese = CheeseFactory.create('camembert');
cheese.stinks(); // prints stinks like Camembert

Takeaway notes

When you notice in your code that you have a few objects of the same base type and manipulate them in very similar way, you might want to use a factory method pattern.

It will hide the complexity of creating new objects of this type and you would access them via common interface.

Simple usage with procedural switch cases implementation violates open-close principle and should be rather used only for demonstrating purposes. Instead you should use class registration implementation or abstact factory pattern.

Share:



Warning! This site uses cookies
By continuing to browse the site, you are agreeing to our use of cookies. Read our privacy policy