Design Patterns
Design patterns are typically categorized into three main "buckets" based on their purpose.
1. Creational Patterns (Object Creation)
These focus on how objects are instantiated, helping to decouple your system from how its
objects are created and composed.
Singleton: Ensuring a class has only one instance (thread-safe implementation, Bill
Pugh approach, and Enum Singletons).
Factory Method: Providing an interface for creating objects but letting subclasses
decide which class to instantiate.
Abstract Factory: Creating families of related objects without specifying their
concrete classes.
Builder: Separating the construction of a complex object from its representation
(essential for modern Java/Lombok).
Prototype: Creating new objects by copying an existing object (Cloning).
2. Structural Patterns (Object Relationships)
These deal with how classes and objects are composed to form larger structures while
keeping them flexible and efficient.
Adapter: Allowing incompatible interfaces to work together (the "Translator").
Decorator: Dynamically adding responsibilities to an object without changing its
code (think Java I/O Streams).
Proxy: Providing a placeholder for another object to control access to it (crucial for
Spring AOP and Hibernate Lazy Loading).
Facade: Providing a simplified interface to a complex set of classes in a subsystem.
Bridge: Decoupling an abstraction from its implementation so the two can vary
independently.
Composite: Treating individual objects and compositions of objects uniformly.
Flyweight: Minimizing memory usage by sharing as much data as possible with
similar objects.
3. Behavioral Patterns (Object Interaction)
These focus on communication between objects and how they assign responsibilities.
Observer: A one-to-many dependency where when one object changes state,
all its dependents are notified (The heart of Event-driven programming).
Strategy: Defining a family of algorithms and making them interchangeable at
runtime (very common with Java 8 Lambdas).
Template Method: Defining the skeleton of an algorithm in an operation,
deferring some steps to subclasses (e.g., JdbcTemplate).
Chain of Responsibility: Passing a request along a chain of handlers (used in
Servlet Filters).
Command: Encapsulating a request as an object, letting you parameterize
clients with different requests.
State: Allowing an object to alter its behavior when its internal state changes.
Iterator: Accessing elements of a collection without exposing its underlying
representation.
Mediator: Reducing chaotic dependencies between objects by making them
communicate via a mediator object.
Memento: Capturing and restoring an object's internal state (Undo
functionality).
Visitor: Representing an operation to be performed on elements of an object
structure.
1. Singleton Pattern
Concept: Ensures a class has only one instance and provides a global point of access to it.
Real-World Example: A Database Connection Pool or a Log Manager. You don't want
100 different loggers writing to the same file simultaneously; you want one central
coordinator.
A. Bill Pugh Implementation (Thread-Safe & Lazy)
This is considered the most elegant way to implement a thread-safe, lazy-initialized
Singleton without using synchronized.
How it handles Multiple Threads:
It relies on the JVM Class Specifications. A class is not loaded into memory until it
is used for the first time.
The Thread-Safety Secret: The JVM guarantees that class initialization is
atomic. If Thread A and Thread B both call getInstance() for the first time, the
JVM's class loader will lock the SingletonHelper class, initialize the INSTANCE,
and only then allow both threads to see it.
Why use it? No synchronized overhead and no volatile complexity. It's clean
and high-performance.
B. Enum Singleton (The "Bulletproof" Way)
Using an Enum is the most robust and concise way to implement the Singleton
pattern in Java
Java guarantees that any Enum value is instantiated only once in a Java program.
Why use Enum for Singletons?
Compared to traditional methods (like double-checked locking), enums provide
several built-in protections:
Thread Safety: The JVM guarantees that the enum instance is created exactly
once in a thread-safe manner during class loading.
Serialization Guarantee: Traditional singletons require
a readResolve() method to prevent creating new instances during
deserialization. Enums handle this automatically; the JVM ensures that only
the existing constant is returned.
Reflection Proof: Enums are resistant to reflection attacks. If you try to
instantiate an enum via reflection, the JVM throws
an IllegalArgumentException.
Conciseness: It requires significantly less code than implementing a private
constructor, a static factory method, and volatile variables for double-checked
locking.
Key Considerations
No Lazy Loading: Enums are initialized eagerly when the class is first loaded.
If the instance is extremely resource-heavy and rarely used, a Bill Pugh
Singleton might be a better fit for lazy initialization.
Inheritance Restrictions: Enums cannot extend other classes because they
already implicitly extend [Link]. However, they can implement
interfaces.
Single Instance per JVM: Like all singletons, it is unique within a single JVM.
If you run your application in a distributed environment, you will have one
instance per node.
The Reflection Attack (Breaking the Private Constructor)
In a standard Singleton (like Bill Pugh or DCL), the constructor is private. Most
developers think that makes it safe. However, using Java's Reflection API, a
developer can change the access level of a constructor at runtime.
Why Enum is Immune:
The JVM is hard-coded to prevent this. If you try to use newInstance() on an Enum
constructor, the Constructor class throws an IllegalArgumentException.
The Internal Check: Inside the [Link]() source code,
there is a check: if (([Link]() & [Link]) != 0) throw new
IllegalArgumentException("Cannot reflectively create enum objects");.
The Result: It is physically impossible to use reflection to create a second
Enum instance.
The Serialization Attack (Cloning via Disk)
Serialization is the process of converting an object into a byte stream (to save to a
file or send over a network).
How it breaks a normal Singleton:
When you "Deserialize" (read back from a file) a standard Singleton, the JVM creates
a new object by default, even if the constructor is private.
If you forget readResolve(), every time you read the object from a file, you get a new
instance, ruining your Singleton.
Why Enum is Immune:
The JVM handles Enum serialization differently.
The Logic: Instead of saving the object's fields, the JVM only saves the Enum
name.
The Recovery: When it reads the name back (e.g., "INSTANCE"), the JVM uses the
[Link]() method to find the existing constant in memory.
The Result: It never creates a new object; it simply points back to the one already in
the JVM.
C. Double-Checked Locking (DCL)
The "Why" for Interviews:
Why the first null check? To avoid the cost of synchronized once the object is
already created.
Why the second null check? Thread A and Thread B both pass Check 1. Thread A
gets the lock, creates the object, and leaves. Thread B enters the lock; without the
second check, it would create a second object.
Why volatile? To prevent Instruction Reordering. Without it, the JVM might
allocate memory and assign the address to instance before the constructor finishes.
Another thread might see a non-null but partially initialized object.
Which one should you recommend in an interview?
If they ask "Which is best?", say:
"It depends. Enum is the most secure against reflection and serialization. However, if I need
Lazy Initialization (to save memory until the object is actually needed), the Bill Pugh
method is the most performant and readable."
2. Factory Method:
Concept: Defines an interface for creating an object but lets subclasses decide
which class to instantiate. Real-World Example: Payment Gateways. Your application
knows it needs to "Pay," but whether it uses PayPal, Stripe, or Razorpay depends on the
user's choice at runtime.
For a senior-level interview, the Factory Method isn't just about "hiding the new keyword."
It’s about the Open/Closed Principle (OCP)—your code should be open for extension
(adding new types) but closed for modification (not touching the existing logic).
1. The "Hidden" Logic: Simple Factory vs. Factory Method
In an interview, they might show you a class with a static method and a bunch of if-else
statements and ask, "Is this the Factory Method pattern?"
The Answer is: No. That is a Simple Factory.
Simple Factory: A single class that handles all logic. If you add a "WalletPayment",
you must change the code of the Factory (Violates Open/Closed Principle). Example
shown below
Factory Method Pattern: You create an abstract creator. To add a "CSV"
document, you simply create a CSVDocument class and a CSVHandler class. You
never touch the existing code.
A. The Product Interface & Concrete Products
These are the objects being created.
B. The Creator (The Factory Method)
This is the heart of the pattern. Notice it is an abstract class.
C. The Concrete Creators
Each subclass decides which document to "new up."
3. Abstract Factory:
Creating families of related objects without specifying their concrete classes.
The Core Concept: Families of Products
While a Factory Method creates one type of object (e.g., a PDFDocument), an Abstract
Factory creates a suite of related objects that must work together.
In a real-world enterprise system, a "Document" doesn't exist in a vacuum. If you are
working with PDFs, you likely also need a PDFValidator and a PDFWatermarker. You
cannot use an XMLValidator on a PDF document.
The Abstract Factory ensures that when you choose a format (like PDF), the entire "suite"
of tools matches that format.
1. The "Family" Definition
Instead of just one product, we now have a suite:
1. Document (The content)
2. Validator (To check if the content is legal)
How it handles Multiple Threads?
As a senior developer, you should mention:
"The PdfSuiteFactory is stateless. I would implement it as a Singleton (using Enum or Bill
Pugh). This way, if 100 threads are processing documents, they all share one factory
instance to get their specific tools, reducing memory overhead and ensuring consistency."
Why not just use two separate Factory Methods?
Interviewer: "Why can't I just have a DocumentFactory and a ValidatorFactory?" Your
Answer: > "Because that allows for Human Error. A developer could accidentally call
[Link]("PDF") and [Link]("XML"). By grouping them into an
Abstract Factory (Suite), I make it physically impossible for the client to mix incompatible
objects. It's a 'compile-time' guarantee of architectural consistency."
Summary Comparison (The Sequence)
1. Simple Factory: I want a PDF. (One class with an if statement).
2. Factory Method: I want a Document. (Subclasses decide if it's PDF or XML).
3. Abstract Factory: I want an entire suite of PDF tools. (A factory object provides a
Document AND a Validator that belong together).
4. Builder:
The Builder Pattern is a favorite in senior-level interviews because it addresses a very
practical problem: The Telescoping Constructor.
As a senior developer, you've likely seen constructors with 10+ parameters where half of
them are null. The Builder pattern solves this by allowing you to construct a complex object
step-by-step.
1. The Core Problem: Telescoping Constructors
Imagine you are building a User object for a system. Some users have addresses, some
don't; some have phone numbers, some don't.
The "Bad" Way:
The Builder Solution: It separates the construction logic (the steps) from the
representation (the final object).
Depth Knowledge: The Implementation
For an interview, you should implement this using a Static Inner Class. This keeps the
Builder closely tied to the class it builds while keeping the actual object Immutable.
3. Why this is "Senior" Level Knowledge
A. Immutability & Thread Safety
In an interview, mention that the Builder pattern is the best way to create Immutable
Objects. Because the User fields are final and there are no setters, once the build() method
returns the object, it can be shared across multiple threads safely without synchronization.
B. Input Validation
Don't just say it makes code "clean." Say:
"The Builder pattern allows for Object State Validation. I can verify that all mandatory fields
are present and that the data is consistent before the actual object is instantiated. This
prevents 'half-baked' objects from entering the system."
C. The Lombok Connection
In modern Java (Spring Boot), we often use @Builder.
Interview Tip: Mention that while @Builder is great for productivity, you understand
that under the hood, Lombok is generating a static inner class exactly like the one
above.
4. Multi-threading in Builder
Interviewer Question: "Is the Builder itself thread-safe?"
Your Answer:
"The resulting object is thread-safe because it's immutable. However, the Builder instance
itself is typically not thread-safe. You should not share one Builder instance across multiple
threads to build different objects simultaneously. Usually, a Builder is created, used, and
discarded within a single thread (stack-confined), which makes it safe in practice."
5. Builder vs. Abstract Factory
This is a common "confusion" question.
Abstract Factory: Focuses on what is being created (a family of objects). It returns
the object in one shot.
Builder: Focuses on how a single complex object is created. It returns the object
only at the final step (.build()).
Comparison for Quick Recall
Feature Builder Pattern
Primary Goal Step-by-step construction of complex objects.
Method Style Fluent API (Method Chaining).
Key
Avoids "Constructor Hell" and ensures Immutability.
Advantage
Objects with many optional parameters (e.g., HTTP Requests,
Best For
Configurations).
5. Prototype
The Prototype Pattern is the most specialized of the creational patterns. In a senior-level
interview, the discussion usually moves away from "how to copy" and toward performance
optimization and JVM memory management.
1. The Core Concept: Cloning vs. New
The Prototype pattern is used when the cost of creating a new object from scratch is higher
than the cost of copying an existing one.
The "Senior" Problem it Solves: Imagine an object that requires a heavy database call, a
complex calculation, or an expensive I/O operation to initialize. If you need 1,000 of these,
you don't want to hit the DB 1,000 times. You create one (the Prototype), and then you
clone it.
2. The Java Implementation: Cloneable and its Flaws
In Java, the Prototype pattern is technically implemented via the Cloneable interface and the
clone() method. However, for a senior interview, you must discuss Shallow Copy vs. Deep
Copy.
A. Shallow Copy (The Danger)
By default, [Link]() performs a shallow copy. It copies the primitive values, but for
objects, it only copies the reference.
B. Deep Copy (The Requirement)
In a deep copy, you create new instances of the internal objects so that the clone is truly
independent.
3. Why this is "Senior" Level Knowledge
A. Performance & The "Registry"
Interviewers often ask how to manage these prototypes. You use a Prototype Registry
(often a Map).
"I maintain a Map<String, Prototype> where I store initialized templates. When a thread
needs a specific configuration, it pulls the template from the map and clones it. This avoids
repeated heavy initialization logic."
B. The Problem with clone()
A very senior point to make: clone() is actually broken in Java. It doesn't call constructors,
it's difficult to use with final fields, and it throws checked exceptions.
The Alternative: Mention that in modern Java, we often use Copy Constructors or
Serialization (via Jackson/Gson) to perform deep copies instead of the Cloneable
interface.
C. Thread Safety
Since the Prototype is often stored in a Registry (shared state), you must ensure that:
1. The Registry itself is thread-safe (use ConcurrentHashMap).
2. The clone() method returns a deep copy so that Thread A modifying its clone doesn't
accidentally change the state for Thread B.
4. Real-World Examples
[Link]#clone(): The base implementation.
Chess Games: To evaluate a move, the AI clones the current board state 10,000
times to simulate future moves without messing up the actual game board.
Spring Scopes: When you use @Scope("prototype"), Spring creates a new bean
instance for every request. While it doesn't strictly use .clone() (it usually uses
reflection to call the constructor), it follows the concept of the Prototype pattern.
5. Comparison: Builder vs. Prototype
Feature Builder Prototype
Creation Logic Step-by-step assembly. One-step duplication.
Starting Point Empty/Mandatory fields. An existing, fully-loaded object.
Complexity High (lots of fluent methods). Low (one clone method).
Best For Many optional configurations. High cost of initialization.
Conclusion of Creational Patterns
We have now covered:
1. Singleton (One instance)
2. Factory Method (Subclass decides)
3. Abstract Factory (Families of objects)
4. Builder (Complex steps)
5. Prototype (Copying templates)