Design Pattern: Factory - A Didactic and Intuitive Guide for Developers

Fullstack Software Engineer.
π― What is the Factory Pattern?
The Factory Pattern is a Creational Design Pattern that aims to delegate the responsibility of creating objects to a specific class (called Factory), avoiding direct use of the new operator in the business logic.
Instead of instantiating objects directly, you delegate this task to a factory method that decides which concrete class to instantiate and returns the object ready for use.
π When should I use the Factory Pattern?
You can consider using Factory when:
β
You need to decouple object creation from business logic.
β
You want to centralize object creation logic.
β
The object creation process involves complex conditions or validations.
β
You need to create objects from different classes implementing the same interface.
β οΈ When not to use
β When you have only one or two simple implementations.
β In small projects where Factory may introduce unnecessary complexity. (KIS - Keep it Simple!!!)
β When there is no need to hide the implementation details.
β Pros
Reduces coupling between classes.
Centralizes and organizes object creation.
Facilitates maintenance and scalability.
Improves testability.
β Cons
Adds an extra layer of complexity.
Excessive use of Factories can overcomplicate the project.
In simple scenarios, it may be overengineering.
π‘ Real scenario without Factory β Common problems
Example 1
public class NotificationService {
public void sendNotification(String type, String message) {
if (type.equals("EMAIL")) {
new EmailNotification().send(message);
} else if (type.equals("SMS")) {
new SmsNotification().send(message);
} else if (type.equals("PUSH")) {
new PushNotification().send(message);
}
}
}
β Problem: Every time a new notification type is added, you need to modify NotificationService, violating the Open/Closed Principle.
Example 2
public class VehicleService {
public Vehicle getVehicle(String type) {
if (type.equals("CAR")) {
return new Car();
} else if (type.equals("BIKE")) {
return new Bike();
}
return null;
}
}
β Problem: Not scalable, difficult to maintain.
Example 3
public class ReportGenerator {
public Report createReport(String format) {
if (format.equals("PDF")) {
return new PdfReport();
} else if (format.equals("EXCEL")) {
return new ExcelReport();
}
return null;
}
}
β Problem: Any change requires direct modification in this class.
β Applying the Factory Pattern
Example 1 - Notification
Interface:
public interface Notification {
void send(String message);
}
Implementations:
public class EmailNotification implements Notification {
public void send(String message) {
System.out.println("Email: " + message);
}
}
public class SmsNotification implements Notification {
public void send(String message) {
System.out.println("SMS: " + message);
}
}
Factory:
public class NotificationFactory {
public static Notification createNotification(String type) {
return switch (type) {
case "EMAIL" -> new EmailNotification();
case "SMS" -> new SmsNotification();
default -> throw new IllegalArgumentException("Invalid notification type");
};
}
}
Usage:
Notification notification = NotificationFactory.createNotification("EMAIL");
notification.send("Hello Factory!");
Example 2 - Vehicle
public interface Vehicle {}
public class Car implements Vehicle {}
public class Bike implements Vehicle {}
public class VehicleFactory {
public static Vehicle createVehicle(String type) {
return switch (type) {
case "CAR" -> new Car();
case "BIKE" -> new Bike();
default -> throw new IllegalArgumentException("Invalid vehicle type");
};
}
}
Example 3 - Report
public interface Report {}
public class PdfReport implements Report {}
public class ExcelReport implements Report {}
public class ReportFactory {
public static Report createReport(String format) {
return switch (format) {
case "PDF" -> new PdfReport();
case "EXCEL" -> new ExcelReport();
default -> throw new IllegalArgumentException("Invalid report format");
};
}
}
π― PLUS: Combining Factory + Strategy Pattern
π‘ Why use them together?
Factory helps to create the strategy dynamically and Strategy allows you to execute different behaviors at runtime.
Together, they create a powerful, flexible, and scalable solution.
β Pros of combining Factory + Strategy
Reduces coupling.
Keeps the code open for extension and closed for modification.
Allows dynamic creation and execution of behaviors.
β Cons
Increases complexity.
May be unnecessary in small systems.
Requires clear design and discipline to avoid overengineering.
π When to use
β
When you have several strategies with different behaviors.
β
When a decision rule is needed to choose which strategy to use.
β
When object creation and behavior execution vary dynamically.
β οΈ When to avoid
β In simple scenarios with few behaviors.
β When strategies never change dynamically.
π₯ 3 Real and Practical Examples
1οΈβ£ File Processing
You may have strategies for processing different file formats (CSVProcessor, JSONProcessor, XMLProcessor).
The Factory decides which strategy to return based on the file type.
2οΈβ£ Report Generation
A Factory can create a report generation strategy (PDFStrategy, ExcelStrategy) based on the requested format.
3οΈβ£ Freight Calculation
Depending on the customer type and region, a Factory can return a specific calculation strategy (PremiumCustomerStrategy, RegularCustomerStrategy).
π― Practical example combining Factory + Strategy
public interface PaymentStrategy {
void pay(double amount);
}
public class CreditCardPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paid with credit card: " + amount);
}
}
public class PixPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println("Paid with PIX: " + amount);
}
}
public class PaymentStrategyFactory {
public static PaymentStrategy getStrategy(String method) {
return switch (method) {
case "CREDIT" -> new CreditCardPayment();
case "PIX" -> new PixPayment();
default -> throw new IllegalArgumentException("Invalid payment method");
};
}
}
Usage:
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy("PIX");
strategy.pay(100.00);
β Conclusion
The Factory Pattern solves the problem of object creation.
The Strategy Pattern solves the problem of varying behavior execution.
When combined, you get a system that is:
π Extensible
π Decoupled
βοΈ Easy to maintain and evolve
Pro Tip: Use both patterns together when you have real dynamic behavior and creation needs. Avoid them when your application is simple and stable, to prevent over-engineering.


