Sep 24, 2019

Factory Method of Creational Patterns


Sometimes when we have to create different instances and doing it with a basic form, could add complexity to the design. In those case, the creational patterns are used to control the creation process and hide the complexity at the same time.

Situation

Consider we are building an application to a Car Assembler Factory, which has to assemble the cars from different parts and they need to trade several models. For this example, the parts will be the Tires and the Engine but we need to take growth and diversity into account.

Description

In Factory pattern, we create object without exposing the creation logic to the client and refer to newly created object using a common interface.

UML Diagram

src: https://www.dofactory.com/net/factory-method-design-pattern

Participants

The classes and interfaces participating in this pattern are:

  • Product as IAutomobile: defines the interface of objects the factory method creates.
  • ConcreteProduct as Car and ElectricCar: implements the Product interface.
  • Creator as CarFactory: 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.
  • ConcreteCreator as ElectrictCar: overrides the factory method to return an instance of a ConcreteProduct.

Implementation

Let's start creating the product interface, in this case the products would be our cars. We have to specify the common properties that might have every model we’ll assemble. At the end, they’re all automobiles:

interface IAutomobile {
  tires: Tire[];
  engine: Engine;
}

Next, we build the class product (Car) extending the product interface, and each component (Tire and Engine) which belong to the product as classes.

class Car implements IAutomobile {
    constructor(public tires: Tire[], public engine: Engine) { }
}

class Tire {
    constructor(public brand: string) { }
}

class Engine {
    constructor(public cylinder: number) { }
}

These components are using a typescript feature for declaring properties with their level of access in the constructor.

Now, we have to create the default implementation of the CarFactory class.

class CarFactory {
  buildCar(): Car {}
  joinTires(): Tire[] {}
  createEngine(): Engine {}
}

Let's start to dive into the methods which create the components need for the build of an average car.

joinTires(): Tire[] {
    let tireSet = []

    for (let i = 0; i < 4; i++) {
        tireSet.push(new Tire("Generic"))
    }

    return tireSet
}

createEngine(): Engine {
    return new Engine(4)
}

The joinTires method iterate 4 times adding a generic tire object to an array and return it after. Meanwhile, createEngine return a simple instance of an Engine with 4 cylinders.

After that, we put them into the buildCar method:

buildCar(): IAutomobile {

    const tires = this.joinTires()
    const engine = this.createEngine()

    return new Car(tires, engine);
}

Now, to build a car we just have to instantiate the CarFactory class and call its buildCar method:

const factory = new CarFactory()

const car = factory.buildCar()

The implementation of this pattern allows us create a new instance without the use of the new keyword operator. At first glance, this maybe seem useless but now we can override the method and change the returned class based on the product interface implemented.

In case we have to create another model, like an electric car model, it must have obviously an electric engine.

To achieve this, we just have to implement the IAutomobile interface to this new one product.

class ElectricCar implements IAutomobile {
    constructor(public tires: Tire[], public engine: Engine) { }
}

As we all know, the electric cars not use cylinders in their engine, so we should create a new class called ElectricEngine. This new class will be slightly different from its parent class Engine.

class ElectricEngine extends Engine {
  constructor(batteryAh: number) {
    super(0)
  }
}

Here, we change the constructor parameter with the battery Ah consume and using the super method to pass this value to the parent constructor.

Next, we extend from the factory class to override the factory method and others we need (as createEngine).

class ElectricCarFactory extends CarFactory {
  createEngine() {
    return new ElectricEngine(3.8)
  }

  buildCar() {
    const tires = this.joinTires()
    const engine = this.createEngine()

    return new ElectricCar(tires, engine)
  }
}

Mission accomplished, we can use another class with the same factory method to create a familiar product and keep the application design scalable.

const factory = new ElectricCarFactory()

const car = factory.buildCar()

You can check the full code example here

Consequence

In the preceding implementation, the factory method buildCar is who handle every step in the car assemble, but what about we have another model and it need an additional component? Fortunately, we have the same structure with the same outline, but that won't always happen.

When should be used?

There are a lot of situations of using it:

  • when you don’t know beforehand the exact types and dependencies of the objects your code should work with.
  • when you want to provide users of your library or framework with a way to extend its internal components.

Comment bellow what is your ideal scenario to use the Factory Method pattern.

Factory Method of Creational Patterns
Commentarios