DEV Community 👩‍💻👨‍💻

José Miguel Álvarez Vañó
José Miguel Álvarez Vañó

Posted on • Updated on • Originally published at jmalvarez.dev

Factory Method pattern in TypeScript

Introduction

The factory method pattern is a creational pattern which provides an interface to create objects in a parent class. Subclasses of that class can override the method that creates the objects to change the type of object that will be created.

This way the creation of the objects is decoupled from the rest of the code.

Applicability

Use the factory method pattern when:

  • you cannot anticipate the type of the object that will be created
  • you want to localize the creation of the objects
  • you want to provide an easy way of extending the type of objects that can be created

Implementation

You can find the full example source code here.

Diagram of the factory method pattern. Credit: Vanderjoe - Wikimedia Commons

1. Define an abstract interface for the object that will be created.

In the example I'm going to handle payments so I'm going to use the following interface:

interface Payment {
  creditCard: number;
}
Enter fullscreen mode Exit fullscreen mode

2. Define an abstract class which will have an abstract method to create the objects. In this class you can include any common logic that the objects created by the subclasses will need.

In this example, createPayment is the abstract method that will be implemented by the subclasses. It could also be possible to have a default implementation of this method in the abstract class if you want to have some default setup.

Apart from this, I'm including the method completePayment which will be common to all the objects created by the subclasses.

abstract class PaymentGateway {
  public abstract createPayment(creditCard: number, ...args: any[]): Payment;

  public completePayment(payment: Payment): void {
    console.log(
      `Payment with credit card ${payment.creditCard} successfully completed using the PaymentGateway`
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Define concreted implementations of the object interface that was defined in the first step.

In the example I will have PhysicalPayment and OnlinePayment.

interface PhysicalPayment extends Payment {
  storeLocation: string;
}

interface OnlinePayment extends Payment {
  email: string;
}
Enter fullscreen mode Exit fullscreen mode

4. Finally, define the subclasses of the abstract class that will implement the createPayment method.

As I have two different object types, I will create two new classes: PhysicalPaymentGateway and OnlinePaymentGateway.

class PhysicalPaymentGateway extends PaymentGateway {
  public createPayment(
    creditCard: number,
    storeLocation: string
  ): PhysicalPayment {
    return {
      creditCard,
      storeLocation,
    };
  }
}

class OnlinePaymentGateway extends PaymentGateway {
  public createPayment(creditCard: number, email: string): OnlinePayment {
    return {
      creditCard,
      email,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

5. The factory method is now ready to be used.

An example of how client code would create objects for the online variant:

const onlinePaymentGateway = new OnlinePaymentGateway();
const onlinePayment = onlinePaymentGateway.createPayment(
  987654321,
  "test@example.com"
);
onlinePaymentGateway.completePayment(onlinePayment);
Enter fullscreen mode Exit fullscreen mode

An this is how the client would use the physical variant:

const physicalPaymentGateway = new PhysicalPaymentGateway();
const physicalPayment = physicalPaymentGateway.createPayment(
  123456789,
  "New York"
);
physicalPaymentGateway.completePayment(physicalPayment);
Enter fullscreen mode Exit fullscreen mode

Advantages

As always, make sure it makes sense to use this pattern in your application. Otherwise you could be introducing unnecessary complexity.

Resources

Top comments (0)

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

⭐️🎀 JavaScript Visualized: Promises & Async/Await

async await