Implementing the Factory Pattern in Java
Introduction
The Factory Design Pattern is a creational pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created. It encapsulates object creation logic and promotes loose coupling between client code and the actual object creation. In essence, it acts as a “factory” for creating instances of related classes. For example, if you have different types of Button objects (e.g., WindowsButton, MacButton), a factory can create the appropriate button based on the platform. This pattern enhances flexibility and maintainability in software design.
Definition of the Factory Pattern
The Factory Design Pattern is a creational pattern that provides an interface for creating objects in a super class, but allows subclasses to alter the type of objects that will be created
Understanding the Factory Pattern
Basic concept and principles
In the Factory Pattern example provided earlier, several of these principles are applied:
Encapsulation: The creation logic is encapsulated within the factory, hiding the complex instantiation process from the client.
Abstraction: The
Button
interface provides an abstract layer for different button implementations.Polymorphism: The factory method returns objects of the
Button
type, allowing different button implementations to be used interchangeably.Single Responsibility Principle (SRP): Each class has a single responsibility –
HTMLButton
andWindowsButton
focus on rendering their respective buttons, whileButtonFactory
handles the instantiation logic.Open/Closed Principle (OCP): New button types can be added without modifying existing client code, just by extending the factory method.
Dependency Inversion Principle (DIP): The client code depends on the
Button
abstraction rather than specific implementations.
When to use the Factory Pattern
The Factory Pattern is a creational design pattern used to create objects without specifying the exact class of object that will be created. This pattern is particularly useful in the following scenarios:
Object Creation Complexity: When the process of creating an object is complex, involves many steps, or depends on external conditions. The Factory Pattern encapsulates this complexity, making the code cleaner and easier to maintain.
Decoupling Client Code from Concrete Classes: When you want to decouple your client code from the specific classes it needs to instantiate. By using a factory, the client code interacts with the factory to get objects, rather than directly instantiating them.
Enhancing Code Maintainability: When you anticipate frequent changes to the object creation process or the objects themselves. Using a factory makes it easier to manage and update these changes in one place without impacting the client code.
Implementing Dependency Injection: When you want to use dependency injection, the Factory Pattern can be a key part of providing dependencies, particularly when the dependencies require some configuration before they can be created.
Managing Object Lifecycle: When the lifecycle of objects needs to be managed centrally, such as with pooling, caching, or reference counting, a factory can handle these concerns.
Components of the Factory Pattern
Product Interface:
The Product interface defines the common methods that all concrete products must implement. It acts as a contract for the product family.
Concrete product classes (e.g., WindowsButton, MacButton) implement this interface.
Example: Product Interface of Button: These classes have logic regarding how button should be render based on object created by concrete factory.
// Product interface interface Button { void render(); }
Example : Product Interface of Transport
// Product interface interface Transport { void delivery(); }
Concrete Products
These are the actual implementations of the
Product
interface. Each concrete product represents a specific type of object that the factory can create.For instance, if we have a
Button
factory, concrete products could beWindowsButton
andMacButton
.Example : Concrete Product of Button
// Concrete product: WindowsButton class WindowsButton implements Button { @Override public void render() { System.out.println("Rendering a Windows button."); } } // Concrete product: MacButton class MacButton implements Button { @Override public void render() { System.out.println("Rendering a Mac button."); } } // Concrete product: HtmlButton class HtmlButton implements Button { @Override public void render() { System.out.println("Rendering a Html button."); } }
Example : Concrete product for Transport - This classes have actual logic of handling delivery
// Concrete product: Truck class Truck implements Transport { @Override public void delivery() { System.out.println("Logic to make delivery through Truck."); } } // Concrete product: Flight class Flight implements Transport { @Override public void delivery() { System.out.println("Logic to make delivery through Flight."); } } // Concrete product: Boat class Boat implements Transport { @Override public void delivery() { System.out.println("Logic to make delivery through Boat."); } }
Factory Interface
The
Factory
interface declares the method(s) for creating products. It acts as a contract for concrete factories.Concrete factories (e.g.,
WindowsButtonFactory
,MacButtonFactory, HtmlButtonFactory
) implement this interface.Example : Factory Interface of ButtonFactory
// Factory interface
interface ButtonFactory {
Button createButton();
}
Example : Factory Interface of TransportFactory
// Factory interface
interface TransportFactory {
Button createTransport();
}
Concrete Factories
These are the actual implementations of the
Factory
interface. Each concrete factory produces a specific family of related products.For example, a
WindowsButtonFactory
createsWindowsButton
instances, while aMacButtonFactory
createsMacButton
instances.Example : Concrete Factory for ButtonFactory to create actual class objects that handle the logic for creating objects.
// Concrete factory: WindowsButtonFactory class WindowsButtonFactory implements ButtonFactory { @Override public Button createButton() { return new WindowsButton(); } } // Concrete factory: MacButtonFactory class MacButtonFactory implements ButtonFactory { @Override public Button createButton() { return new MacButton(); } } // Concrete factory: HtmlButtonFactory class HtmlButtonFactory implements ButtonFactory { @Override public Button createButton() { return new HtmlButton(); } }
Example : Concrete Factory for TransportFactory to create actual class objects that handle the logic for creating objects.
// Concrete factory: TruckTransportFactory class TruckTransportFactory implements TransportFactory { @Override public Truck createTransport() { return new Truck(); } } // Concrete factory: FlightTransportFactory class FlightTransportFactory implements TransportFactory { @Override public Flight createTransport() { return new Flight(); } } // Concrete factory: BoatTransportFactory class BoatTransportFactory implements TransportFactory { @Override public Boat createTransport() { return new Boat(); } }
Usage Scenario
public class Client {
Button btn;
ButtonFactory factory;
public void renderButton(String type){
if(type=="html"){
this.factory = new HtmlButtonFactory ();
}else if(type=="mac"){
this.factory = new MacButtonFactory ();
}else{
this.factory = new WindowsButtonFactory();
}
this.btn = this.factory.createButton();
this.btn.render();
}
public static void main(String args[]){
Client client = new Client();
client.renderButton("html") //based on type pass button will be render
}
}
Applying the Factory Pattern to solve the problem
The Factory Design Pattern finds practical use in various scenarios across software development. Here are 20 real-life situations where you can apply it:
Database Connection Management:
- Creating database connections (e.g., MySQL, PostgreSQL, MongoDB) based on configuration settings.
Logging Libraries:
- Generating loggers (e.g., Log4j, SLF4J) with different appenders (file, console, database).
GUI Frameworks:
- Creating UI components (buttons, labels, text fields) for different platforms (Windows, macOS, Linux).
Dependency Injection Containers:
- Instantiating and managing dependencies (services, repositories) in Spring, Guice, or Dagger.
Abstracting Third-Party APIs:
- Wrapping API calls (e.g., payment gateways, social media) with a consistent interface.
Document Generation Libraries:
- Creating PDF, Excel, or Word documents using libraries like Apache POI or iText.
Plugin Systems:
- Loading and initializing plugins dynamically (e.g., WordPress, Eclipse IDE).
Abstracting File I/O:
- Creating file readers/writers (CSV, JSON, XML) based on file formats.
Localization and Internationalization:
- Generating localized resources (strings, images) for different languages.
Abstracting Data Sources:
- Creating data source objects (e.g., JDBC, JPA) for different databases.
Abstract Factory for Widgets:
- Building UI components (buttons, menus, dialogs) in a cross-platform GUI library.
Game Development:
- Creating game objects (characters, weapons, power-ups) based on game levels or player choices.
Abstracting Network Protocols:
- Implementing network communication (HTTP, WebSocket, FTP) with different protocols.
Dynamic Charting Libraries:
- Generating charts (line, bar, pie) with customizable styles and data sources.
Abstracting Caching Strategies:
- Creating cache providers (in-memory, Redis, Memcached) based on application requirements.
Abstracting Authentication Providers:
- Handling authentication (OAuth, JWT, SAML) for different identity providers.
Abstracting Report Generation:
- Creating reports (PDF, HTML, CSV) with varying layouts and data sources.
Abstracting Payment Gateways:
- Integrating payment gateways (PayPal, Stripe, Square) with consistent APIs.
Abstracting Hardware Devices:
- Creating drivers for printers, scanners, or sensors.
Dynamic Class Loading:
- Loading and instantiating classes at runtime (e.g., plugins, extensions).
Remember, the Factory Design Pattern shines when you need to create objects with varying implementations while maintaining a common interface.
Advantages of Using the Factory Pattern
Abstraction: The pattern abstracts the process of object creation, allowing clients to work with a common interface.
Flexibility: You can easily add new product types (concrete classes) without modifying existing code.
Decoupling: It promotes loose coupling by separating object creation from client code.
Centralized Logic: The factory encapsulates complex creation logic in one place.
Potential Drawbacks
Increased complexity
Overhead of additional classes and interfaces
Best Practices
- Keep the factory interface minimal and focused on creating objects.
Follow naming conventions (e.g.,
createProduct()
).Consider using dependency injection frameworks for more complex scenarios.
Conclusion
This article explores the Factory Design Pattern, a creational pattern that provides an interface for creating objects while allowing subclasses to determine the type. It enhances flexibility by encapsulating object creation logic, promoting loose coupling, and following principles like encapsulation, abstraction, polymorphism, SRP, OCP, and DIP. The article provides Java examples illustrating component constructs (product interface, concrete products, factory interface, concrete factories) and usage scenarios. It also highlights real-life applications, advantages, potential drawbacks, and best practices, offering a comprehensive guide to implementing and utilizing the Factory Pattern effectively.
Real time Scenarios For Practice:
Notification System:
Description: Design a notification system that supports different types of notifications like Email, SMS, and Push notifications. Each notification type should implement a common interface with a method to send the notification.
Goal: Implement a factory class to create instances of the different notification types based on input parameters.
Document Converter:
Description: Create a document converter that can convert documents into various formats such as PDF, Word, and HTML. Each converter should implement a common interface with a method to perform the conversion.
- Goal: Use a factory to instantiate the appropriate converter based on the desired output format.
Payment Processor:
Description: Design a payment processing system that supports multiple payment methods like Credit Card, PayPal, and Bank Transfer. Each payment method should implement a common interface with a method to process the payment.
- Goal: Develop a factory class to create instances of the different payment methods based on the user's choice.
Shape Drawer:
Description: Build a drawing application that can draw different shapes like Circle, Square, and Rectangle. Each shape should implement a common interface with a method to draw the shape.
- Goal: Create a factory to produce the appropriate shape objects based on the user input.
Logger System:
Description: Create a logging system that supports different logging mechanisms such as File Logger, Database Logger, and Console Logger. Each logger should implement a common interface with a method to log messages.
- Goal: Implement a factory to generate instances of the various loggers based on configuration settings.
Game Character :
Description: Design a game character creation system where characters can be of different types like Warrior, Mage, and Archer. Each character type should implement a common interface with methods for actions such as attack and defend.
- Goal: Use a factory to create different character types based on the player's choice.
Transport Service:
Description: Develop a transport service application that can provide different transport modes like Car, Bike, and Bus. Each transport mode should implement a common interface with a method to calculate the fare.
- Goal: Create a factory to instantiate the appropriate transport mode based on the user's request.
User Authentication:
Description: Create an authentication system that supports different authentication methods such as Password, Fingerprint, and Face Recognition. Each authentication method should implement a common interface with a method to authenticate the user.
- Goal: Develop a factory to create instances of the various authentication methods based on the user's configuration.
Further reading and resources
Subscribe to my newsletter
Read articles from Vishad Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by