Factory Pattern: Practical Approach
Stop cluttering your business logic with complex instantiation code. Learn the Practical Factory pattern to manufacture objects cleanly and maintainably.
While the Singleton Pattern is about restricting creation to one, the Factory Pattern is about organizing creation at scale.
Think of the word "Factory." In the real world, a factory doesn't just make one specific item (like one single Alto car). Instead, it manufactures a type of item. A Maruti factory might produce Altos, Swifts, or Balenos depending on the requirements. Similarly, a Kurkure Factory doesn't just produce "Orange Kurkure": it's a system designed to produce Masala Munch, Green Chutney, or any other flavor based on the production schedule.
The Problem: The "If-Else" Nightmare
Imagine you are building a system that manages different types of users: Students, TAs, Mentors, and Instructors. Depending on what a user types in a menu, you need to create the correct object.
// Naive implementation in your main Business Logic
public User createUser(String input) {
User u;
if (input.equals("student")) {
u = new Student();
} else if (input.equals("ta")) {
u = new TA();
} else if (input.equals("mentor")) {
u = new Mentor();
} else if (input.equals("instructor")) {
u = new Instructor();
}
return u;
}Why this is "Code Smell"
- Violates SRP (Single Responsibility Principle): Your business logic (like a
MenuProgram) shouldn't be responsible for knowing how to instantiate every single subtype. - Violates OCP (Open-Closed Principle): Every time you add a new user type (e.g., "Admin"), you have to modify this existing code.
- Code Duplication: If you need to create users in five different places in your app, you will copy-paste this
if-elseblock five times.
The Solution: The Practical Factory
The Practical Factory pattern (a widely-used variation of the classic GoF patterns) suggests moving this creation logic into its own dedicated class.
class UserFactory {
// Utility method to handle instantiation
public static getUser(type: string): User {
if (type === "student") return new Student();
if (type === "ta") return new TA();
if (type === "mentor") return new Mentor();
if (type === "instructor") return new Instructor();
throw new Error("Invalid User Type");
}
}The "Clean" Business Logic
Now, your main application code becomes incredibly simple:
// One clean line
User u = UserFactory.getUser(input);Why call it "Practical"?
If you read the classic "Gang of Four" book, you will find the Factory Method and Abstract Factory patterns. While powerful, they can be over-engineered for simple daily tasks.
In modern software engineering, the Practical Factory (a simple class with a static method) is the version you will use 90% of the time. It provides 80% of the benefits with 20% of the complexity.
When should you use a Factory?
A Factory is useful whenever you have an Inheritance Relationship (Is-A) and you need to create objects based on dynamic runtime data (like user input or a database config).
Example: The Bird System
If you are designing a game with different birds, you might have:
BirdFactory: ReturnsEagle,Penguin, orParrot.FlyingBehaviorFactory: ReturnsFastFlying,SlowFlying, orNoFlying.
Even if the return types are Interfaces (like FlyingBehavior), the Factory still works perfectly. It hides the complexity of "which class to use" behind a simple method name.
Deep Dive: Factory FAQs
Should Factory Methods be Static?
In 99% of cases, yes. Factory methods like getUser() are self-sufficient; they take an input and return an object. They don't need to maintain any internal state. Making them static allows the client to call UserFactory.getUser() directly without the overhead of creating a factory object first.
Isn't the if-else in the Factory still an OCP violation?
Technically, yes. But it is a Strategic Violation. Without a Factory, your OCP violation is scattered across 50 different client classes. Every time you add a new "Bird" or "User" type, you have to find and fix 50 files. With a Factory, you centralize the "if-else" logic in one single place. You only fix one file. This "centralized violation" is much easier to maintain and keeps the rest of your system clean.
Handling Multiple Creation Criteria
A Factory isn't limited to just a "type" string. You can have multiple factory methods for different business needs:
createBirdOfType(BirdType type)createBirdBySeason(Season season)createBirdFromDatabase(Record record)
Each method encapsulates a specific business rule for how an object should be born.
Summary
The Factory pattern acts as a "Buffer" between your high-level business logic and your low-level instantiation code. It keeps your codebase decoupled and ensures that adding a new "Product" to your factory doesn't break the rest of the factory's customers.
Key Takeaways:
- Centralization: Move
newkeywords out of the client and into a specialized class. - Abstraction: The client interacts with the Parent type/Interface, unaware of the specific subclass being instantiated.
- Maintenance: Adding a new type requires changes in only one file (the Factory), not the entire codebase.
Further Reading
In the next part, we will explore the Factory Method Pattern, where we solve the "Centralized If-Else" problem by using inheritance to delegate creation responsibility.
Practice what you just read.
Keep reading