Table of Contents
What is Factory pattern?
It is a very commonly used creational design pattern. Following diagram shows it’s implementation:
When is Factory pattern used?
The Factory Pattern is typically used in situations where a system needs to manage, create, and represent multiple types or classes of objects that share a common base class or interface. This pattern is particularly useful when the details of object creation should be hidden from the system to promote modularity and scalability.
This pattern is especially valuable in environments where the details of object creation are likely to change frequently or are inherently complex. Here are six high-fidelity real-world examples illustrating the usage of the Factory Pattern:
- Cloud Resource Provisioning:
- Scenario: A cloud management platform needs to provision various types of resources like virtual machines, storage accounts, and databases.
- Application: A
ResourceFactory
class can abstract the creation details and return appropriate resource instances such asVirtualMachine
,StorageAccount
, orDatabase
based on the specifications provided by the user. This allows new types of resources to be added with minimal changes to the existing code.
- Automobile Manufacturing:
- Scenario: An automobile factory must assemble different types of vehicles like cars, trucks, and motorcycles.
- Application: A
VehicleFactory
is used where each type of vehicle is created based on customer requirements. The factory could produce instances ofCar
,Truck
, orMotorcycle
classes. This method centralizes the vehicle creation logic, making the manufacturing process easier to manage and adapt to new vehicle types.
- E-commerce Product Display:
- Scenario: An online store displays different types of products, each with distinct layouts and information.
- Application: A
ProductDisplayFactory
might create differentProductDisplay
objects likeBookDisplay
,ElectronicsDisplay
, orClothingDisplay
based on the product type. This encapsulates how product information is formatted and presented, simplifying the UI code and improving maintainability.
- Job Scheduling in Computing:
- Scenario: A software system that manages and allocates tasks to different workers based on task type, such as computational tasks, I/O intensive tasks, or network tasks.
- Application: A
TaskFactory
could generate different types ofTask
objects, such asComputeTask
,IOTask
, andNetworkTask
. This abstracts the task creation and makes the scheduler more flexible and easier to extend with new types of tasks.
- Financial Report Generation:
- Scenario: A financial software system needs to generate various types of reports like income statements, balance sheets, and cash flow statements.
- Application: A
ReportFactory
might be responsible for creating different types ofReport
objects likeIncomeStatement
,BalanceSheet
, andCashFlow
. This approach decouples the report generation logic from the types of reports, facilitating easier additions and modifications of report types.
- Healthcare Management Systems:
- Scenario: A hospital management system needs to manage different types of medical records such as patient records, treatment records, and drug prescription records.
- Application: A
RecordFactory
could create specificRecord
objects likePatientRecord
,TreatmentRecord
, orPrescriptionRecord
based on the data being processed. This centralizes the creation logic and ensures that the system can adapt to new types of medical records without extensive modifications.
Why is factory pattern used?
The most important benefit of using the Factory Pattern is its ability to decouple the creation of objects from their implementation. This decoupling is central to solving several key problems in software design, particularly those related to maintainability, scalability, and flexibility.
In many applications, especially those that are large and complex, object creation can involve more than just constructing an instance. It might include setting up various initial settings, integrating dependencies, and other preparatory steps necessary for the object to function correctly in its designated context. The Factory Pattern addresses this complexity by:
- Abstracting and Encapsulating Creation Logic: The pattern hides the details of how objects are created and initialized. Clients simply request objects from the factory without needing to understand the complexities involved in their creation. This encapsulation of creation logic makes the client code cleaner, easier to read, and focused on its primary responsibilities.
- Supporting System Evolution: As systems evolve, new types of objects may need to be introduced, and existing ones might need modifications. The Factory Pattern allows for these changes with minimal impact on the client code. You can introduce new object types or change the creation logic without affecting any of the system parts that use these objects. This makes the system more adaptable to change and easier to extend.
- Ensuring Consistent Object Configuration: By centralizing object creation, factories can ensure that all objects are configured consistently. This is particularly important when objects must be created with specific, possibly complex configurations that could easily be misconfigured if done in multiple places throughout the application.
- Reducing Code Duplication: Without a factory, the same creation code might be repeated across multiple locations in the application, leading to code duplication. The Factory Pattern centralizes this creation logic in one place, reducing redundancy and the potential for bugs associated with having multiple points of change for object creation.
How to Implement factory pattern (in Java)
Consider an application that must connect to different types of databases depending on the environment (development, testing, production). Without using the Factory Pattern, code snippets creating specific database connections might be scattered throughout the application, leading to a situation where changing the database type or configuration parameters becomes cumbersome and error-prone.
By implementing a DatabaseConnectionFactory
, you can encapsulate all the logic required to connect to a database within one class. This factory can then check the environment settings and return a connection object to the appropriate database. Any part of the application needing a database connection simply asks the factory for one, without needing to know the details of database instantiation.
Without using the factory interface:
- The client is directly responsible for determining which database connection to instantiate. This mixes decision-making with action, leading to less flexible and more brittle code.
- Adding a new database type requires modifying the client code, potentially introducing errors and violating the open-closed principle.
- Changes in the instantiation process or configuration of database connections necessitate changes in all locations where the connections are created.
public interface IDatabaseConnection {
void connect();
}
public class MySQLConnection implements IDatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to MySQL database...");
}
}
public class OracleConnection implements IDatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to Oracle database...");
}
}
public class Client {
public static void main(String[] args) {
IDatabaseConnection connection;
String dbType = System.getenv("DB_TYPE");
if ("MySQL".equals(dbType)) {
connection = new MySQLConnection();
} else if ("Oracle".equals(dbType)) {
connection = new OracleConnection();
} else {
throw new IllegalArgumentException("No such database type");
}
connection.connect();
}
}
Using factory Interface
- The creation logic is abstracted away in a factory. The client only interacts with the factory, remaining agnostic of the specifics of object creation
- New database types can be added by extending the factory. The client code remains unchanged, adhering to the open-closed principle.
- Changes are localized within the factory classes, making them easier to manage and reducing the risk of bugs.
public interface IDatabaseConnection {
void connect();
}
public class MySQLConnection implements IDatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to MySQL database...");
}
}
public class OracleConnection implements IDatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to Oracle database...");
}
}
public abstract class DatabaseConnectionFactory {
abstract IDatabaseConnection createConnection(String type);
}
public class ConcreteDatabaseConnectionFactory extends DatabaseConnectionFactory {
@Override
public IDatabaseConnection createConnection(String type) {
switch (type) {
case "MySQL":
return new MySQLConnection();
case "Oracle":
return new OracleConnection();
default:
throw new IllegalArgumentException("No such database type");
}
}
}
public class Client {
public static void main(String[] args) {
DatabaseConnectionFactory factory = new ConcreteDatabaseConnectionFactory();
IDatabaseConnection connection = factory.createConnection(System.getenv("DB_TYPE"));
connection.connect();
}
}