1. What is the Factory Pattern?
The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
In a real-world system, objects are rarely created using a simple new Object(). Object creation can be complex, involving dependency injection, configuration lookups, or conditional logic based on runtime parameters. If you spread this new keyword logic throughout your codebase, you violate the Single Responsibility Principle and make your code impossible to maintain.
2. The Core Problem: Tightly Coupled Code
Imagine you are building an e-commerce notification system.
public class OrderService {
public void sendNotification(String type) {
if (type.equals("EMAIL")) {
EmailNotification email = new EmailNotification();
email.send();
} else if (type.equals("SMS")) {
SMSNotification sms = new SMSNotification();
sms.send();
}
// What happens when we add Push Notifications? We have to modify this class!
}
}
This violates the Open/Closed Principle. Every time a new notification type is added, the OrderService must be modified.
3. The Solution: Centralized Creation
We delegate the creation logic to a dedicated "Factory" class.
// 1. The Interface
public interface Notification {
void send();
}
// 2. Concrete Classes
public class EmailNotification implements Notification {
public void send() { System.out.println("Sending Email"); }
}
public class SMSNotification implements Notification {
public void send() { System.out.println("Sending SMS"); }
}
// 3. The Factory
public class NotificationFactory {
public static Notification createNotification(String type) {
if (type == null) return null;
switch (type.toUpperCase()) {
case "EMAIL": return new EmailNotification();
case "SMS": return new SMSNotification();
default: throw new IllegalArgumentException("Unknown channel");
}
}
}
Now, the OrderService becomes completely decoupled from the creation logic:
public class OrderService {
public void notifyUser(String type) {
Notification notification = NotificationFactory.createNotification(type);
notification.send();
}
}
4. Advanced: The Factory Method Pattern (GoF)
The simple factory above still has a switch statement. The true Gang of Four (GoF) Factory Method Pattern eliminates this by relying on polymorphism.
Instead of a single factory class, we create an abstract creator class.
classDiagram
class NotificationCreator {
<<abstract>>
+createNotification() Notification
+notifyUser()
}
class EmailCreator {
+createNotification() Notification
}
class SMSCreator {
+createNotification() Notification
}
NotificationCreator <|-- EmailCreator
NotificationCreator <|-- SMSCreator
public abstract class NotificationCreator {
// The Factory Method
protected abstract Notification createNotification();
// Core Business Logic
public void notifyUser() {
Notification n = createNotification();
n.send();
}
}
public class EmailCreator extends NotificationCreator {
@Override
protected Notification createNotification() {
return new EmailNotification();
}
}
5. The "Staff" Perspective on Factories
In modern Java development (especially with Spring Boot), you rarely write manual Factory classes. The Dependency Injection Container acts as a giant, generalized Factory.
When you use @Autowired or constructor injection, Spring resolves the dependency and injects it. However, the Factory pattern is still heavily used in:
- SDK/Library Design: When you distribute a
.jar, you provide factories to allow users to instantiate complex objects without exposing the concrete implementations. - Domain-Driven Design (DDD): Aggregate roots often act as factories for their child entities to ensure business invariants are maintained during creation.
6. Interview Verbal Script
Interviewer: "What is the difference between Simple Factory, Factory Method, and Abstract Factory?"
You: "A Simple Factory is just a class with a static method containing a switch statement. It's a useful idiom but technically violates the Open/Closed Principle because adding a new type requires modifying the switch statement. The Factory Method Pattern solves this by defining an abstract method for creation, deferring the actual instantiation to subclasses. This allows us to add new types without touching existing code. An Abstract Factory takes this a step further; it is a factory of factories. It groups the creation of a 'family' of related objects. For example, creating a WindowsButton and a WindowsCheckbox together, ensuring UI consistency without exposing the concrete platform classes."
Advanced Architectural Blueprint: The Staff Perspective
In modern high-scale engineering, the primary differentiator between a Senior and a Staff Engineer is the ability to see beyond the local code and understand the Global System Impact. This section provides the exhaustive architectural context required to operate this component at a "MANG" (Meta, Amazon, Netflix, Google) scale.
1. High-Availability and Disaster Recovery (DR)
Every component in a production system must be designed for failure. If this component resides in a single availability zone, it is a liability.
- Multi-Region Active-Active: To achieve "Five Nines" (99.999%) availability, we replicate state across geographical regions using asynchronous replication or global consensus (Paxos/Raft).
- Chaos Engineering: We regularly inject "latency spikes" and "node kills" using tools like Chaos Mesh to ensure the system gracefully degrades without a total outage.
2. The Data Integrity Pillar (Consistency Models)
When managing state, we must choose our position on the CAP theorem spectrum.
| Model | latency | Complexity | Use Case |
|---|---|---|---|
| Strong Consistency | High | High | Financial Ledgers, Inventory Management |
| Eventual Consistency | Low | Medium | Social Media Feeds, Like Counts |
| Monotonic Reads | Medium | Medium | User Profile Updates |
3. Observability and "Day 2" Operations
Writing the code is only 10% of the lifecycle. The remaining 90% is spent monitoring and maintaining it.
- Tracing (OpenTelemetry): We use distributed tracing to map the request flow. This is critical when a P99 latency spike occurs in a mesh of 100+ microservices.
- Structured Logging: We avoid unstructured text. Every log line is a JSON object containing
correlationId,tenantId, andlatencyMs. - Custom Metrics: We export business-level metrics (e.g., "Orders processed per second") to Prometheus to set up intelligent alerting with PagerDuty.
4. Production Readiness Checklist for Staff Engineers
- Capacity Planning: Have we performed load testing to find the "Breaking Point" of the service?
- Security Hardening: Is all communication encrypted using mTLS (Mutual TLS)?
- Backpressure Propagation: Does the service correctly return HTTP 429 or 503 when its internal thread pools are saturated?
- Idempotency: Can the same request be retried 10 times without side effects? (Critical for Payment systems).
Critical Interview Reflection
When an interviewer asks "How would you improve this?", they are looking for your ability to identify Bottlenecks. Focus on the network I/O, the database locking strategy, or the memory allocation patterns of the JVM. Explain the trade-offs between "Throughput" and "Latency." A Staff Engineer knows that you can never have both at their theoretical maximums.
Optimization Summary:
- Reduce Context Switching: Use non-blocking I/O (Netty/Project Loom).
- Minimize GC Pressure: Prefer primitive specialized collections over standard Generics.
- Data Sharding: Use Consistent Hashing to avoid "Hot Shards."