Singleton Design Pattern Overview
Singleton Design Pattern Overview
Intent
Ensure that a class has only one instance and provide a global point of access to it.
Motivation
Imagine you’re building an application that needs to access a shared resource—for example, a
database connection, configuration manager, or logging utility. Creating multiple instances of
these classes can lead to conflicts, performance degradation, or inconsistent behavior.
Applicability
Si
ngleton class has:
Participants
Singleton:
o Declares a static getInstance() method.
o Stores the sole instance in a private static field.
o Prevents direct construction via a private constructor.
o Handles lazy initialization and thread-safety.
Client:
o Accesses the Singleton via [Link]().
Collaborations
✅ Pros:
⚠️Cons:
Implementation
public class Singleton {
private static volatile Singleton instance; // Ensures visibility across
threads
private String data;
private Singleton() {
[Link] = data; // Initialize fields
}
Structural design patterns are concerned with how classes and objects are composed to form
larger structures. They rely on principles of inheritance and composition to enable the addition
of functionality and behavioral flexibility.
Purpose
Structural patterns provide mechanisms for assembling objects and classes into flexible,
maintainable, and scalable structures. They are particularly useful when system architecture
must accommodate change without requiring modification to existing code.
Key Characteristics
Benefits
Convert the interface of a class (e.g., FancyUIService) into another interface clients expect
(e.g., IMultiRestoApp).
Adapter allows incompatible interfaces to work together without modifying their source code.
Motivation
Imagine you’ve developed an application similar to Zomato, where you aggregate menu data
from many restaurants in XML format. Your core app (MultiRestoApp) processes this data
using an interface IMultiRestoApp, and presents it to the user via standard UI modules.
To improve the user experience, you want to integrate a powerful third-party UI library
(FancyUIService). However, this library only accepts data in JSON format. Changing the
library is not feasible — it may be closed-source or used elsewhere. Refactoring your entire
application to support JSON is also risky and inefficient.
This allows your original app to work unchanged, while seamlessly integrating the new third-
party library.
Applicability
You want to use an existing class, but its interface doesn't match what your code expects.
You want to decouple your system from third-party or legacy components.
You want to reuse functionality without rewriting or duplicating logic.
You want to follow the Open-Closed Principle by extending behavior through
composition, not modification.
Structure
Client: MultiRestoApp
Client Interface: IMultiRestoApp
Service: FancyUIService (JSON-only)
Adapter: FancyUIServiceAdapter
Participants
Client (MultiRestoApp): Contains core app logic and expects a standard XML-driven
interface.
Target (IMultiRestoApp): Defines the expected interface used by the UI modules;
defines the set of behaviors that other classes must follow to be able to collaborate with
the client.
Adaptee (FancyUIService): Third-party library with a different interface (accepts JSON
only); can’t be used directly because the client has an incompatible interface.
Adapter (FancyUIServiceAdapter): Implements client (IMultiRestoApp), wraps
(adaptee/servie) FancyUIService, and performs the XML → JSON transformation. It
translates the client calls into something that the wrapped service can understand
Collaborations
The client code interacts with the adapter through the expected interface.
The adapter internally translates inputs and delegates behavior to the adaptee.
The client remains agnostic to the use of a third-party library.
Consequences
✅ Pros:
⚠️Cons:
Implementation
// Target Interface
public interface IMultiRestoApp {
void displayMenus(String xmlData);
}
// Adaptee (3rd-party)
public class FancyUIService {
public void renderUI(JSONObject jsonData) {
// Renders a beautiful UI using JSON data
}
}
// Adapter
public class FancyUIServiceAdapter implements IMultiRestoApp {
private FancyUIService fancyUIService;
@Override
public void displayMenus(String xmlData) {
// Convert XML → JSON (simplified here)
JSONObject jsonData = [Link](xmlData);
[Link](jsonData);
}
}
BRIDGE
Intent
Decouple an abstraction from its implementation so that the two can vary independently.
Motivation
Let’s say you operate a pizza delivery system. You start with a Pizza class and define subclasses
like PepperoniPizza and VeggiePizza. Business grows, and now you want to offer pizzas
prepared in two different styles: American and Italian.
This happens because you're trying to extend a class in two independent dimensions:
The Bridge pattern solves this by splitting the two dimensions into separate class
hierarchies:
Each Restaurant holds a reference to a Pizza instance. Now you can independently create new
Pizza types and new Restaurant types, and combine them dynamically at runtime.
This not only eliminates the class explosion but also respects the Single Responsibility and
Open-Closed Principles.
Applicability
You want to avoid a permanent binding between an abstraction and its implementation.
You want to vary both the abstraction and implementation independently.
Changes in the implementation should not affect client code.
You have a class explosion due to multiple dimensions of variation.
Structure
Abstraction: Restaurant
Refined Abstraction: ItalianRestaurant, AmericanRestaurant
Implementation: Pizza
Concrete Implementations: PepperoniPizza, VeggiePizza
Participants
Collaborations
Consequences
✅ Advantages:
⚠️Tradeoffs:
Implementation
// Implementor
public interface Pizza {
void prepare();
}
// Concrete Implementors
public class VeggiePizza implements Pizza {
public void prepare() {
[Link]("Preparing Veggie Pizza...");
}
}
// Abstraction
public abstract class Restaurant {
protected Pizza pizza;
// Refined Abstractions
public class ItalianRestaurant extends Restaurant {
public ItalianRestaurant(Pizza pizza) {
super(pizza);
}
@Override
public void deliver() {
[Link]("Italian style: ");
[Link]();
[Link]("Delivered with wood-fired flair!");
}
}
@Override
public void deliver() {
[Link]("American style: ");
[Link]();
[Link]("Delivered with extra cheese and soda combo!");
}
}
FLYWEIGHT
Intent
Imagine you’re working at Amazon, tasked with rebuilding the part of the UI that displays
millions of books. You create a Book class, where each instance contains attributes like title,
author, type, distributor, and some other data.
On your machine, everything renders perfectly. But when your manager runs the code, the
service crashes — his laptop lacks the RAM to handle millions of heavy Book objects.
After inspection, you discover that some book attributes (e.g., type, distributor, otherData)
are shared across many books. Every fantasy book has the same distributor, same metadata,
etc.
The Flyweight pattern reduces RAM usage by externalizing and sharing the invariant,
common state (called intrinsic state) of objects, while keeping only the unique part (extrinsic
state) within the main class.
In your case:
Move shared attributes (e.g., type, distributor, otherData) into a new class BookType
Make BookType immutable
Each Book holds a reference to a BookType, instead of copying the data
This dramatically cuts memory usage by allowing all books of the same type to reuse the same
BookType object.
Applicability
Structure
Flyweight: BookType
Context: Book
FlyweightFactory: BookTypeFactory
Client: Store or BookRenderer
Participants
Flyweight (BookType): Stores shared, immutable data. Used by many objects. Must be
memory-safe (no setters, immutable).
Context (Book): Contains unique data (e.g., title, ID) and a reference to a Flyweight.
Together, they form the complete object.
FlyweightFactory (BookTypeFactory): Caches and reuses Flyweight objects. Returns
existing ones if available, or creates new ones.
Client (Store): Assembles complete objects by combining extrinsic state with
Flyweights. Thinks it’s working with a regular Book.
Collaborations
✅ Advantages:
⚠️Tradeoffs:
Implementation
// Flyweight
public class BookType {
private final String type;
private final String distributor;
private final String otherData;
// Context
public class Book {
private String title;
private String author;
private BookType bookType;
// Flyweight Factory
public class BookTypeFactory {
private static final Map<String, BookType> bookTypeMap = new HashMap<>();
The final keyword enforces immutability and prevents modification or extension at various
levels:
Context Effect
This is particularly relevant in structural patterns like Flyweight, where shared objects must
remain immutable and consistent across contexts.
DECORATOR
Intent
Motivation
Suppose you're building a notification service for a food delivery app. Initially, the Notifier
class simply sends email notifications. It has a send(String message) method that gets the
user's email from a database and sends the message.
Later, customers request additional channels — WhatsApp, Facebook, even SMS. The first
instinct is to use inheritance, creating subclasses like FacebookNotifier, WhatsAppNotifier,
etc.
But problems arise:
Instead of creating every possible combination via subclassing, we apply the Decorator
pattern.
We:
java
CopyEdit
INotifier notifier = new FacebookDecorator(
new WhatsAppDecorator(
new Notifier()
)
);
[Link]("Your order has been delivered!");
Each decorator adds behavior (e.g., sending via Facebook or WhatsApp) before or after
delegating to the wrapped notifier. You can now stack behaviors without subclass explosion.
Applicability
Structure
Component: INotifier
ConcreteComponent: Notifier (email)
Decorator: BaseNotifierDecorator
ConcreteDecorators: FacebookDecorator, WhatsAppDecorator
Client: Notification Service or UI Module
Participants
Component (INotifier): Declares the interface for objects that can have responsibilities
added.
ConcreteComponent (Notifier): Defines the basic behavior (sending email).
Decorator (BaseNotifierDecorator): Maintains a reference to a Component and
implements the same interface.
ConcreteDecorators (FacebookDecorator, WhatsAppDecorator): Add responsibilities
either before or after delegating to the wrapped object.
Client: Composes decorators dynamically, unaware whether it’s working with the base
component or decorated versions.
Collaborations
Consequences
✅ Advantages:
⚠️Tradeoffs:
Implementation
// Component
public interface INotifier {
void send(String message);
}
// ConcreteComponent
public class Notifier implements INotifier {
public void send(String message) {
// send email logic
[Link]("Email: " + message);
}
}
// Decorator
public abstract class BaseNotifierDecorator implements INotifier {
protected INotifier wrappee;
// ConcreteDecorator
public class WhatsAppDecorator extends BaseNotifierDecorator {
public WhatsAppDecorator(INotifier notifier) {
super(notifier);
}
@Override
public void send(String message) {
[Link](message);
[Link]("WhatsApp: " + message);
}
}
@Override
public void send(String message) {
[Link](message);
[Link]("Facebook: " + message);
}
}
To support clean structure and reduce boilerplate in Java, the Lombok library provides
annotations that automatically generate commonly used code constructs such as getters, setters,
constructors, and toString() methods.
@Data
Generates:
@Getter
Generates getter methods for all fields. Can also be applied to individual fields.
@AllArgsConstructor
Generates a constructor with one parameter for each field in the class.
@RequiredArgsConstructor
These annotations promote the Single Responsibility Principle by removing repetitive method
declarations and enhancing readability.
Chain of Responsibility
Command
Interpreter
Iterator
Mediator
Memento
Observer
State
Strategy
Template Method
Visitor
Chain of Responsibility:
Avoid coupling the sender of a request to its receiver by giving more than one object a
chance to handle the request. Chain the receiving objects and pass the request along
the chain until one handles it.
Command:
Encapsulate a request as an object, allowing you to parameterize clients with different
requests, queue or log requests, and support undoable operations.
Interpreter:
Given a language, define a representation for its grammar along with an interpreter that
uses the representation to interpret sentences in the language.
Mediator:
Define an object that encapsulates how a set of objects interact. Mediator promotes
loose coupling by keeping objects from referring to each other explicitly, and it lets you
vary their interaction independently.
Memento:
Without violating encapsulation, capture and externalize an object's internal state so
that the object can be restored to this state later.
Observer:
Define a one-to-many dependency between objects so that when one object changes
state, all its dependents are notified and updated automatically.
State:
Allow an object to alter its behavior when its internal state changes. The object will
appear to change its class.
Strategy:
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.
Template Method:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
Template Method lets subclasses redefine certain steps without changing the algorithm's
structure.
Visitor:
Represent an operation to be performed on the elements of an object structure. Visitor
lets you define a new operation without changing the classes of the elements on which
it operates.
TEMPLATE METHOD
Intent
Define the skeleton of an algorithm in a base class, deferring some steps to subclasses.
Template Method lets subclasses redefine certain steps of an algorithm without changing its
overall structure.
Motivation
Suppose you are developing the loading screens for AAA video games such as World of
Warcraft and Diablo. The loading process in both cases involves several core steps:
While implementing these screens, you observe that the structure of the algorithm is identical
across games. However, some steps (such as object creation or media loading) require different
implementations depending on the game, whereas others (such as file cleanup) may remain the
same.
To avoid code duplication and enforce a consistent process structure, you apply the Template
Method Pattern.
You:
Define a BaseGameLoader class that declares all the loading steps as individual methods.
Implement a load() method in BaseGameLoader — the template method — that calls
the steps in a fixed order.
Provide default implementations for steps that are common across games (e.g.,
cleanTempFiles()).
Let subclasses like WorldOfWarcraftLoader and DiabloLoader override only the steps
they need to customize.
This pattern enables you to enforce a consistent loading sequence across games while allowing
flexibility in individual steps.
Applicability
Several classes share the same algorithm structure but differ in specific steps.
You want to avoid code duplication by moving shared behavior to a base class.
You want to enforce a standard workflow while allowing customization of certain
operations.
Structure
AbstractClass: BaseGameLoader
Template Method: load()
Primitive Operations: loadLocalData(), createObjects(),
downloadAdditionalFiles(), cleanTempFiles(), initializeProfiles()
AbstractClass (BaseGameLoader):
o Defines the template method load() which calls a fixed sequence of step
methods.
o Declares primitive operations (steps) that may be abstract or have default
implementations.
ConcreteClass (WorldOfWarcraftLoader, DiabloLoader):
o Implements abstract steps.
o Optionally overrides default steps for customization.
o May not override the template method.
Collaborations
Consequences
✅ Advantages:
⚠️Drawbacks:
Implementation
// AbstractClass
public abstract class BaseGameLoader {
// Template Method
public final void load() {
loadLocalData();
createObjects();
downloadAdditionalFiles();
cleanTempFiles();
initializeProfiles();
}
// ConcreteClass
public class WorldOfWarcraftLoader extends BaseGameLoader {
protected void loadLocalData() {
[Link]("Loading WoW assets...");
}
ITERATOR
Intent
Provide a way to access the elements of an aggregate object sequentially without exposing its
underlying representation.
Motivation
Suppose you are navigating a complex data structure, such as a graph that may internally be
implemented as a binary search tree, a red-black tree, or any other variant. You want to traverse
the graph — for example, using depth-first or breadth-first search — but you do not want to
expose or depend on its underlying structure.
This scenario is similar to visiting Paris with a local guide: You are unfamiliar with the area (the
internal graph structure), but the guide (iterator) will show you all the sites (graph nodes) in an
order that matches your interest (e.g., DFS, BFS) without you needing to know the layout.
The Iterator Pattern encapsulates this traversal behavior inside a separate object, allowing
clients to iterate over a collection independently and safely. Each iterator maintains its own
internal state and traversal logic, supporting multiple simultaneous traversals of the same
structure.
In your application, you might create DepthFirstIterator and BreadthFirstIterator
classes, each implementing a shared Iterator interface. The client code remains unchanged
regardless of which algorithm is used or how the graph is structured.
Applicability
Collaborations
Consequences
✅ Advantages:
⚠️Disadvantages:
// Concrete Iterator
public class DepthFirstIterator implements GraphIterator {
private Stack<Node> stack = new Stack<>();
Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.
Motivation
Soon, new requirements arise: the app must support PayPal, and potentially other methods like
Apple Pay or cash on delivery. You modify the method to use if or switch statements,
handling each case within a growing block of logic.
This approach quickly violates the Open-Closed Principle: every new payment method requires
reopening and modifying working code. It also breaks the Single Responsibility Principle, as
the PaymentService class now handles multiple payment strategies.
Extract each payment method (e.g., credit card, PayPal) into its own class.
Define a common PaymentStrategy interface that all strategies implement.
The PaymentService holds a reference to the strategy and uses it for all payment-related
behavior.
Applicability
You have multiple algorithms or behaviors for a specific task (e.g., payment).
You want to encapsulate each behavior in its own class.
You need to switch behavior dynamically at runtime.
You aim to adhere to the Single Responsibility and Open-Closed Principles.
Structure
Strategy Interface: PaymentStrategy
Concrete Strategies: PaymentByCreditCard, PaymentByPayPal
Context: PaymentService
Client: Checkout Screen or Order Processor
Participants
Strategy (PaymentStrategy):
o Declares the interface common to all supported algorithms (e.g.,
collectPaymentDetails(), validate(), pay()).
ConcreteStrategy (PaymentByCreditCard, PaymentByPayPal):
o Implements specific payment logic.
Context (PaymentService):
o Maintains a reference to a PaymentStrategy.
o Delegates the payment process to the strategy object.
Client:
o Creates the appropriate strategy and injects it into the context.
Collaborations
Consequences
✅ Advantages:
⚠️Disadvantages:
Implementation
// Strategy Interface
public interface PaymentStrategy {
void collectPaymentDetails();
boolean validatePaymentDetails();
void pay(double amount);
}
// Concrete Strategy
public class PaymentByCreditCard implements PaymentStrategy {
public void collectPaymentDetails() {
[Link]("Collecting credit card info...");
}
// Context
public class PaymentService {
private PaymentStrategy strategy;
MEDIATOR
Intent
Motivation
A Login button
Two Text fields for username and password
Two Labels to describe each field
Without the mediator pattern, the Login button is tightly coupled to the Text fields,
directly fetching and validating their data. This setup makes it:
To avoid this mess, we introduce a Mediator object (e.g., Dialog) to manage the interactions.
Decoupled UI components
Reusability across different contexts
Easier control over interaction logic
Applicability
A set of objects communicate in complex ways, but you want to avoid tight coupling.
You want to reuse individual components in different contexts.
Behavior is distributed among several objects, making it difficult to change or reuse.
Structure
Mediator Interface: DialogInterface
Concrete Mediator: LoginDialog
Colleagues (Components): LoginButton, UsernameField, PasswordField, Labels
Participants
Mediator (DialogInterface):
o Declares a common interface for communication among components.
ConcreteMediator (LoginDialog):
o Coordinates communication between UI elements.
o Encapsulates interaction logic.
Colleague Components (LoginButton, TextField, Label):
o Reference the mediator, notify it of events.
o Do not know or reference each other directly.
Collaborations
Consequences
✅ Advantages:
Reduces coupling between components.
Makes individual components reusable and easier to test.
Centralizes complex communication logic into a single class.
Promotes Single Responsibility and Open-Closed principles.
⚠️Disadvantages:
The Mediator can become overly complex or a God object if it manages too many
interactions.
Components depend on the mediator API.
Implementation
// Mediator Interface
public interface Dialog {
void notify(Component sender, String event);
}
// Abstract Component
public abstract class Component {
protected Dialog dialog;
// Concrete Components
public class LoginButton extends Component {
public LoginButton(Dialog dialog) {
super(dialog);
}
// Concrete Mediator
public class LoginDialog implements Dialog {
private TextField usernameField;
private TextField passwordField;
private LoginButton loginButton;
public LoginDialog() {
[Link] = new TextField(this);
[Link] = new TextField(this);
[Link] = new LoginButton(this);
}
OBSERVER
Intent
Define a one-to-many dependency between objects so that when one object changes state, all its
dependents are notified and updated automatically.
Also Known As
Publish-Subscribe, Listener
Motivation
Imagine you own a store. One day a customer asks about a specific item that’s currently out of
stock. You tell them to check back later.
Here, both you and the customer have two inefficient options:
1. The customer keeps visiting the store daily — wasting effort if the item is still
unavailable.
2. The store spams all customers with updates — irritating customers not interested in that
item.
A better approach is a subscription mechanism where customers can choose to be notified only
about specific events (e.g., new arrivals or sales). To design such a system, we use the Observer
Pattern.
This pattern introduces a Publisher (e.g., NotificationService) that manages a dynamic list
of Subscribers (e.g., EmailMessageListener, MobileAppListener). Each subscriber is
notified only when relevant events occur.
For example, when a new item arrives, the store's newItemPromotion() method invokes the
notification service, which alerts only the relevant subscribers (those interested in NEW_ITEM).
The pattern also allows extending this mechanism to push notifications, mobile alerts, and
event-specific subscriptions, all while following clean separation of concerns and open/closed
design principles.
Applicability
You need to notify multiple objects about events happening in another object.
You want to implement a publisher-subscriber model without tightly coupling classes.
You want to support dynamic subscription and unsubscription at runtime.
You want to extend the types of subscribers without changing the publisher logic.
Structure
Client: Store
Publisher: NotificationService
Subscriber Interface: EventListener
Concrete Subscribers: EmailMessageListener, MobileAppListener
Event: Enum { NEW_ITEM, SALE }
Participants
Collaborations
Consequences
✅ Pros:
⚠️Cons:
Subscribers are not guaranteed notification order.
Debugging may become complex if many listeners are involved.
Potential for memory leaks if listeners aren’t unsubscribed properly.
Implementation
// Subscriber Interface
public interface EventListener {
void update(Event eventType, String data);
}
// Concrete Subscriber
public class EmailMessageListener implements EventListener {
private String email;
// Publisher
public class NotificationService {
private Map<Event, List<EventListener>> listeners = new HashMap<>();