Skip to main content

Factory Pattern Critcism or Caveats

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

Critism or Caveats

While the Factory Pattern is highly beneficial in many scenarios, it is not without some potential downsides. Here are a few to consider:

Complexity

The use of the Factory Pattern can lead to additional complexity, particularly in smaller projects or cases where a class will only ever have one type. The additional abstraction can overcomplicate the architecture of your application and make it harder to follow the logic.

Let's discuss how the Factory Pattern might be adding complexity to the PaymentProcessor implementation:

  1. Additional Abstraction: The Factory Pattern introduces an additional layer of abstraction to the application. Instead of instantiating payment processors directly, you're now doing it through a factory. While this abstraction helps to decouple the concrete classes from the client code, it can also make the application harder to understand for newcomers, because there's an extra level to think about.

  2. More Classes: With the Factory Pattern, you're creating an extra class (PaymentProcessorFactory). In larger applications with many types of objects, this could mean a substantial increase in the total number of classes. This can make the application harder to manage and increase the learning curve for new developers.

  3. Control Flow Complexity: The Factory Pattern centralizes object creation in one place, but this also means adding logic to decide what object to create. This is typically done with a switch statement or a series of if/else conditions, as in the PaymentProcessorFactory's createProcessor method. This can become increasingly complex as more types are added.

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");
}
}
}

While these factors do add some complexity, it's important to note that they also bring benefits, like decoupling, flexibility, and encapsulation. The key is to balance the trade-offs and decide whether the Factory Pattern makes sense for your specific use case.

Refactoring

If you already have a large codebase and want to introduce the Factory Pattern, refactoring might become a challenge. Changing direct instantiation to use a factory can involve modifying a significant amount of code.

Hidden Types

While decoupling is generally beneficial, it can sometimes lead to confusion, as the actual type of object can be obscured. Developers may need to look up the factory code to understand what specific type is being returned.

The Factory Pattern can lead to "Hidden Types" using the PaymentProcessor implementation as an example.

The Factory Pattern hides the concrete types of objects that the application is using and this is done by design. In the case of the PaymentProcessorFactory, when you use the factory to create a payment processor, you're getting back a PaymentProcessor:

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

In this example, paypalProcessor is typed as a PaymentProcessor. You know that it's some kind of PaymentProcessor, but you don't know what specific subclass it is. This can be both an advantage and a disadvantage.

This obscurity is useful because it means the rest of your application doesn't need to care about what specific type of payment processor it is, as long as it knows it can call processPayment() on it. This abstraction is beneficial because it decouples your code, makes it more modular, and easier to change in the future.

However, the downside is that you lose some visibility into the concrete type that you're dealing with. If the different subclasses have unique methods not present in the base PaymentProcessor class, you won't be able to call them without type checking or type casting. For example, if only StripeProcessor had a method stripeSpecificMethod(), you wouldn't be able to call that method without explicitly checking that you're dealing with a StripeProcessor.

That's what is meant by "Hidden Types" in this context. It's not necessarily a problem, but it's something to be aware of when using the Factory Pattern.

Increased Number of Classes

Factory Pattern can lead to an increase in the number of classes used in your codebase. This might make the code more difficult to manage or understand for newcomers.

The Factory Pattern increases the number of classes in our PaymentProcessor implementation:

In a simpler design without the Factory Pattern, you might directly instantiate the specific PaymentProcessor objects where needed, like this:

let paypalProcessor = new PaypalProcessor(100);
let stripeProcessor = new StripeProcessor(200);

In this case, you're dealing with three classes: PaymentProcessor, PaypalProcessor, and StripeProcessor.

However, when we introduce the Factory Pattern, we add an additional class, PaymentProcessorFactory:

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

Now, you have four classes: PaymentProcessor, PaypalProcessor, StripeProcessor, and PaymentProcessorFactory.

This increases the number of classes in the application, adding to the overall complexity. More classes can potentially make the code harder to manage, understand, and test, especially for developers who are new to the codebase.

While it's true that introducing the Factory Pattern increases the number of classes, remember that it's a trade-off. The extra class brings with it benefits such as abstraction of object creation, decoupling of components, and easier integration of new types of payment processors.

Testing

While Factory Pattern generally helps to write easily testable code by abstracting the creation logic, it can sometimes complicate the testing process if the factories are complex. The test setup might require more work, as now you need to take care of the factory setup as well.

Before deciding to use the Factory Pattern, it's important to weigh these potential downsides against the benefits in the context of your specific project. As with any design pattern, the Factory Pattern is simply a tool with its own use cases—it's not a one-size-fits-all solution.

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