In the world of object-oriented programming, the need to create objects that share a common theme is a recurrent problem. The Factory Design Pattern is here to solve this issue in a manner that promotes code reusability and scalability. In this blog post, we delve into the world of the Factory Pattern, exploring the scenarios where it is most suitable and how to correctly implement it in Java.
The Factory Pattern is a creational pattern used to define a runtime class of an object and creating its instance, hiding the intricate details of object creation from the user.
It’s important to understand the scenarios which warrant the use of the Factory Pattern. You should consider using this pattern when:
Implementing the Factory Pattern
Below, we illustrate a basic implementation of the Factory Pattern, where we have a factory class responsible for creating instances of different report types.
public class ReportFactory {
public static Report createReport(String reportType) {
if (reportType.equals("PDF")) {
return new PDFReport();
} else if (reportType.equals("Excel")) {
return new ExcelReport();
} else {
throw new IllegalArgumentException("Unknown report type");
}
}
}
interface Report {
void generate();
}
class PDFReport implements Report {
@Override
public void generate() {
System.out.println("Generating PDF report...");
}
}
class ExcelReport implements Report {
@Override
public void generate() {
System.out.println("Generating Excel report...");
}
}
// Client code
public class Main {
public static void main(String[] args) {
Report report = ReportFactory.createReport("PDF");
report.generate();
}
}
In this code:
To further refine our factory, we can introduce a registration mechanism that facilitates the addition of new report types dynamically, enhancing the factory’s extensibility.
import java.util.HashMap;
import java.util.Map;
public class ReportFactory {
private static final Map<String, Report> creators = new HashMap<>();
public static void registerReport(String reportType, Report creator) {
creators.put(reportType, creator);
}
public static Report createReport(String reportType) {
Report creator = creators.get(reportType);
if (creator == null) {
throw new IllegalArgumentException("Unknown report type");
}
return creator;
}
}
// Client code
public class Main {
public static void main(String[] args) {
ReportFactory.registerReport("PDF", new PDFReport());
ReportFactory.registerReport("Excel", new ExcelReport());
Report report = ReportFactory.createReport("PDF");
report.generate();
}
}
Here, a static map holds the different report creators, and we have a static block to register them. This way, adding a new report type is as simple as creating a new class and registering it with the factory.
Factory Pattern finds its utility in various use cases, such as:
Libraries and frameworks: Utilized extensively in libraries and frameworks to instantiate classes at runtime. Database Connections: Handy in creating different DB connections based on the DB type. UI Toolkit: In UI toolkits where a set of controls are created, and depending on various parameters, different controls are returned.
When it comes to large-scale software development, the Factory Pattern is indispensable. Let’s take a look at some real-world instances where renowned libraries and frameworks leverage this pattern:
In the Spring framework, a prevalent framework in the Java ecosystem, the Factory Pattern is employed extensively. The BeanFactory, one of the core interfaces providing configuration support to manage any type of object, is a quintessential implementation of the Factory Pattern, responsible for creating and managing beans defined in the Spring configuration files.
JDBC, which facilitates database connections in Java applications, makes use of the Factory Pattern in the DriverManager class, which is responsible for managing a list of database drivers. The getConnection method, acting as a simple factory, decides which driver to use based on the JDBC URL passed to it, hiding the intricacies of driver initialization and connection from the developer.
JNDI leverages the Factory Pattern to provide various naming and directory services allowing applications to access these services using a unified API. When an application looks up a JNDI name, a factory is employed to create the corresponding Java object, abstracting away the details of the service being accessed and facilitating a loose coupling between the application and external services.
Log4j, a reliable logging framework for Java applications, utilizes the Factory Pattern in creating different types of loggers. The LoggerFactory class serves as a factory for creating logger instances, allowing developers to easily swap different logger implementations and configure them as per their requirements.
In the Apache Wicket, a component-oriented Java web application framework, the Factory Pattern is implemented in creating web pages and components dynamically. It employs factories to create instances of web pages and components at runtime based on the application's configuration, promoting code reusability and separation of concerns.
Factory Pattern is one of the pillars in crafting robust and flexible Java applications, owing much to its adoption by renowned frameworks and libraries in the Java ecosystem. By infusing your codebase with the Factory Pattern, we can implement more scalable and maintainable applications and to align the development practice with the standards adopted by industry.
In this tutorial we are going to explore the factory pattern, to understand when to use and looking at a few practical use cases.
In this tutorial we are going to explore the factory pattern, to understand when to use and looking at a few practical use cases.