Factory Pattern
What is it? (The Analogy)
Walk into a bakery and say: "I'd like a chocolate cake." You don't walk into the kitchen, grab flour, eggs, and cocoa, preheat the oven, mix the batter, and bake it yourself. You just tell the bakery what you want, and the bakery creates it for you. You get back a delicious cake without knowing (or caring about) the 47 steps that went into making it.
The bakery is a factory. It takes your order (input), handles all the complex creation logic (the recipe), and returns a finished product (output). If the bakery adds a new "red velvet cake" to the menu, YOU don't need to learn anything new -- you just order it.
The Factory Pattern works the same way in code: instead of using new directly to create objects (which couples your code to specific classes), you ask a factory to create the object for you. The factory decides which concrete class to instantiate based on the input you provide.
Why do we need it?
Let's see the pain of creating objects directly:
1// BAD EXAMPLE -- Object creation scattered everywhere
2public class GameWorld {
3
4 public void spawnEnemy(String type, int x, int y) {
5 // Creation logic is hardcoded and repeated everywhere
6 if (type.equals("zombie")) {
7 Zombie z = new Zombie();
8 z.setHealth(100);
9 z.setSpeed(2);
10 z.setDamage(10);
11 z.setPosition(x, y);
12 z.setTexture("zombie_walk.png");
13 // ... 10 more setup lines
14 }
15 else if (type.equals("skeleton")) {
16 Skeleton s = new Skeleton();
17 s.setHealth(80);
18 s.setSpeed(4);
19 s.setDamage(15);
20 s.setBowRange(50);
21 s.setPosition(x, y);
22 s.setTexture("skeleton_idle.png");
23 // ... 10 more setup lines
24 }
25 else if (type.equals("dragon")) {
26 Dragon d = new Dragon();
27 d.setHealth(500);
28 d.setSpeed(8);
29 d.setFireDamage(50);
30 d.setWingspan(20);
31 d.setPosition(x, y);
32 d.setTexture("dragon_fly.png");
33 // ... 10 more setup lines
34 }
35 // This grows with every new enemy type...
36 }
37}This same if-else block might be copy-pasted in GameWorld, LevelLoader, MultiplayerManager, and more. If you change how a Zombie is created, you need to find and update EVERY copy.
The pain: Object creation logic is scattered, duplicated, and tightly coupled to specific classes. Adding a new enemy means modifying every place enemies are created. Testing is hard because you can't mock the creation process.
How it works -- Step by Step
- Define a common interface or abstract class for the products (the objects being created).
- Create concrete product classes that implement the interface (Zombie, Skeleton, Dragon).
- Create a Factory class with a method that takes a parameter and returns the correct product.
- Move ALL creation logic into the factory -- constructors, setup, configuration, everything.
- Client code asks the factory for objects instead of using
newdirectly. The client only knows about the interface, never the concrete classes.
Let's Build It Together
Let's build a Video Game Character Creator -- think of those screens where you pick a character class!
1// ========================================
2// Step 1: Define what every game character can do
3// ========================================
4
5public interface GameCharacter {
6 void attack();
7 void defend();
8 void specialAbility();
9 String getName();
10 int getHealth();
11}1// ========================================
2// Step 2: Create concrete character classes
3// Each has its own unique stats and abilities
4// ========================================
5
6public class Warrior implements GameCharacter {
7 private String name;
8 private int health;
9 private int armor;
10
11 // Package-private constructor -- only the factory should call this!
12 Warrior(String name) {
13 this.name = name;
14 this.health = 150; // Warriors are tanky
15 this.armor = 80;
16 }
17
18 @Override
19 public void attack() {
20 System.out.println(name + " swings a mighty sword! Deals 25 damage.");
21 }
22
23 @Override
24 public void defend() {
25 System.out.println(name + " raises a shield! Blocks " + armor + "% of damage.");
26 }
27
28 @Override
29 public void specialAbility() {
30 System.out.println(name + " uses BATTLE CRY! All allies gain +10 attack for 5 seconds.");
31 }
32
33 @Override
34 public String getName() { return name; }
35
36 @Override
37 public int getHealth() { return health; }
38}
39
40public class Mage implements GameCharacter {
41 private String name;
42 private int health;
43 private int mana;
44
45 Mage(String name) {
46 this.name = name;
47 this.health = 80; // Mages are squishy
48 this.mana = 200; // But lots of mana!
49 }
50
51 @Override
52 public void attack() {
53 System.out.println(name + " casts Fireball! Deals 40 magic damage.");
54 }
55
56 @Override
57 public void defend() {
58 System.out.println(name + " conjures a Magic Shield! Absorbs 50 damage.");
59 }
60
61 @Override
62 public void specialAbility() {
63 System.out.println(name + " uses METEOR STORM! Rains fire on all enemies for 60 damage!");
64 }
65
66 @Override
67 public String getName() { return name; }
68
69 @Override
70 public int getHealth() { return health; }
71}
72
73public class Archer implements GameCharacter {
74 private String name;
75 private int health;
76 private int range;
77
78 Archer(String name) {
79 this.name = name;
80 this.health = 100;
81 this.range = 50; // Archers attack from far away
82 }
83
84 @Override
85 public void attack() {
86 System.out.println(name + " fires an arrow from " + range + " yards! Deals 30 damage.");
87 }
88
89 @Override
90 public void defend() {
91 System.out.println(name + " does a backflip to dodge! +50% evasion for 3 seconds.");
92 }
93
94 @Override
95 public void specialAbility() {
96 System.out.println(name + " uses RAIN OF ARROWS! Fires 20 arrows in an area!");
97 }
98
99 @Override
100 public String getName() { return name; }
101
102 @Override
103 public int getHealth() { return health; }
104}
105
106public class Healer implements GameCharacter {
107 private String name;
108 private int health;
109 private int healPower;
110
111 Healer(String name) {
112 this.name = name;
113 this.health = 90;
114 this.healPower = 40;
115 }
116
117 @Override
118 public void attack() {
119 System.out.println(name + " throws a holy light bolt! Deals 15 damage.");
120 }
121
122 @Override
123 public void defend() {
124 System.out.println(name + " casts Heal! Restores " + healPower + " HP.");
125 }
126
127 @Override
128 public void specialAbility() {
129 System.out.println(name + " uses DIVINE RESURRECTION! Revives a fallen ally!");
130 }
131
132 @Override
133 public String getName() { return name; }
134
135 @Override
136 public int getHealth() { return health; }
137}1// ========================================
2// Step 3: The FACTORY -- centralizes ALL creation logic
3// ========================================
4
5public class CharacterFactory {
6
7 // An enum for type safety (better than raw strings!)
8 public enum CharacterClass {
9 WARRIOR, MAGE, ARCHER, HEALER
10 }
11
12 // The factory method -- takes a type and name, returns a ready-to-use character
13 public static GameCharacter createCharacter(CharacterClass type, String name) {
14 switch (type) {
15 case WARRIOR:
16 System.out.println("Forging armor for " + name + "...");
17 return new Warrior(name);
18
19 case MAGE:
20 System.out.println("Enchanting staff for " + name + "...");
21 return new Mage(name);
22
23 case ARCHER:
24 System.out.println("Crafting bow for " + name + "...");
25 return new Archer(name);
26
27 case HEALER:
28 System.out.println("Blessing robes for " + name + "...");
29 return new Healer(name);
30
31 default:
32 throw new IllegalArgumentException("Unknown character class: " + type);
33 }
34 }
35}1// ========================================
2// Step 4: The game uses the factory -- never uses "new" directly!
3// ========================================
4
5public class GameApp {
6 public static void main(String[] args) {
7 System.out.println("=== CHARACTER SELECT ===\n");
8
9 // Create characters through the factory
10 GameCharacter player1 = CharacterFactory.createCharacter(
11 CharacterFactory.CharacterClass.WARRIOR, "Thorin"
12 );
13 GameCharacter player2 = CharacterFactory.createCharacter(
14 CharacterFactory.CharacterClass.MAGE, "Gandalf"
15 );
16 GameCharacter player3 = CharacterFactory.createCharacter(
17 CharacterFactory.CharacterClass.ARCHER, "Legolas"
18 );
19 GameCharacter player4 = CharacterFactory.createCharacter(
20 CharacterFactory.CharacterClass.HEALER, "Elrond"
21 );
22
23 System.out.println("\n=== BATTLE BEGINS ===\n");
24
25 // The game code only knows about GameCharacter interface
26 // It has NO idea about Warrior, Mage, Archer, Healer classes!
27 List<GameCharacter> party = List.of(player1, player2, player3, player4);
28
29 for (GameCharacter hero : party) {
30 System.out.println(hero.getName() + " (HP: " + hero.getHealth() + ")");
31 hero.attack();
32 hero.defend();
33 hero.specialAbility();
34 System.out.println();
35 }
36 }
37}Aha moment: The GameApp class never imports or references Warrior, Mage, Archer, or Healer directly. It works entirely through GameCharacter and CharacterFactory. If you add a "Ninja" class tomorrow, only the factory needs a new case -- the game code stays identical!
Visual Mental Model
1 Client Code Factory Products
2 (GameApp) (CharacterFactory) (Classes)
3 +-----------+ +------------------+
4 | | "WARRIOR" | |------> Warrior
5 | "Give me | ----------> | createCharacter |------> Mage
6 | a MAGE" | | |------> Archer
7 | | <---------- | (decides which |------> Healer
8 | Gets back | GameCharacter| class to make) |------> ??? (future)
9 | interface | +------------------+
10 +-----------+
11 |
12 v
13 Uses GameCharacter methods
14 (attack, defend, etc.)
15 without knowing the concrete class!Common Mistakes & Gotchas
- Exposing concrete classes alongside the factory -- If client code can still say
new Warrior(), the factory is pointless. Consider making constructors package-private. - Putting too much logic in the factory -- The factory should CREATE objects, not contain business logic. Keep it focused on instantiation and initial setup.
- Confusing Simple Factory with Factory Method and Abstract Factory -- Simple Factory (what we covered) is a class with a static/instance method. Factory Method uses inheritance (subclasses decide). Abstract Factory creates families of related objects. Start with Simple Factory!
- Using strings instead of enums --
createCharacter("warrior")is error-prone. Use enums for type safety. - Not returning the interface -- If your factory method returns
Warriorinstead ofGameCharacter, you lose the abstraction benefit. Always return the interface type.
Interview Tip
Factory Pattern is one of the first patterns interviewers expect you to mention. When designing a system, say: "Object creation should go through a factory so that the rest of the code is decoupled from specific classes." This is critical in problems like Parking Lot (different vehicle types), Chess (different piece types), Food Delivery (different restaurant types). Mention that it centralizes creation logic, making it easy to add new types and keeping client code clean.
Quick Quiz
- You're building a document editor that supports creating different document types: PDF, Word, and Spreadsheet. Each has different default formatting. Design a factory for this.
- What would happen if the
CharacterFactory.createCharacter()method returnedWarriorinstead ofGameCharacter? Why is this a problem?
- Your team has 5 different places in the codebase that create database connections, each with slightly different configuration. How could a factory help?
Summary -- Key Takeaways
- Factory Pattern centralizes object creation in one place, so client code doesn't need to know which concrete class to instantiate.
- Client code depends on interfaces, not implementations. The factory returns the interface type; the client never sees the concrete class.
- Adding new types is easy -- add a new class and update the factory. No client code changes needed.
- Use it when object creation is complex, involves setup/configuration, or when you have a family of related classes that should be chosen dynamically.