Skip to main content

Factory Pattern Real World Implementation

Imagine you're building an e-commerce platform that supports multiple types of payments, such as PayPal, Stripe, and Bank Transfer. Each of these payment types have their own ways of processing payments but they all share a common goal: to process a payment.

Real World Implementation

Each of these payment types have their own ways of processing payments but they all share a common goal: to process a payment.

First, let's create a base PaymentProcessor class:

abstract class PaymentProcessor {
constructor(public amount: number) {}

abstract processPayment(): void;
}

Now, we define the specific payment processors:

class PaypalProcessor extends PaymentProcessor {
processPayment() {
// Insert PayPal-specific payment processing logic here.
console.log(`Processing PayPal payment of $${this.amount}`);
}
}

class StripeProcessor extends PaymentProcessor {
processPayment() {
// Insert Stripe-specific payment processing logic here.
console.log(`Processing Stripe payment of $${this.amount}`);
}
}

class BankTransferProcessor extends PaymentProcessor {
processPayment() {
// Insert bank transfer-specific payment processing logic here.
console.log(`Processing Bank Transfer payment of $${this.amount}`);
}
}

Next, we create our PaymentProcessorFactory to generate the specific type of payment processor:

class PaymentProcessorFactory {
public createProcessor(type: string, amount: number): PaymentProcessor {
switch (type) {
case "Paypal":
return new PaypalProcessor(amount);
case "Stripe":
return new StripeProcessor(amount);
case "BankTransfer":
return new BankTransferProcessor(amount);
default:
throw new Error("Invalid payment processor type");
}
}
}

Finally, we can use our PaymentProcessorFactory:

const paymentProcessorFactory = new PaymentProcessorFactory();

// Use the factory to create a PayPal payment processor
const paypalProcessor = paymentProcessorFactory.createProcessor("Paypal", 100);
paypalProcessor.processPayment();
// Outputs: Processing PayPal payment of $100

// Use the factory to create a Stripe payment processor
const stripeProcessor = paymentProcessorFactory.createProcessor("Stripe", 200);
stripeProcessor.processPayment();
// Outputs: Processing Stripe payment of $200

// Use the factory to create a Bank Transfer payment processor
const bankTransferProcessor = paymentProcessorFactory.createProcessor(
"BankTransfer",
300
);
bankTransferProcessor.processPayment();
// Outputs: Processing Bank Transfer payment of $300

In this real world example, you can see how the Factory pattern is used to simplify the creation of different types of payment processors. The Factory Pattern makes it easy to add new types of payment processors in the future, without having to change the client code.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

Advantages Of The Factory Design Pattern

The Factory Design Pattern provides several advantages, especially in cases where a system needs to be independent from how its objects are created and composed. Here are a few key advantages:

Decoupling

The Factory Pattern decouples the client code in the application from the concrete classes that are instantiated. This means that the client code does not need to know anything about the concrete classes, which leads to less coupling between different parts of an application.

For instance, in our example, the client code doesn't need to know anything about how different payment processors are instantiated. It only needs to call the factory's createProcessor method.

const paymentProcessorFactory = new PaymentProcessorFactory();
const paypalProcessor = paymentProcessorFactory.createProcessor("Paypal", 100);

Flexibility

Factory Pattern provides flexibility when adding new types of objects. If we need to add a new payment processor in the future, we can simply add a new class for it and update the factory without affecting the existing client code.

For instance, if we were to add a new payment processor, like GooglePayProcessor, we would only need to update our factory:

class GooglePayProcessor extends PaymentProcessor {
processPayment() {
console.log(`Processing Google Pay payment of $${this.amount}`);
// Insert Google Pay-specific payment processing logic here.
}
}

class PaymentProcessorFactory {
public createProcessor(type: string, amount: number): PaymentProcessor {
switch (type) {
// other cases...
case "GooglePay":
return new GooglePayProcessor(amount);
default:
throw new Error("Invalid payment processor type");
}
}
}

Encapsulation

The Factory Pattern encapsulates the details of object creation. The Factory is responsible for knowing which concrete classes the system can instantiate, and how to instantiate them.

In the Payment Processor example, the PaymentProcessorFactory encapsulates how a payment processor is created based on the type:

class PaymentProcessorFactory {
public createProcessor(type: string, amount: number): PaymentProcessor {
switch (type) {
case "Paypal":
return new PaypalProcessor(amount);
// other cases...
default:
throw new Error("Invalid payment processor type");
}
}
}

Each of these advantages leads to code that is more maintainable and easier to extend, making the Factory Pattern a common choice in many large applications.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.

YouTube @cloudaffle