Back to: Java Design Patterns
Factory Design Pattern in Java with Examples
In this article, I am going to discuss the Factory Design Pattern in Java with Real-Time Examples. Please read our previous article where we give a brief introduction to the Creational Design Pattern. The Factory Design Pattern is one of the most frequently used design patterns in real-time applications. The Factory Design Pattern falls under the Creational Design Patterns Category.
What is a Factory Design Pattern?
In software development, object creation can sometimes become complex and tightly coupled to implementation details. The Factory design pattern provides a solution by encapsulating the object creation process in a separate factory class. By delegating the responsibility of object creation to a factory, the pattern promotes loose coupling, enhances flexibility, and simplifies maintenance. In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Factory design pattern, emphasizing its significance in object creation and encapsulation.
The Factory design pattern is a creational pattern that separates object creation from its usage. It introduces a factory class, responsible for creating instances of objects based on a specified interface or base class. The client code interacts with the factory, requesting objects without directly knowing the concrete implementation details. The factory class encapsulates the creation logic, allowing for easy modification or extension of object creation without impacting the client code. The pattern provides a standardized interface for object creation, ensuring that the Object Creation Process remains consistent throughout the application.
According to Gang of Four (GOF), the Factory Design Pattern states that A factory is an object which is used for creating other objects. In technical terms, we can say that a factory is a class with a method. That method will create and return different types of objects based on the input parameter, it received.
In simple words, if we have a superclass and n number of subclasses, and based on the data provided, if we have to create and return the object of one of the subclasses, then we need to use the Factory Design Pattern in Java.
Note: The Factory design pattern offers valuable advantages in encapsulating object creation, promoting loose coupling, enhancing flexibility and extensibility, centralizing creation logic, and encouraging dependency inversion. Its benefits include encapsulation and abstraction, loose coupling, flexibility, centralized object creation, and adherence to the Dependency Inversion Principle. However, it is important to consider potential drawbacks, including increased complexity, coupling between the factory and concrete implementations, limited dynamic object creation, and maintenance overhead. By carefully evaluating the requirements of the system and considering trade-offs, developers can leverage the Factory pattern to create objects in a flexible and maintainable manner, promoting code reuse and modularity.
Example to Understand Factory Design Patterns in Java:
Let’s consider a real-world example where the Factory design pattern can be applied: a fuel manufacturing company. In this scenario, the company produces different types of fuel, each with different prices. The Factory pattern can be used to streamline the creation process of these fuels.
The company can define an abstract class called “Fuel” that represents the common features of all cars, such as the price. Then, it can create concrete subclasses for each type of fuel, like “Petrol”, “Diesel”, and “CNG,” which extend the Fuel class.
To instantiate car objects, a GetFuelFactory class can be implemented. This class will have methods for creating different types of fuel based on the client’s request.
By using the Factory pattern, the company can achieve several benefits. Firstly, it centralizes the object creation logic, ensuring consistent construction of objects throughout the system. It also enhances code maintainability and scalability since any changes in the creation process can be made in the factory class without affecting the client code. Additionally, the Factory pattern provides a clear separation of concerns, as the client code only needs to interact with the factory class, abstracting away the complex object creation details. This can be described using the following UML:
Implementing Factory Design Pattern in Java
Step 1: Create a new directory to store all the class files of this project.
Step 2: Open VS Code and create a new project, called factorypattern.
Step 3: In the project, create a new file called Fuel.java.
Step 4: Add the following contents to the file:
We have added the following code:
- Created an abstract class, called Fuel. This class will later be extended.
- Created a variable called rate. This variable will be used to store the rate of the particular fuel.
- Added a function stub for a function called getRate(). Note that this function is not implemented. This is because the implementation differs for each kind of fuel. Hence, the implementation will happen when another (child) class extends from this (parent) class.
- Added a function called calcPrice(). This function, when called, will return the price of the fuel for that amount. Because the implementation of this function is the same for each type of fuel, we have done the implementation in the parent class itself.
Note that the parent class has been created, we can start creating the child classes.
Step 5: Create a file called Petrol.java in the project.
Step 6: Populate it with the following code:
We have added the following code:
- Created a class called Petrol. This class is said to be the child class of Fuel.
- Implemented the getRate() function defined in the parent class. Note that we use the @Override annotation. This is to indicate that if an implementation of getRate() is present in the parent class, that implementation is to be overridden with this one. Because petrol is not an abstract class, and because it inherits from Fuel, the implementation of getRate() has to be present.
Note that we have not added an implementation for calcPrice(). This is because the proper implementation for calcPrice() is already present in the parent class.
Step 7: Create a file called Diesel.java in the project.
Step 8: Populate it with the following code:
All the code additions are similar to those done for the Petrol.java file.
Step 9: Create a file called CNG.java in the project.
Step 10: Populate it with the following code:
All the code additions are similar to those done for the Petrol.java file.
Step 11: Create a file called GetFuelFactory.java in the project.
Step 12: Populate it with the following code:
We have added the following code:
- Created a class called GetFuelFactory. This class is responsible for creating an object of type Fuel, based on user entry.
- Created a function called getFuel(). This function will take in the name of the fuel, and compare it with the three types of fuel that we have. Based on this, it will return the appropriate kind of fuel.
Step 13: Create a file called GeneratePrice.java in the project. This class should contain the main() function.
Step 14: Populate it with the following code:
We have performed the following code additions:
- Imported the required packages
- Created a class called GeneratePrice
- Added the main() function in the class.
- On line 9, we define an object of type BufferedReader. This object will be used to obtain input from the user.
- On line 10, we create an object of type GetFuelFactory. This object will then be used to obtain fuel, based on user input.
- From lines 13 to 22, there is a try block.
- From lines 14 to 19, we obtain the fuel and amount from the user. Based on this, we create an object of type of fuel.
- On line 21, we print out the price of the fuel.
- From lines 23 to 26, there is a catch block. This block will execute when an IOException occurs. It will print the error message.
Step 15: Compile and execute the application. Ensure compilation is successful.
The program is now waiting for input. Enter the type of fuel and the amount. The program should print the price:
Congratulations! You now know how to implement the factory pattern in Java!
The Complete Example Code
CNG.java
public class CNG extends Fuel { @Override void getRate() { rate=86.00; } }
Diesel.java
public class Diesel extends Fuel { @Override void getRate() { rate=96.35; } }
Fuel.java
public abstract class Fuel { protected double rate; abstract void getRate(); public double calcPrice (double amount) { return rate * amount; } }
GeneratePrice.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class GeneratePrice { public static void main(String[] args) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); GetFuelFactory getFuelFactory = new GetFuelFactory(); try { System.out.print("Enter the type of fuel: "); Fuel f = getFuelFactory.getFuel(br.readLine()); f.getRate(); System.out.print("Enter the amount: "); double amount = Double.parseDouble(br.readLine()); System.out.println("The cost of fuel = " + f.calcPrice(amount)); } catch (IOException e) { System.out.println(e.getMessage()); } } }
GetFuelFactory.java
public class GetFuelFactory { public Fuel getFuel(String fuelType) { if (fuelType.equalsIgnoreCase("petrol")) return new Petrol(); else if (fuelType.equalsIgnoreCase("diesel")) return new Diesel(); else if (fuelType.equalsIgnoreCase("cng")) return new CNG(); return null; } }
Petrol.java
public class Petrol extends Fuel { @Override void getRate() { rate=101.61; } }
UML Diagram of Factory Design Pattern
The classes can be described as follows:
- Product: This class forms the abstract class from which other (concrete) classes extend.
- ConcreteProduct: These classes extend from the Product abstract classes. They implement the abstract methods from the abstract class.
- ProductFactory: This class is responsible for providing the client with the required type of fuel.
- DriverClass: This class contains the main() function and is responsible for ‘driving’ the entire program.
Real-Life Example of Factory Pattern:
From Lehman’s point of view, we can say that a factory is a place where products are created. In order words, we can say that it is a centralized place for creating products. Later, based on the order received, the appropriate product is delivered by the factory. For example, a car factory can produce different types of cars. If you are ordering a car, then based on your requirements or specifications, the factory will create the appropriate car and then deliver that car to you.
The same thing also happens in the Factory Design Pattern. A factory (i.e. a class) will create and deliver products (i.e. objects) based on the incoming parameters.
Advantages of Factory Design Pattern in Java
Some of the advantages of using the factory pattern are:
- Encapsulation and Abstraction: The Factory pattern promotes encapsulation by encapsulating the object creation process within a separate factory class. This encapsulation hides the creation details from the client code, providing a clear separation of concerns and enhancing code maintainability. Clients are only exposed to the factory’s interface, shielding them from the complexities of object creation.
- Loose Coupling: The Factory pattern promotes loose coupling by decoupling the client code from the concrete implementation of objects. Clients interact with the factory through an interface or abstract base class, ensuring that they depend on abstractions rather than concrete implementations. This loose coupling enables flexibility, as different concrete implementations can be easily substituted or added without affecting the client code.
- Flexibility and Extensibility: The Factory pattern provides flexibility in object creation. It allows for easy modification or extension of the creation process by adding new factory methods or subclasses. This flexibility facilitates the integration of new object types or variations, supporting future requirements and reducing the impact of changes on existing code.
- Centralized Object Creation: The Factory pattern centralizes object creation logic within a single location, the factory class. This centralization simplifies maintenance and promotes code reuse, as the creation logic is consolidated in one place. It avoids duplication of code across multiple client modules, enhancing consistency and reducing the chances of errors or inconsistencies.
- Encourages Dependency Inversion: The Factory pattern encourages the Dependency Inversion Principle by relying on abstractions. Clients depend on interfaces or abstract base classes rather than concrete implementations, promoting a flexible and modular design. This inversion of dependencies allows for easy substitution of objects, improves testability, and facilitates the implementation of the Open-Closed Principle.
Disadvantages of Factory Design Pattern in Java
Some of the disadvantages of using the factory pattern are:
- Increased Complexity: The Factory pattern can introduce additional complexity to the codebase. The presence of a factory class and the need for additional abstraction layers may complicate the overall design and understanding of the system. Developers must carefully design and manage the factory and its relationships with other components to avoid unnecessary complexity.
- Coupling Between the Factory and Concrete Implementations: While the Factory pattern promotes loose coupling between clients and objects, it introduces a coupling between the factory and concrete implementations. Changes in the concrete implementation may require modifications to the factory class, potentially impacting multiple parts of the system. Proper management of dependencies and adherence to SOLID principles can mitigate this issue.
- Limited Dynamic Object Creation: The Factory pattern is most effective when the set of possible objects is known at compile-time. If the object creation needs to be dynamically determined at runtime, the Factory pattern may not be the best choice. Other creational patterns like the Abstract Factory or Builder pattern may be more suitable in such cases.
- Maintenance Overhead: The introduction of a factory class adds an additional layer of indirection and complexity to the codebase. This can increase the maintenance overhead, as modifications or additions to the object hierarchy may require corresponding changes in the factory class. Proper documentation and adherence to design principles can mitigate this overhead.
In the next article, I am going to discuss the Abstract Factory Design Pattern in Java with Examples. Here, in this article, I try to explain the Factory Design Pattern in Java with Examples. I hope this article will help you with your needs. I would like to have your feedback. Please post your feedback, question, or comments about this article.