Factory Pattern delegates object creation to a dedicated class, decoupling callers from concrete types
Key components: Product interface, concrete implementations, and a factory method (static or instance)
Performance: Factory overhead is negligible – same cost as direct instantiation
Production risk: Factory returning null or throwing unhandled exceptions causes cascading failures
Biggest mistake: Returning concrete types from factory instead of interface, breaking loose coupling
✦ Definition~90s read
What is Factory Pattern in Java?
The Factory pattern is a creational design pattern from the GoF catalog that decouples object creation from your business logic. Instead of sprinkling new calls throughout your code—which hard-codes concrete classes and makes testing or swapping implementations painful—you delegate instantiation to a dedicated method or class.
★
Imagine you walk into a coffee shop and say 'I'll have a latte.' You don't grind the beans, steam the milk, or assemble the cup yourself — the barista (the factory) handles all of that and hands you the finished drink.
In Java, this means you program to an interface or abstract class, and the factory decides which concrete implementation to return based on runtime context, configuration, or parameters. The core problem it solves is dependency rigidity: when you write new ArrayList<>() in ten places, changing to LinkedList requires ten edits; a factory centralizes that decision into one place.
In the Java ecosystem, the Factory pattern shows up everywhere: java.util.Calendar.getInstance(), java.text.NumberFormat.getNumberInstance(), and Spring’s BeanFactory are all factories. You’ll reach for it when you have a class hierarchy where the exact subclass isn’t known until runtime, or when construction logic is complex enough that you don’t want to repeat it.
The pattern has two main flavors: Factory Method (a single method that creates one type of object) and Abstract Factory (an interface for creating families of related objects without specifying their concrete classes). Don’t use a factory for simple objects with no variation—that’s over-engineering.
But when you’re building a payment system that needs to create CreditCardPayment, PayPalPayment, or CryptoPayment based on user choice, or a UI toolkit that must produce Windows-style vs. macOS-style buttons and menus, the Factory pattern is your go-to.
A common pitfall is confusing the Factory pattern with a simple static utility method. The real value comes from polymorphism: the factory itself is often an interface or abstract class, letting you swap entire creation strategies (e.g., a TestPaymentFactory that returns mocks).
In production Java codebases, you’ll see this combined with dependency injection frameworks like Spring or Guice, where the factory is injected and its create() method returns objects that are already wired with their dependencies. The pattern doesn’t eliminate new—it just moves it behind an abstraction layer that you can test, extend, and maintain without touching client code.
Plain-English First
Imagine you walk into a coffee shop and say 'I'll have a latte.' You don't grind the beans, steam the milk, or assemble the cup yourself — the barista (the factory) handles all of that and hands you the finished drink. In programming, a Factory does the same thing: you ask for an object by name, and the factory builds and returns it for you. You never need to know the messy details of how it was created.
You type new and you own that object for life. Want to swap implementations? You're rewriting constructors. Want to unit test? Hope that concrete class plays nice. The Factory Pattern in Java is your get-out-of-jail card: it hands creation logic to a dedicated method or object, so your code depends on abstractions, not hardcoded constructors. Without it, you get tight coupling, scattered instantiation, and a nightmare when requirements change.
What the Factory Pattern Actually Does in Java
The Factory pattern is a creational design pattern that delegates object instantiation to a separate method or class, decoupling the client from concrete implementations. Instead of calling new directly, you invoke a factory method that returns an instance of a common interface or abstract class. The core mechanic is simple: encapsulate the selection logic for which concrete class to instantiate, centralizing change when new types are added.
In practice, the factory method returns a type based on runtime parameters, configuration, or environment. This gives you a single point of control over object creation — essential when constructors are complex, require dependency injection, or when the concrete type must be resolved dynamically. The pattern is not about avoiding new; it's about managing where and how new happens.
Use a factory when you have multiple subclasses or implementations of an interface, and the correct one depends on input data or configuration. It shines in frameworks, plugin systems, and any codebase where adding a new implementation should not require modifying existing client code. The payoff is reduced coupling and a clear extension point — adding a new class means adding a new branch in the factory, not hunting down every switch or if-else in the codebase.
Factory vs. Abstract Factory
Factory method creates a single object; Abstract Factory creates families of related objects. Don't conflate them — the simpler Factory covers 80% of real needs.
Production Insight
A payment gateway integration used a switch statement across 15 services to instantiate processors. When a new gateway was added, three services missed the update, causing ClassNotFoundException at runtime.
The exact symptom: a 500 error with 'NoClassDefFoundError' for the new processor class, but only on two of five nodes because of a stale deployment.
Rule of thumb: if you find yourself writing the same switch/if-else for object creation in more than one place, extract it into a factory — or you will miss a branch in production.
Key Takeaway
Factory pattern decouples client code from concrete class selection, centralizing creation logic.
Use it when object type depends on runtime data or configuration — not as a default for every class.
A factory is a single point of change: adding a new implementation requires one new branch, not a hunt through the codebase.
thecodeforge.io
Factory Pattern in Java — GoF & Real-World Usage
Factory Pattern Java
The Problem With Scattering 'new' Everywhere
Let's make this concrete. Say you're building a payment processing system. At first you only support credit cards, so you write new CreditCardProcessor() in five different places. Six weeks later the product team adds PayPal. Now you're touching five files, and there's a real chance you miss one and introduce a subtle runtime bug.
This is called tight coupling — your calling code knows too much about the concrete type it's creating. It knows the class name, it knows what constructor arguments to pass, and it has to change every time the implementation changes.
The Factory Pattern breaks that coupling. Instead of your code saying 'I want a CreditCardProcessor', it says 'I want a PaymentProcessor for this payment type'. One central factory decides what gets built. When you add PayPal, you update one place: the factory. Everything else stays untouched.
This is the Open/Closed Principle in action — your system is open to extension (add new payment types) but closed to modification (don't touch existing calling code). That's the real value here, not just 'hiding the new keyword'.
TightCouplingProblem.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// This is what we're TRYING TO AVOID — tight coupling via direct instantiation.// Imagine this pattern repeated across 10 service classes.publicclassCheckoutService {
publicvoidprocessPayment(String paymentType, double amount) {
// BAD: CheckoutService is tightly coupled to concrete classes.// Adding a new payment type means editing THIS method — and every// other class that does the same thing.if (paymentType.equals("CREDIT_CARD")) {
CreditCardProcessor processor = new CreditCardProcessor(); // hard dependency
processor.charge(amount);
} elseif (paymentType.equals("PAYPAL")) {
PayPalProcessor processor = new PayPalProcessor(); // another hard dependency
processor.charge(amount);
}
// Adding CRYPTO means editing this method. And the one in RefundService.// And the one in SubscriptionService. Painful.
}
}
// The concrete classes we're leaking knowledge about:classCreditCardProcessor {
publicvoidcharge(double amount) {
System.out.println("Charging $" + amount + " to credit card.");
}
}
classPayPalProcessor {
publicvoidcharge(double amount) {
System.out.println("Charging $" + amount + " via PayPal.");
}
}
Output
Charging $99.99 to credit card.
Watch Out:
If you find yourself copy-pasting an if/else block that creates objects in multiple service classes, that's your signal a Factory is overdue. Every duplicate block is a future bug waiting to happen.
Production Insight
Direct instantiation creates hidden dependencies across the codebase.
Adding a new product type requires editing N files — N-1 will be missed.
Rule: if you search for 'new' more than once, refactor to a factory.
Key Takeaway
Scattering 'new' couples code to concrete types.
Every duplicate instantiation is a future maintenance burden.
Factory centralises change — one edit, zero ripple.
Factory Pattern UML Structure — The Classic GoF Diagram
The Gang of Four (GoF) defines the Factory Method pattern with four key participants:
Product: An interface or abstract class that defines the type of objects the factory method creates.
ConcreteProduct: Classes that implement the Product interface, each providing a different behaviour.
Creator (also called Factory): An abstract class that declares the factory method (often abstract) and may contain core logic that uses products.
ConcreteCreator: Subclasses of Creator that override the factory method to return specific ConcreteProduct instances.
The following UML diagram illustrates these relationships. Notice how the Creator depends only on the Product interface, not on concrete classes. ConcreteCreators decide what to create by overriding factoryMethod().
Key Insight
The Creator is often an abstract class that contains the core logic and calls its own factory method. Subclasses override that method to change the created product. This is exactly how frameworks like Spring and Java's SDK work.
Production Insight
Understanding GoF structure helps when reading legacy code. Many frameworks use abstract Creator classes that you extend. Be prepared to implement factoryMethod() in subclasses. The Creator's someOperation() method typically calls factoryMethod() and works with the returned Product through polymorphism.
Key Takeaway
Factory Method pattern defines a separate method for object creation, letting subclasses decide the concrete class. The Creator remains decoupled from concrete products.
UML class diagram for Factory Method pattern
Applicability — When Should You Reach for the Factory Pattern?
The Factory Pattern (and its variants) fits specific situations. Here are the clearest criteria for when to use it:
A class cannot anticipate the types of objects it must create. For example, a framework that manages user interface widgets doesn't know in advance what OS the application will run on — it relies on a factory to produce the appropriate set of buttons and dialogs.
You want to delegate the responsibility of object creation to subclasses. When the core algorithm is fixed but the product creation varies, the Factory Method pattern lets subclasses decide the concrete class without modifying the algorithm.
You need to isolate complex creation logic. If constructing an object involves configuration, dependency injection, or external resource fetching, centralising that logic in a factory prevents duplication and makes it testable.
You anticipate adding new product types frequently. Factories make it easy to introduce new implementations by adding a single case without touching existing callers.
You want to provide a library of related objects. The Abstract Factory pattern is ideal when a family of products must be used together (e.g., a consistent UI theme).
On the other hand, avoid factories when you have only one concrete product and no foreseeable variation. Premature abstraction adds complexity without payoff.
When Not to Use
If you only have one concrete product and no plans to extend, a factory adds unnecessary complexity. Use direct instantiation until variation arrives. A good rule: add a factory the third time you write the same creation code.
Production Insight
In enterprise apps, factories are introduced retrospectively during refactoring. The first sign is when a switch/if chain appears in multiple places. Extract that into a factory and watch your code become more maintainable. Another sign: unit tests that stub static methods — a factory interface eliminates the need for PowerMock.
Key Takeaway
Use Factory Pattern when the client code must be decoupled from concrete classes, or when creation logic may change or be extended later. Don't over-engineer before the need arises.
Building a Clean Factory Method Pattern From Scratch
The Factory Method Pattern introduces a dedicated creator — a single method (or class) whose only job is to decide which concrete object to build and return. The calling code only ever talks to the abstract type (an interface or abstract class), never to the concrete implementation.
Here's the structure: define an interface (the product), create concrete implementations of it, then write a factory class with a static method that takes a parameter and returns the right implementation. The magic is in the return type — it's always the interface, so the caller never sees the concrete class at all.
This works because of polymorphism. Your CheckoutService holds a reference of type PaymentProcessor. Whether that reference points to a CreditCardProcessor or a CryptoProcessor at runtime is none of its business. All it knows is 'this thing has a charge() method', and that's all it needs to know.
Let's build this properly — interface first, then implementations, then the factory, then the client.
PaymentProcessorFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// ─── Step 1: Define the Product Interface ───────────────────────────────────// All payment processors must implement this contract.// The factory will always return this type — callers never see the concrete class.publicinterfacePaymentProcessor {
voidcharge(double amount);
voidrefund(double amount);
}
// ─── Step 2: Concrete Implementations ───────────────────────────────────────classCreditCardProcessorimplementsPaymentProcessor {
@Overridepublicvoidcharge(double amount) {
// Real impl would call a payment gateway SDK hereSystem.out.println("[CreditCard] Charging $" + amount + " via Stripe gateway.");
}
@Overridepublicvoidrefund(double amount) {
System.out.println("[CreditCard] Refunding $" + amount + " to card.");
}
}
classPayPalProcessorimplementsPaymentProcessor {
@Overridepublicvoidcharge(double amount) {
System.out.println("[PayPal] Charging $" + amount + " via PayPal REST API.");
}
@Overridepublicvoidrefund(double amount) {
System.out.println("[PayPal] Refunding $" + amount + " to PayPal account.");
}
}
classCryptoProcessorimplementsPaymentProcessor {
@Overridepublicvoidcharge(double amount) {
System.out.println("[Crypto] Charging $" + amount + " worth of BTC on-chain.");
}
@Overridepublicvoidrefund(double amount) {
// Crypto refunds are a manual process in realitySystem.out.println("[Crypto] Initiating manual refund of $" + amount + ".");
}
}
// ─── Step 3: The Factory ─────────────────────────────────────────────────────// This is the ONLY place that knows about concrete classes.// To add a new payment type: add it here. Nothing else changes.classPaymentProcessorFactory {
// Using an enum as the key is safer than raw Strings — typos become// compile errors instead of silent runtime failures.publicenumPaymentType {
CREDIT_CARD, PAYPAL, CRYPTO
}
publicstaticPaymentProcessorcreate(PaymentType type) {
// Switch expression (Java 14+) — clean, exhaustive, no fall-through bugsreturnswitch (type) {
case CREDIT_CARD -> new CreditCardProcessor(); // factory decides the concrete typecasePAYPAL -> newPayPalProcessor();
caseCRYPTO -> newCryptoProcessor();
// No default needed — enum exhaustiveness is checked at compile time
};
}
}
// ─── Step 4: The Client (CheckoutService) ───────────────────────────────────// Notice: CheckoutService imports ZERO concrete processor classes.// It only knows about PaymentProcessor (the interface) and PaymentProcessorFactory.classCheckoutService {
privatefinalPaymentProcessorFactory.PaymentType preferredPaymentType;
publicCheckoutService(PaymentProcessorFactory.PaymentType preferredPaymentType) {
this.preferredPaymentType = preferredPaymentType;
}
publicvoidcompletePurchase(double orderTotal) {
// Ask the factory for the right processor — we don't care which class comes backPaymentProcessor processor = PaymentProcessorFactory.create(preferredPaymentType);
processor.charge(orderTotal); // polymorphic call — works for any implementationSystem.out.println("Purchase complete. Total charged: $" + orderTotal);
}
}
// ─── Step 5: Main — wire it together and run ─────────────────────────────────classMain {
publicstaticvoidmain(String[] args) {
// Simulate three different customers with different payment preferencesCheckoutService cardCustomer = newCheckoutService(PaymentProcessorFactory.PaymentType.CREDIT_CARD);
CheckoutService paypalCustomer = newCheckoutService(PaymentProcessorFactory.PaymentType.PAYPAL);
CheckoutService cryptoCustomer = newCheckoutService(PaymentProcessorFactory.PaymentType.CRYPTO);
cardCustomer.completePurchase(49.99);
paypalCustomer.completePurchase(149.00);
cryptoCustomer.completePurchase(999.00);
}
}
Output
[CreditCard] Charging $49.99 via Stripe gateway.
Purchase complete. Total charged: $49.99
[PayPal] Charging $149.0 via PayPal REST API.
Purchase complete. Total charged: $149.0
[Crypto] Charging $999.0 worth of BTC on-chain.
Purchase complete. Total charged: $999.0
Pro Tip:
Always use an enum (or a registry map) as the factory's key instead of a raw String. String-based factories fail silently at runtime with a null or exception on a typo. Enum-based factories make invalid types a compile error — caught before the code ever runs.
Production Insight
Enum-based factories shift errors from runtime to compile time.
But adding a new enum value without updating the switch still breaks production.
Rule: always include a default case or a static test that verifies all enum values are mapped.
Key Takeaway
Use enum keys, not strings, in factory switches.
Compile-time safety beats runtime null checks.
Test that every enum value has a corresponding case.
Abstract Factory — When You Need Families of Related Objects
The simple Factory Method is perfect for creating one type of object. But sometimes you need to create a family of related objects that must be used together. That's where the Abstract Factory Pattern comes in — think of it as a factory of factories.
A great real-world example: a UI toolkit. A Windows-themed UI needs a Windows-style Button AND a Windows-style Dialog AND a Windows-style TextField — all matching. A Mac UI needs the Mac versions of all three. You can't mix a Mac Button with a Windows Dialog; they need to be consistent.
The Abstract Factory defines an interface for creating each type of object in the family. Concrete factories (WindowsUIFactory, MacUIFactory) implement that interface and produce the matching set. The application only ever holds a reference to the abstract factory — swap the factory, and every single component produced is automatically the right theme.
This is a step up in complexity from the simple factory, so only reach for it when you genuinely have a consistency requirement across multiple related types.
AbstractUIFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// ─── Product Interfaces ──────────────────────────────────────────────────────// Each UI component type gets its own interface.interfaceButton {
voidrender();
voidonClick();
}
interfaceDialog {
voidshow(String message);
}
// ─── Windows Concrete Products ───────────────────────────────────────────────classWindowsButtonimplementsButton {
@Overridepublicvoidrender() {
System.out.println("[Windows] Rendering a flat, square button with ClearType font.");
}
@OverridepublicvoidonClick() {
System.out.println("[Windows] Playing Windows click sound effect.");
}
}
classWindowsDialogimplementsDialog {
@Overridepublicvoidshow(String message) {
System.out.println("[Windows] Modal dialog box: " + message);
}
}
// ─── Mac Concrete Products ───────────────────────────────────────────────────classMacButtonimplementsButton {
@Overridepublicvoidrender() {
System.out.println("[Mac] Rendering a rounded, glossy button with SF Pro font.");
}
@OverridepublicvoidonClick() {
System.out.println("[Mac] Playing macOS click haptic feedback.");
}
}
classMacDialogimplementsDialog {
@Overridepublicvoidshow(String message) {
System.out.println("[Mac] HUD-style dialog sheet: " + message);
}
}
// ─── Abstract Factory Interface ──────────────────────────────────────────────// This is the core of the Abstract Factory pattern.// It declares a creation method for EACH product in the family.interfaceUIComponentFactory {
ButtoncreateButton();
DialogcreateDialog();
// If we add TextField later, we add createTextField() here// and implement it in ALL concrete factories — compiler enforces completeness.
}
// ─── Concrete Factories ──────────────────────────────────────────────────────// Each factory produces a CONSISTENT family of components.// You can't accidentally mix Mac buttons with Windows dialogs.classWindowsUIFactoryimplementsUIComponentFactory {
@OverridepublicButtoncreateButton() {
return new WindowsButton(); // guaranteed Windows-themed
}
@OverridepublicDialogcreateDialog() {
return new WindowsDialog(); // guaranteed Windows-themed
}
}
classMacUIFactoryimplementsUIComponentFactory {
@OverridepublicButtoncreateButton() {
return new MacButton(); // guaranteed Mac-themed
}
@OverridepublicDialogcreateDialog() {
return new MacDialog(); // guaranteed Mac-themed
}
}
// ─── Application — only depends on the abstract factory interface ─────────────classApplication {
privatefinalButton confirmButton;
privatefinalDialog errorDialog;
// The factory is injected — Application has ZERO knowledge of Windows vs Mac.// Swap the factory, and every component produced automatically changes theme.publicApplication(UIComponentFactory factory) {
this.confirmButton = factory.createButton();
this.errorDialog = factory.createDialog();
}
publicvoidrun() {
confirmButton.render();
confirmButton.onClick();
errorDialog.show("File not found — please check the path and try again.");
}
}
// ─── Main ────────────────────────────────────────────────────────────────────classUIMain {
publicstaticvoidmain(String[] args) {
String operatingSystem = System.getProperty("os.name").toLowerCase();
// Decide which factory to use ONCE — at the entry point of the app.// Everything downstream gets consistent components automatically.UIComponentFactory factory;
if (operatingSystem.contains("mac")) {
factory = newMacUIFactory();
} else {
factory = newWindowsUIFactory();
}
Application app = newApplication(factory);
app.run();
}
}
Output
// On Windows:
[Windows] Rendering a flat, square button with ClearType font.
[Windows] Playing Windows click sound effect.
[Windows] Modal dialog box: File not found — please check the path and try again.
// On Mac:
[Mac] Rendering a rounded, glossy button with SF Pro font.
[Mac] Playing macOS click haptic feedback.
[Mac] HUD-style dialog sheet: File not found — please check the path and try again.
Interview Gold:
Interviewers love asking 'when would you use Abstract Factory over Factory Method?' The answer: use Factory Method when you create ONE type of object. Use Abstract Factory when you need FAMILIES of related objects that must stay consistent with each other. Nail this distinction and you'll stand out.
Production Insight
Adding a new product family requires creating a whole new concrete factory — that's N new classes.
The complexity is justified only when cross-product consistency is mandatory.
Rule: if you only need one product type, start with Factory Method; Abstract Factory is for multi-product consistency.
Key Takeaway
Abstract Factory ensures families of objects stay consistent.
But it increases class count — don't over-engineer.
Start with Factory Method; scale to Abstract Factory only when needed.
Factory Pattern With Dependency Injection
In real production systems, factories rarely work in isolation — they're often integrated with dependency injection (DI) containers like Spring or Guice. The DI container itself is a factory: it configures bean definitions and creates objects when requested. But sometimes you still need the flexibility of a custom factory inside a container-managed application.
For example, Spring's ApplicationContext acts as an abstract factory: you ask for a bean by type or name, and it returns a fully wired instance. However, when you need to decide at runtime which implementation to create (e.g., payment processor based on user selection), you can't wire every possible processor as a bean upfront — you need a custom factory that uses the container to fetch the right implementation.
A clean approach: inject a Map<String, PaymentProcessor> where the map keys are payment types and the values are prototype-scoped beans. Spring creates the map at startup, and your factory simply looks up the right processor. This avoids switch/if altogether and gives you compile-time safety without manual factory maintenance.
PaymentProcessorFactoryWithDI.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ─── Spring-based factory using dependency injection ────────────────────────interfacePaymentProcessor {
voidcharge(double amount);
}
// Concrete implementations are Spring beans (prototype scope for thread safety)
@Component @Scope("prototype")
classCreditCardProcessorimplementsPaymentProcessor { ... }
@Component @Scope("prototype")
classPayPalProcessorimplementsPaymentProcessor { ... }
// ─── Factory that uses injected map ──────────────────────────────────────────
@ComponentclassPaymentProcessorFactory {
// Spring injects a Map<String, PaymentProcessor> where keys are bean namesprivatefinalMap<String, PaymentProcessor> processorByType;
@AutowiredpublicPaymentProcessorFactory(Map<String, PaymentProcessor> processorByType) {
this.processorByType = processorByType;
}
publicPaymentProcessorcreate(String type) {
PaymentProcessor processor = processorByType.get(type);
if (processor == null) {
thrownewIllegalArgumentException("Unknown payment type: " + type);
}
return processor;
}
}
// Client code is clean:
@ServiceclassCheckoutService {
@AutowiredprivatePaymentProcessorFactory factory;
publicvoidcheckout(String type, double amount) {
PaymentProcessor processor = factory.create(type);
processor.charge(amount);
}
}
// Adding a new payment type? Just add a new @Component (and a bean name in properties).// The factory and client code don't change. Open/Closed Principle in practice.
Pro Tip:
When integrating factory with DI, prefer injection of a Map of beans over writing manual switch statements. Spring's Map injection ensures you never forget to register a new implementation — if the bean exists, it's automatically included.
Production Insight
Using DI with a factory reduces boilerplate but introduces a runtime dependency on the container.
Misconfigured bean scopes (e.g., singleton in multi-threaded factory) cause data races.
Rule: for stateful processors, use prototype scope; for stateless, use singleton; never assume default scope is safe.
Key Takeaway
DI containers can act as backbone for factory pattern.
Injecting a Map of beans eliminates switch statements entirely.
But watch bean scopes: prototype for stateful, singleton for stateless.
Testing With the Factory Pattern
One of the biggest advantages of the Factory Pattern is how it improves testability. When your calling code depends on an interface and creates objects through a factory, you can easily swap real implementations with mocks or stubs during unit tests. Without a factory, you'd have to modify the class under test or resort to bytecode manipulation (PowerMock) — both are fragile and slow.
Consider the CheckoutService from earlier: it depends on PaymentProcessorFactory.PaymentType to get a processor. In a unit test, instead of creating a real CreditCardProcessor (which might call an external gateway), you can inject a mock PaymentProcessorFactory that returns a mock processor. Or better, refactor the factory to be an interface and inject a test double.
A cleaner approach: make the factory a dependency of the service (dependency injection) rather than calling a static factory method. Then in tests, you provide a test factory that returns canned responses. This follows the Dependency Inversion Principle and makes your code truly testable.
The pattern itself doesn't guarantee testability — it's how you wire it. Static factories are less testable than instance factories that can be swapped. Always prefer an instance factory with an interface that can be mocked.
FactoryPatternTestability.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ─── Interface-based factory (testable) ─────────────────────────────────────interfacePaymentProcessorFactory {
PaymentProcessorcreate(PaymentType type);
}
// Real implementationclassRealPaymentProcessorFactoryimplementsPaymentProcessorFactory {
@OverridepublicPaymentProcessorcreate(PaymentType type) {
returnswitch (type) {
case CREDIT_CARD -> newCreditCardProcessor();
casePAYPAL -> newPayPalProcessor();
};
}
}
// Client that depends on factory interfaceclassCheckoutService {
privatefinalPaymentProcessorFactory factory;
// Factory is injected — easy to mock in testspublicCheckoutService(PaymentProcessorFactory factory) {
this.factory = factory;
}
publicvoidcheckout(double amount, PaymentType type) {
PaymentProcessor processor = factory.create(type);
processor.charge(amount);
}
}
// ─── Test with Mockito ───────────────────────────────────────────────────────classCheckoutServiceTest {
@TestvoidshouldChargeViaCreditCard() {
// Given a mock factoryPaymentProcessor mockProcessor = mock(PaymentProcessor.class);
PaymentProcessorFactory mockFactory = mock(PaymentProcessorFactory.class);
when(mockFactory.create(PaymentType.CREDIT_CARD)).thenReturn(mockProcessor);
CheckoutService service = newCheckoutService(mockFactory);
// When
service.checkout(100.0, PaymentType.CREDIT_CARD);
// Thenverify(mockProcessor).charge(100.0);
}
}
// Notice: No real CreditCardProcessor is instantiated. No external dependencies.// Test runs in milliseconds and never touches a database or network.// That's the power of factory + DI.
Key Insight:
A static factory method (like PaymentProcessorFactory.create()) cannot be mocked easily. If you need testability, make the factory an interface and inject it. Your unit tests will thank you.
Production Insight
Legacy code with static factories and 'new' calls is notoriously hard to test.
Refactoring to interface-based factory is the first step to covering untested code with unit tests.
Rule: if you can't instantiate a class without touching a database, it's a factory candidate.
Testability is the real ROI of the Factory Pattern.
Factory Pattern in the Java Standard Library — It's Already Everywhere
One of the best ways to solidify a pattern is to see where it already exists in code you use every day. The Java standard library is full of Factory Method implementations — and spotting them will train your eye to recognise the pattern instinctively.
Calendar.getInstance() returns the right Calendar subclass for your locale — you never call new GregorianCalendar() directly. NumberFormat.getCurrencyInstance() returns a locale-appropriate formatter. DriverManager.getConnection() returns the right JDBC Connection implementation for whichever database driver you've registered on the classpath.
Spring Framework takes this further with its ApplicationContext, which is essentially a giant Abstract Factory — you ask for a bean by type or name and Spring decides what concrete object to build and return, handling lifecycle, proxies, and dependency injection transparently.
Studying how these APIs are designed teaches you the pattern better than any textbook. Next time you call a static getInstance() or create() method in Java, ask yourself: 'What is this hiding from me, and why?'
FactoryPatternInJavaSDK.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Set;
publicclassFactoryPatternInJavaSDK {
publicstaticvoidmain(String[] args) {
// ── Calendar.getInstance() ───────────────────────────────────────────// Returns a BuddhistCalendar in Thailand, a JapaneseImperialCalendar in Japan,// a GregorianCalendar everywhere else. You don't pick — the factory picks.Calendar today = Calendar.getInstance(); // factory method — returns correct subclassSystem.out.println("Today's year: " + today.get(Calendar.YEAR));
System.out.println("Calendar impl class: " + today.getClass().getSimpleName());
// ── NumberFormat factory methods ─────────────────────────────────────// Same interface, radically different formatting behaviour by locale.// You never call `new SomeCurrencyFormat()` — the factory handles it.NumberFormat usDollarFormat = NumberFormat.getCurrencyInstance(Locale.US);
NumberFormat euroFormat = NumberFormat.getCurrencyInstance(Locale.GERMANY);
NumberFormat japanYenFormat = NumberFormat.getCurrencyInstance(Locale.JAPAN);
double price = 1299.99;
System.out.println("US: " + usDollarFormat.format(price)); // factory-produced formatterSystem.out.println("Germany: " + euroFormat.format(price)); // different impl, same interfaceSystem.out.println("Japan: " + japanYenFormat.format(price)); // yet another impl// ── Collections factory methods (Java 9+) ────────────────────────────// List.of() and Set.of() return private internal implementations// (ImmutableCollections$List12, etc.) — you never see or care about the// concrete class. Classic factory pattern: ask for a List, get an optimised impl.List<String> paymentMethods = List.of("CREDIT_CARD", "PAYPAL", "CRYPTO");
Set<String> currencies = Set.of("USD", "EUR", "JPY", "BTC");
System.out.println("Payment methods: " + paymentMethods);
System.out.println("List impl class: " + paymentMethods.getClass().getName()); // not java.util.ArrayList!System.out.println("Currencies: " + currencies);
}
}
Output
Today's year: 2025
Calendar impl class: GregorianCalendar
US: $1,299.99
Germany: 1.299,99 €
Japan: ¥1,300
Payment methods: [CREDIT_CARD, PAYPAL, CRYPTO]
List impl class: java.util.ImmutableCollections$List12
Currencies: [USD, BTC, EUR, JPY]
Pro Tip:
Run paymentMethods.getClass().getName() on a List.of() result and look at what comes back. It's not ArrayList — it's an internal optimised class. This is the Factory Pattern protecting you from implementation details that the JDK team can freely change in future versions without breaking your code.
Production Insight
Real-world factories in the JDK are designed for extreme performance — ImmutableCollections uses compact internal representations.
If you ever need to mimic that, consider using factory methods from the JDK as benchmarks.
Rule: study the JDK's factories; they represent decades of production-hardened design choices.
Key Takeaway
Java's standard library is a living factory pattern reference.
Calendar, NumberFormat, Collections — each is a factory.
Recognising these patterns in familiar APIs solidifies the concept.
Simple Factory vs Factory Method vs Abstract Factory — A Side-by-Side Comparison
While all three patterns centralise object creation, they differ in flexibility, complexity, and coupling. The following table highlights key differences:
Aspect
Simple Factory
Factory Method
Abstract Factory
Purpose
Centralise creation of a single product type
Delegate creation to subclasses, create one product type
Create families of related products
Structure
Single static/instance method with a switch
Abstract Creator with factoryMethod() overridden by subclasses
Interface with multiple creation methods, each returning a product type
Flexibility
Low — hard-coded mapping in one method
Medium — subclasses can decide product
High — can swap entire families
Coupling
Client depends on factory, product interface
Client depends on Creator abstract class
Client depends on AbstractFactory interface
Extensibility
Add case in factory method
Add new ConcreteCreator subclass
Add new ConcreteFactory (must implement all methods)
Complexity
Low
Medium
High
When to use
Simple object creation varying by one parameter
Framework where subclasses define products
When multiple products must be consistent (e.g., UI themes)
A Simple Factory is not a true GoF pattern but a common idiom — often sufficient for small projects. Factory Method is the formal GoF pattern, allowing inheritance-based customisation. Abstract Factory is the most powerful but also the most complex. Choose based on how many product types you need and whether they must be consistent.
Interview Favourite
Expect questions on the difference between Simple Factory, Factory Method, and Abstract Factory. Know when each is appropriate and be ready to give real-world examples.
Production Insight
In production, Simple Factory is often used internally within a module. Factory Method appears in frameworks (e.g., servlet containers). Abstract Factory is common in cross-platform libraries. Choose the variant that matches your need for flexibility vs. simplicity.
Key Takeaway
Simple Factory for one product, Factory Method for subclass control, Abstract Factory for product families. Complexity should match growth: start simple, evolve as needed.
Factory Pattern — Pros and Cons
No pattern is a silver bullet. Here are the trade-offs to consider when adopting the Factory Pattern:
Pros
Cons
Decoupling — Client code depends on interfaces, not concrete classes
Increased complexity — Adds extra classes and indirection
Open/Closed Principle — New products can be added without modifying existing callers
May introduce unnecessary abstraction — Overuse for single-product scenarios
Testability — Easy to mock factories and swap implementations in tests
More classes to maintain — Each new product needs a new class and factory update
Centralised creation logic — Changes to object construction happen in one place
Can become a God object — If the factory handles too many types, it grows unwieldy
Reusable — Same factory can be used across multiple clients
Not suitable for very simple creation — When constructor logic is trivial, factory adds overhead
Static factories break testability — If not designed as interface-injected
The key is to evaluate whether the benefits outweigh the costs in your specific context. For a payment system with frequent gateway additions, the factory pays for itself quickly. For a utility class that creates one internal object, it's over-engineering.
Overuse Alert
Factories can become an anti-pattern if you create them prematurely. Only add a factory when you have at least two variations or anticipate future ones. A good heuristic: add a factory the third time you write the same new call.
Production Insight
In production, factory overhead is negligible. The real cost is cognitive: a new developer must understand where objects come from. Keep factories focused and well-named. If a factory has more than 10 product types, consider splitting into multiple factories.
Key Takeaway
Factory Pattern trades a small increase in initial complexity for large gains in maintainability and testability when variation exists. Avoid premature abstraction.
Practice Exercises — Sharpen Your Factory Pattern Skills
The best way to internalise the Factory Pattern is to implement it yourself. Try these exercises, each reflecting a real-world scenario:
1. Payment Gateway Factory You're building an e-commerce platform that supports Stripe, PayPal, and Square. Define a PaymentGateway interface with processPayment(double amount) and refundPayment(double amount). Create concrete classes for each gateway. Build a factory that takes a PaymentGatewayType enum and returns the correct gateway. Hint: Use a switch expression (Java 14+) or a Map<String, PaymentGateway>.
2. Notification Factory A mobile app must send notifications via Email, SMS, and Push. Create a NotificationSender interface with send(String recipient, String message). Implement concrete senders for each channel. The factory should accept a NotificationChannel enum. Add a UserNotificationPreferences that the factory could use to route notifications. Hint: Think about thread safety if senders are stateful.
3. Database Driver Factory You need to connect to MySQL, PostgreSQL, or MongoDB based on a configuration string. Define a DatabaseConnection interface with connect() and executeQuery(String sql). Implement drivers for each database. The factory reads the connection string and returns the appropriate driver. Hint: Use String.contains() or parse the JDBC prefix (e.g., "jdbc:mysql"). Extend the factory to return a connection pool instead of a single connection.
4. Logging Framework Factory Your application needs Console, File, and Cloud logging. Define a Logger interface with log(LogLevel level, String message). Create concrete loggers. The factory should support dynamic switching (e.g., from config file). Challenge: Make the factory return a composite logger that sends to multiple destinations.
5. Shape Factory (for a Drawing App) You're building a vector drawing tool that supports Circle, Rectangle, and Triangle. Each shape must implement calculateArea(), draw(), and resize(double factor). The factory creates shapes based on user input. Hint: Use an enum for shape types and provide additional parameters (like radius for Circle) via a builder pattern inside the factory.
Try to implement each exercise with JUnit tests that mock the factory. This will solidify both the pattern and testability.
Try Implementing
Implement at least one exercise fully with unit tests to see the pattern in action. Start with the Payment Gateway Factory — it's the closest to real-world production code.
Production Insight
These exercises mirror real-world use cases. Payment gateways, notifications, databases, logging, and drawing shapes are common scenarios where factories shine. Practicing with them will make you faster at recognising factory opportunities in your daily work.
Key Takeaway
Practice implementing factories for diverse scenarios; it will become second nature in design discussions and interviews. Each exercise reinforces decoupling and testability.
What the Factory Method Design Pattern Actually Is
Most blog posts will tell you the Factory Method is about 'defining an interface for creating an object, but letting subclasses decide which class to instantiate.' That's the textbook definition. Here's what it means in practice: you've got a method that returns a product, and you've hidden the concrete type behind an abstraction. The caller doesn't know — and shouldn't care — whether it gets a DieselEngine or a HydrogenFuelCell. That's the entire point.
Why does this matter? Because every time you write new GasolineEngine() scattered across your codebase, you've introduced a compile-time dependency on a concrete class. Change that to new ElectricMotor() and you're hunting down every instantiation. The Factory Method centralizes that decision. One method, one place to change. The subclasses decide which concrete object to create, and the client just calls the factory method and gets back something it can use through the interface.
This isn't abstract theory. This is the pattern that powers Spring's FactoryBean, JPA's EntityManagerFactory, and half the instantiation logic in the JDK. If you're not using it, you're doing manual work the framework could be doing for you.
Never put conditional logic (if/switch) inside the factory method to decide which concrete class to return. That defeats the purpose — you've just moved the hardcoding. The whole point is that each subclass makes that decision. If you need runtime configuration, use Abstract Factory or pass a config parameter.
Key Takeaway
A factory method hides the concrete type behind an abstraction. The caller doesn't know what it gets, and it shouldn't care.
Key Components — The Moving Parts You Actually Need to Know
Every Factory Method implementation has four players. Miss one, and the pattern breaks down into a tangled mess of dependencies. Here they are, no UML diagram fluff:
Product — The interface or abstract class defining what the factory method returns. This is your contract. Keep it narrow. A product interface with fifteen methods is a sign you're doing it wrong.
ConcreteProduct — The actual implementation. DieselEngine, ElectricMotor, whatever. These are the classes your factory method instantiates. They should never leak outside the factory chain.
Creator — The abstract class or interface declaring the factory method. This is where createEngine() lives. The Creator can also provide a default implementation that returns a common product, letting subclasses override when needed.
ConcreteCreator — The subclass that overrides the factory method to return a specific ConcreteProduct. This is the only place where new appears for that product type.
That's it. Four roles. The Creator doesn't know what ConcreteProduct it'll get — it just knows it'll get something that conforms to the Product interface. This is the Hollywood Principle in action: don't call us, we'll call you.
// new StripePaymentService().processPayment(49.99)
// Output: Charging $49.99 via Stripe
// new PayPalPaymentService().processPayment(29.99)
// Output: Charging $29.99 via PayPal
Senior Shortcut:
If you find yourself writing a Creator class that never has a default factory method implementation, ask if you really need the pattern. Sometimes a static factory method on the Product interface is cleaner. Factory Method shines when the Creator has shared logic that uses the product.
Key Takeaway
Four roles — Product, ConcreteProduct, Creator, ConcreteCreator. The Creator only knows the interface. The ConcreteCreator knows the concrete class.
Factory Pattern vs. Direct Construction — The Performance and Memory Argument
Every 'new' call in Java locks you into a concrete class at compile time. The Factory Pattern defers object creation to runtime, which changes how the JVM handles memory and dispatch. Direct construction with 'new' forces early binding — the JVM knows the exact type at compile time and can inline constructors. A Factory Method, by contrast, uses virtual dispatch through an interface or abstract class. This adds a single vtable lookup per object creation (negligible for most apps). The real cost is in object lifecycles: factories let you pool objects, cache frequently used instances, or return singletons without the caller knowing. In high-throughput systems, a factory can reduce GC pressure by reusing objects instead of allocating fresh ones. The tradeoff is that direct 'new' is trivially faster for one-off objects with no reuse strategy. Always measure before optimizing. But if you need to decouple creation logic from business logic, the factory's flexibility outweighs the nanosecond overhead of a virtual call.
FactoryVsNew.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — java tutorial// Direct construction — locked to ArrayListList<String> list1 = newArrayList<>();
// Factory method — deferred to implementationList<String> list2 = ListFactory.newList();
classListFactory {
staticList<String> newList() {
return new ArrayList<>(); // swap to LinkedList anytime
}
}
// Without factory: every call site must change// With factory: one line change in ListFactory
Production Trap:
Never pre-optimize by avoiding factories. The real performance killer is object allocation churn, not a virtual method call. Profile first.
Key Takeaway
Factory patterns trade compile-time binding for runtime flexibility, enabling object pooling and reduced GC pressure at the cost of one virtual dispatch per creation.
Why the Factory Pattern Exists — It's a Contract, Not a Crutch
Many developers think Factory Pattern is just a fancy way to call 'new' from a different class. That misses the point. The Factory Pattern exists to enforce a contract between the creator and the product. When you call a factory method, you're saying: 'I need an object that satisfies this interface, but I don't care about its concrete type or how it's built.' This decouples the caller from the construction logic, enabling dependency inversion. Without a factory, every client class must import both the interface and all concrete implementations. With a factory, the client depends only on the abstraction. This is not about code organization — it's about preventing your codebase from turning into a tangled mess of compile-time dependencies. In large systems, factories are the difference between swapping an implementation by changing one line vs. tracking down hundreds of scattered 'new' calls. The factory pattern is a contract enforcer: it says 'the creator knows how to build it; the client only knows how to use it.'
Don't use a factory for every object. Only apply it where the concrete type varies at runtime or where you need to control object lifecycle.
Key Takeaway
A factory is a contractual boundary: it enforces that clients depend on abstractions, not concrete classes, preventing compile-time coupling across your codebase.
● Production incidentPOST-MORTEMseverity: high
Payment Factory Returns Null After Config Migration
Symptom
All payment transactions fail with NullPointerException at PaymentProcessorFactory.create(). The stack trace points to a switch expression returning null.
Assumption
The team assumed the factory would always handle all enum values because the old switch had a default case that mapped to a fallback processor.
Root cause
During a configuration migration, a new enum value 'CRYPTO' was added to PaymentType but the switch expression in the factory was not updated. The switch (Java 14+) had no default case because the team relied on compile-time exhaustiveness, but the new value was added after compilation. The factory returned null, and the caller didn't check for null.
Fix
Add a default case that throws a descriptive IllegalArgumentException or returns a fallback implementation. Also add null checks at the caller level and central logging in the factory to capture missing type mappings.
Key lesson
Never assume factory exhaustiveness survives deployment — new enum values can be added without recompiling the factory.
Always include a default case in factory switch expressions, even with exhaustive enums, to guard against future additions.
Callers must always handle null return values from factories, either via Optional or explicit null checks.
Production debug guideSymptom → Action table for common factory failures in production4 entries
Symptom · 01
Factory returns null for a valid product type
→
Fix
Check switch/if exhaustiveness. Add logging at factory entry and exit. Verify enum values match constants.
Symptom · 02
Factory returns wrong implementation class
→
Fix
Inspect factory logic for incorrect condition. Check for copy-paste errors in case branches. Validate that the key parameter is not being transformed incorrectly.
Symptom · 03
Factory throws ClassNotFoundException or NoClassDefFoundError
→
Fix
Ensure all implementation classes are on the classpath. For reflective factories, verify class.forName() arguments. Use compile-time registration (enum+switch) instead of reflection.
Symptom · 04
Factory instantiates objects with missing dependencies
→
Fix
If factory uses constructor injection, verify that dependencies are available. Consider using a dependency injection container or a Builder inside the factory.
★ Factory Cheat SheetQuick commands and checks when factory behaviour breaks
Null from factory−
Immediate action
Add null check at caller and log the factory input parameter
Commands
System.err.println("Factory input: " + type);
Add default case in switch: default -> throw new IllegalArgumentException("Unknown type: " + type);
Fix now
Wrap factory call in Optional.ofNullable() and handle absent case
Add a guard trace: if (!expectedClass.isInstance(result)) { ... }
Factory throwing exception on new type+
Immediate action
Check if the type enum value was added but factory not updated
Commands
grep -r 'PAYMENT_TYPE' src/main/java/**/factory/
Verify that the enum and factory switch are in the same module
Fix now
Add the missing case to the switch and recompile
Factory Method vs Abstract Factory
Aspect
Factory Method
Abstract Factory
Purpose
Create one type of product
Create families of related products
Structure
Single factory class with one create() method
Factory interface with one method per product type
When to use
Object creation logic varies by a single parameter
Multiple related objects must be consistent with each other
Adding new product types
Add a new case to the factory switch/if
Add a new concrete factory class implementing all methods
Complexity
Low — easy to start with
Higher — more classes, but more flexible
Real-world Java example
Calendar.getInstance()
Spring ApplicationContext (bean factory)
Coupling to concrete classes
Isolated to factory class only
Isolated to concrete factory classes only
Client code changes when adding products
None — just update the factory
None — just add a new concrete factory
Key takeaways
1
The Factory Pattern's real value is not 'hiding new'
it's enforcing the Open/Closed Principle so you can add new types by updating one place (the factory) without touching any calling code.
2
Always return the interface type from a factory method, never the concrete class. The moment calling code can see the concrete type, the encapsulation is broken and the pattern stops paying dividends.
3
Use Factory Method for one product type, Abstract Factory for a family of related products that must stay consistent
like a full themed UI kit where mixing Windows buttons with Mac dialogs would break the design.
4
Java's standard library is full of production-grade factory pattern examples (Calendar.getInstance, NumberFormat.getCurrencyInstance, List.of)
study these APIs to internalise what good factory design looks like in the wild.
5
Prefer instance factories (with interfaces) over static factories for testability
static factories cannot be mocked in unit tests.
Common mistakes to avoid
3 patterns
×
Using raw String keys in the factory switch statement
Symptom
A typo like 'CREDITCARD' instead of 'CREDIT_CARD' returns null or throws an unhandled exception at runtime, often in production
Fix
Use an enum as the factory key. Typos in enum values are compile-time errors, not runtime surprises. Your IDE will also autocomplete enum values, eliminating the problem entirely.
×
Returning a concrete class type from the factory method instead of the interface
Symptom
Calling code starts using concrete-class-specific methods (e.g., CreditCardProcessor.setCardNumber()), tightly coupling it to one implementation and defeating the entire purpose of the pattern
Fix
Always declare the factory method's return type as the interface (PaymentProcessor, not CreditCardProcessor). If the calling code only sees the interface, it physically cannot call implementation-specific methods.
×
Putting business logic inside the factory
Symptom
The factory grows to hundreds of lines, starts importing service classes, and becomes a bottleneck that's hard to test and impossible to reuse
Fix
The factory's only responsibility is deciding which class to instantiate and returning it. Zero business logic. If you need conditional logic beyond 'which class do I create', that logic belongs in the object itself or in a separate service, not in the factory.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What is the difference between the Factory Method Pattern and the Abstra...
Q02SENIOR
How does the Factory Pattern relate to the Open/Closed Principle? Can yo...
Q03SENIOR
If a factory method needs to create objects that require expensive initi...
Q01 of 03SENIOR
What is the difference between the Factory Method Pattern and the Abstract Factory Pattern, and when would you choose one over the other?
ANSWER
The Factory Method Pattern provides a single method (usually static) that creates one type of object based on a parameter. The Abstract Factory Pattern defines an interface for creating families of related objects, ensuring consistency across those objects. Choose Factory Method when you need to create a single product that varies by a simple condition (e.g., payment processor by type). Choose Abstract Factory when you have multiple products that must be used together and must be consistent (e.g., UI components for a specific OS theme).
Q02 of 03SENIOR
How does the Factory Pattern relate to the Open/Closed Principle? Can you give a concrete example where adding a new type requires zero changes to existing calling code?
ANSWER
The Factory Pattern supports the Open/Closed Principle because the calling code depends on an abstraction (the product interface), not on concrete classes. Adding a new concrete product only requires adding a new class and updating the factory's creation logic. All existing callers remain unchanged because they only interact through the interface. Example: adding a CryptoProcessor to the payment system requires creating the class and adding a case in the factory switch. CheckoutService, RefundService, and other services that rely on PaymentProcessor need no modifications.
Q03 of 03SENIOR
If a factory method needs to create objects that require expensive initialisation — like a database connection — how would you prevent the factory from creating a new object on every call? (Follow-up probes knowledge of combining Factory with Singleton or object pooling.)
ANSWER
You can combine the Factory Pattern with other creational patterns. For expensive objects, use a Singleton (or a pool) inside the factory. The factory checks if an instance already exists; if not, it creates and caches it. For thread safety, use double-checked locking or an eager singleton. Alternatively, for a pool, use a BlockingQueue or Apache Commons Pool2 inside the factory. The key is that the factory's creation method may return existing objects — the caller shouldn't care whether it's new or cached; the factory abstracts that decision.
01
What is the difference between the Factory Method Pattern and the Abstract Factory Pattern, and when would you choose one over the other?
SENIOR
02
How does the Factory Pattern relate to the Open/Closed Principle? Can you give a concrete example where adding a new type requires zero changes to existing calling code?
SENIOR
03
If a factory method needs to create objects that require expensive initialisation — like a database connection — how would you prevent the factory from creating a new object on every call? (Follow-up probes knowledge of combining Factory with Singleton or object pooling.)
SENIOR
FAQ · 3 QUESTIONS
Frequently Asked Questions
01
What is the Factory Pattern in Java and why is it used?
The Factory Pattern is a creational design pattern that delegates object creation to a dedicated factory class or method, instead of using new directly throughout your code. It's used to decouple calling code from concrete class names, making it easy to add new types or swap implementations without changing any code that uses those objects.
Was this helpful?
02
Is the Factory Pattern the same as the Factory Method Pattern?
Not quite. 'Factory Pattern' is an informal term often used to describe a simple static factory class. The 'Factory Method Pattern' is a formal GoF pattern where a method in a class (or interface) is responsible for creating objects, and subclasses can override that method to change what gets created. Abstract Factory is a third, related pattern for creating families of objects. In practice, most teams use 'factory' loosely to mean any class whose job is creating other objects.
Was this helpful?
03
Does using a Factory Pattern make unit testing harder?
The opposite, actually. Because your calling code depends on an interface rather than a concrete class, you can easily inject a mock or test implementation during unit tests. Without the factory (i.e., with new ConcreteClass() inline), you can't swap in a test double without modifying the class under test. The factory pattern, especially when combined with dependency injection, makes classes significantly more testable.