0% found this document useful (0 votes)
9 views40 pages

Singleton Design Pattern Overview

The document discusses various design patterns in software development, including Singleton, Adapter, Bridge, Flyweight, and Decorator patterns, detailing their intents, motivations, applicability, structures, participants, collaborations, and consequences. Each pattern addresses specific design challenges, such as ensuring a single instance of a class, adapting interfaces, decoupling abstractions from implementations, efficiently managing memory for numerous objects, and dynamically adding responsibilities to objects. The document also highlights the importance of immutability in certain patterns and provides implementation examples for better understanding.

Uploaded by

22-101003
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views40 pages

Singleton Design Pattern Overview

The document discusses various design patterns in software development, including Singleton, Adapter, Bridge, Flyweight, and Decorator patterns, detailing their intents, motivations, applicability, structures, participants, collaborations, and consequences. Each pattern addresses specific design challenges, such as ensuring a single instance of a class, adapting interfaces, decoupling abstractions from implementations, efficiently managing memory for numerous objects, and dynamically adding responsibilities to objects. The document also highlights the importance of immutability in certain patterns and provides implementation examples for better understanding.

Uploaded by

22-101003
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Singleton

Intent

Ensure that a class has only one instance and provide a global point of access to it.

Also Known As: Single Instance

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.

A Singleton ensures that such classes:

 Have only one instance


 Are accessed in a controlled, consistent manner

A real-world analogy is a president or government of a country. Regardless of who holds


office, there is only one “President of Country X.” All references point to this one entity, not
multiple competing instances.

Applicability

Use the Singleton pattern when:

 You need exactly one instance of a class.


 The class instance must be accessible from multiple parts of the application.
 You want to control instantiation and lifecycle (e.g., lazy loading).
 You need to manage global shared state.
Structure

Si
ngleton class has:

o A private static instance of itself.


o A private constructor.
o A public static getInstance() method to return the instance.

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

 Clients never instantiate Singleton directly.


 All access is funneled through the singleton access method, which guarantees a single
instance is created and shared.
Consequences

✅ Pros:

 Global access point for shared resources.


 Ensures controlled instantiation.
 Encourages resource reuse and low memory overhead.
 Helps coordinate state and configuration.

⚠️Cons:

 Acts like a global variable, introducing tight coupling.


 Hard to unit test due to hidden dependencies.
 Multithreading issues if not implemented carefully.
 Violates Single Responsibility Principle if it does too much.

Implementation
public class Singleton {
private static volatile Singleton instance; // Ensures visibility across
threads
private String data;

private Singleton() {
[Link] = data; // Initialize fields
}

public static Singleton getInstance() {


Singleton result = instance;
if (result == null) {
synchronized ([Link]) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}

public String getData() {


return data;
}
}

🔹 Uses double-checked locking


🔹 volatile ensures visibility and ordering of writes
🔹 Local caching improves access performance
STRUCTURAL PATTERNS – DISCUSSION
Definition

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

 Promote object composition over class inheritance, enabling behavior changes at


runtime.
 Maintain loose coupling between interacting components.
 Help manage the complexity of large software systems by defining clear relationships
and responsibilities among components.
 Support the Open-Closed Principle, allowing new behavior to be added without altering
existing class definitions.

Benefits

 Reduce class hierarchy complexity by replacing subclassing with composition.


 Enable developers to add functionality to objects without modifying their structure.
 Allow flexible runtime combinations of behaviors, which is not feasible with static
inheritance.
 Increase the reusability of existing code by supporting dynamic composition.
ADAPTER
Intent

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.

Also Known As: Wrapper

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.

To bridge this incompatibility, you create an Adapter class (FancyUIServiceAdapter) that:

 Implements the client interface IMultiRestoApp


 Internally wraps the FancyUIService object
 Transforms XML data to JSON before forwarding calls

This allows your original app to work unchanged, while seamlessly integrating the new third-
party library.

Applicability

Use the Adapter pattern when:

 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:

 Promotes reuse of existing libraries without modification.


 Improves flexibility via composition (object adapter).
 Honors Single Responsibility: adapting logic lives in its own class.
 Complies with Open-Closed Principle: new adapters can be added without touching
core code.

⚠️Cons:

 Adds a level of indirection.


 May require duplication of conversion logic (e.g., XML → JSON).

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;

public FancyUIServiceAdapter(FancyUIService service) {


[Link] = service;
}

@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.

Also Known As: Handle/Body

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.

A naive solution would be to create subclasses like AmericanPepperoniPizza,


ItalianVeggiePizza, and so on. But this quickly leads to combinatorial explosion: for every
new pizza type or new preparation style, you need new subclasses. Add Chicken Pizza? You
need ItalianChickenPizza, AmericanChickenPizza, and it never ends.

This happens because you're trying to extend a class in two independent dimensions:

 Pizza Type (Pepperoni, Veggie, Chicken)


 Preparation Style (American, Italian)

The Bridge pattern solves this by splitting the two dimensions into separate class
hierarchies:

 The Pizza hierarchy becomes the implementation


 The Restaurant hierarchy becomes the abstraction

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

Use the Bridge pattern when:

 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

 Abstraction (Restaurant): Defines the high-level control logic. It maintains a reference


to the Pizza implementation. Delegates the work to the implementation layer.
 Refined Abstraction (ItalianRestaurant, AmericanRestaurant): Specializations of
the abstraction with custom logic.
 Implementor (Pizza): Interface for pizza types; declares methods like prepare() or
bake().
 Concrete Implementors (PepperoniPizza, VeggiePizza): Concrete pizza logic —
toppings, prep steps, etc.
 Client: Instantiates both a Restaurant and a Pizza, links them, and calls
[Link]().

Collaborations

 The abstraction forwards requests to its implementor via the interface.


 Implementations handle the platform-specific or pizza-specific logic.
 The client works only with abstractions and links them with implementations.

Consequences

✅ Advantages:

 Avoids class explosion from combining multiple dimensions.


 Abstractions and implementations can be developed and evolved independently.
 Supports runtime switching of pizza types or restaurant styles.
 Encourages cleaner code separation (SRP, OCP).

⚠️Tradeoffs:

 Slightly more complexity: more classes and indirection.


 Needs careful interface design to keep abstraction and implementation aligned.

Implementation
// Implementor
public interface Pizza {
void prepare();
}

// Concrete Implementors
public class VeggiePizza implements Pizza {
public void prepare() {
[Link]("Preparing Veggie Pizza...");
}
}

public class PepperoniPizza implements Pizza {


public void prepare() {
[Link]("Preparing Pepperoni Pizza...");
}
}

// Abstraction
public abstract class Restaurant {
protected Pizza pizza;

public Restaurant(Pizza pizza) {


[Link] = pizza;
}

public abstract void deliver();


}

// 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!");
}
}

public class AmericanRestaurant extends Restaurant {


public AmericanRestaurant(Pizza pizza) {
super(pizza);
}

@Override
public void deliver() {
[Link]("American style: ");
[Link]();
[Link]("Delivered with extra cheese and soda combo!");
}
}

FLYWEIGHT
Intent

Use sharing to support large numbers of fine-grained objects efficiently.


Motivation

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.

This is where the Flyweight pattern helps.

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

Use the Flyweight pattern when:

 You have a huge number of similar objects (e.g., millions of books)


 Objects share a substantial amount of invariant (intrinsic) state
 RAM/memory is a critical concern
 Object creation or storage is expensive

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

 Clients don’t directly instantiate BookType; they go through BookTypeFactory.


 BookType is shared by many Book instances.
 Book instances manage their own unique data but rely on BookType for shared data.
Consequences

✅ Advantages:

 Massive RAM savings for applications with large datasets


 Separation of responsibilities: immutable vs. runtime state
 Fast object creation once flyweights are cached

⚠️Tradeoffs:

 Adds complexity: two classes per logical object (context + flyweight)


 Requires careful distinction between intrinsic and extrinsic state
 Not useful unless you truly have many similar objects

Implementation
// Flyweight
public class BookType {
private final String type;
private final String distributor;
private final String otherData;

public BookType(String type, String distributor, String otherData) {


[Link] = type;
[Link] = distributor;
[Link] = otherData;
}

// getters only — no setters (immutable)


}

// Context
public class Book {
private String title;
private String author;
private BookType bookType;

public Book(String title, String author, BookType bookType) {


[Link] = title;
[Link] = author;
[Link] = bookType;
}
}

// Flyweight Factory
public class BookTypeFactory {
private static final Map<String, BookType> bookTypeMap = new HashMap<>();

public static BookType getBookType(String type, String distributor, String


otherData) {
String key = type + distributor + otherData;
if (![Link](key)) {
[Link](key, new BookType(type, distributor, otherData));
}
return [Link](key);
}
}

The final Keyword in Java

The final keyword enforces immutability and prevents modification or extension at various
levels:

Context Effect

Creates a constant; cannot be


Final variable
reassigned

Prevents method from being


Final method
overridden

Final class Prevents class from being subclassed

This is particularly relevant in structural patterns like Flyweight, where shared objects must
remain immutable and consistent across contexts.

DECORATOR
Intent

Attach additional responsibilities to an object dynamically.


Decorators provide a flexible alternative to subclassing for extending functionality.

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:

 What if a customer wants both WhatsApp and Facebook notifications?


 What if you add SMS? Now you need FacebookWhatsAppNotifier,
FacebookSMSNotifier, and so on — this grows exponentially.

Instead of creating every possible combination via subclassing, we apply the Decorator
pattern.

We:

 Define an INotifier interface (Component).


 Keep Notifier as the ConcreteComponent (sends email).
 Create BaseNotifierDecorator, a wrapper that also implements INotifier.
 Create FacebookDecorator, WhatsAppDecorator, etc. as ConcreteDecorators,
extending BaseNotifierDecorator.

Now we can wrap objects in layers, dynamically:

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

Use the Decorator pattern when:

 You need to add responsibilities to individual objects without affecting others.


 Subclassing would lead to an explosion of combinations.
 You want to assign responsibilities dynamically and flexibly at runtime.
 You want to follow Open-Closed Principle: extend behavior without modifying original
code.

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

 Decorators delegate operations to the wrapped object (composition).


 Decorators can execute additional behavior before/after the delegate call.
 Multiple decorators can be stacked — outer decorators wrap inner ones.

Consequences

✅ Advantages:

 Avoids large inheritance trees for feature combinations.


 Adds behavior dynamically at runtime.
 Adheres to Single Responsibility (each decorator is self-contained).
 Adheres to Open-Closed Principle (new features via new decorators).

⚠️Tradeoffs:

 Can result in many small classes.


 Logic spread across layers can become hard to trace/debug.
 Ordering of decorators can affect behavior and must be managed carefully.

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;

public BaseNotifierDecorator(INotifier notifier) {


[Link] = notifier;
}

public void send(String message) {


[Link](message);
}
}

// ConcreteDecorator
public class WhatsAppDecorator extends BaseNotifierDecorator {
public WhatsAppDecorator(INotifier notifier) {
super(notifier);
}

@Override
public void send(String message) {
[Link](message);
[Link]("WhatsApp: " + message);
}
}

public class FacebookDecorator extends BaseNotifierDecorator {


public FacebookDecorator(INotifier notifier) {
super(notifier);
}

@Override
public void send(String message) {
[Link](message);
[Link]("Facebook: " + message);
}
}

Java Language Support – Lombok Annotations

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:

 Getters for all fields


 Setters for all non-final fields
 toString(), equals(), and hashCode()
 A constructor for all final fields

@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

Generates a constructor with parameters for:


 All final fields
 All fields annotated with @NonNull

These annotations promote the Single Responsibility Principle by removing repetitive method
declarations and enhancing readability.

Behavioral Patterns Discussion

 The main concern of behavioral patterns is the communication and assignment of


responsibilities between objects.
 Behavioral patterns describe patterns of classes/objects and communication among
them.
 Behavioral class patterns use inheritance to distribute behavior between classes.
 Behavioral object patterns use object composition rather than inheritance.
 Other behavioral object patterns are concerned with encapsulating behavior in an
object and delegating requests to it.

Types of Behavioral Patterns

 Behavioral Class Patterns:


o Template Method
o Interpreter
 Behavioral Object Patterns:
o Mediator
o Chain of Responsibility
o Observer
 Encapsulating Behavior Patterns:
o Strategy
o Command
o State
o Visitor
o Iterator

List of Behavioral Design Patterns

 Chain of Responsibility
 Command
 Interpreter
 Iterator
 Mediator
 Memento
 Observer
 State
 Strategy
 Template Method
 Visitor

Behavioral Class Patterns

 Use inheritance to distribute behavior between classes.


 Example: Template Method

Encapsulating Behavioral Object Patterns

 Encapsulate behavior in an object and delegate requests to it.


 Examples: Iterator, Strategy

Other Behavioral Patterns’ Intent

 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:

1. Loading data and media from disk into RAM


2. Creating large numbers of in-game objects
3. Downloading additional assets (e.g., sounds, textures, translations)
4. Cleaning temporary files used during loading
5. Initializing saved user profiles or generating new ones

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

Use the Template Method pattern when:

 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()

ConcreteClasses: WorldOfWarcraftLoader, DiabloLoader


Participants

 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

 The template method calls various steps in a specific order.


 Subclasses override only the necessary steps, preserving the structure of the algorithm.
 The client interacts with the template method, unaware of the subclass details.

Consequences

✅ Advantages:

 Promotes code reuse by placing invariant behavior in the base class.


 Enforces a fixed algorithm structure, improving maintainability and readability.
 Supports the Open-Closed Principle: new behaviors can be added via subclassing
without modifying existing code.
 Reduces code duplication across subclasses that share steps.

⚠️Drawbacks:

 Can lead to an increased number of subclasses if many variants are needed.


 Tight coupling between the base class and subclasses.
 Template method is not flexible once fixed in the abstract class — all subclasses must
follow the defined sequence.

Implementation
// AbstractClass
public abstract class BaseGameLoader {
// Template Method
public final void load() {
loadLocalData();
createObjects();
downloadAdditionalFiles();
cleanTempFiles();
initializeProfiles();
}

protected abstract void loadLocalData();


protected abstract void createObjects();
protected abstract void downloadAdditionalFiles();

// Shared method with default implementation


protected void cleanTempFiles() {
[Link]("Deleting temporary files...");
}

protected abstract void initializeProfiles();


}

// ConcreteClass
public class WorldOfWarcraftLoader extends BaseGameLoader {
protected void loadLocalData() {
[Link]("Loading WoW assets...");
}

protected void createObjects() {


[Link]("Creating WoW characters and environment...");
}

protected void downloadAdditionalFiles() {


[Link]("Downloading WoW expansions...");
}

protected void initializeProfiles() {


[Link]("Loading WoW player profile...");
}
}

public class DiabloLoader extends BaseGameLoader {


protected void loadLocalData() {
[Link]("Loading Diablo textures...");
}

protected void createObjects() {


[Link]("Creating Diablo monsters and maps...");
}

protected void downloadAdditionalFiles() {


[Link]("Downloading Diablo patches...");
}

protected void initializeProfiles() {


[Link]("Initializing Diablo player session...");
}
}

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

Use the Iterator pattern when:

 You need to traverse a collection without exposing its internal representation.


 You want to support multiple or custom traversal strategies (e.g., DFS, BFS).
 You need to allow multiple iterators to operate independently over the same structure.
 You aim to decouple traversal logic from the collection itself.

Structure (Lecture Example)


Iterator Interface: GraphIterator
Concrete Iterators: DepthFirstIterator, BreadthFirstIterator
Aggregate Interface: Graph
Concrete Aggregates: BinarySearchTree, RedBlackTree
Client: GraphProcessor or Application Logic
Participants

 Iterator (GraphIterator): Declares the interface for accessing elements sequentially.


Typically includes methods like next(), hasNext(), or getCurrent().
 ConcreteIterator (DepthFirstIterator, BreadthFirstIterator): Implements
traversal logic and maintains iteration state independently.
 Aggregate (Graph): Declares method(s) for creating an iterator object.
 ConcreteAggregate (BinarySearchTree, RedBlackTree): Implements the creation of
specific iterator instances.
 Client: Uses the iterator interface to traverse the structure, remaining decoupled from
both traversal logic and collection internals.

Collaborations

 A client obtains an iterator from a collection via a factory method such as


createIterator().
 The iterator provides sequential access to the collection elements.
 The collection and iterator interact through well-defined interfaces, supporting flexible
substitution and extensibility.

Consequences

✅ Advantages:

 Promotes encapsulation by hiding collection internals.


 Allows custom traversal logic to be isolated and reused.
 Supports multiple simultaneous iterations on the same collection.
 Adheres to Single Responsibility and Open-Closed Principles.

⚠️Disadvantages:

 May introduce additional objects and complexity.


 For large or dynamic collections, maintaining iterator state can require extra care (e.g.,
invalidation on modification).
Implementation
// Iterator Interface
public interface GraphIterator {
boolean hasNext();
Node next();
}

// Concrete Iterator
public class DepthFirstIterator implements GraphIterator {
private Stack<Node> stack = new Stack<>();

public DepthFirstIterator(Node start) {


[Link](start);
}

public boolean hasNext() {


return ![Link]();
}

public Node next() {


Node current = [Link]();
for (Node neighbor : [Link]()) {
[Link](neighbor);
}
return current;
}
}

Generic Type <T> in Java

 It tells Java that this interface works with a generic type T.


 The actual type T will be specified later when someone uses or implements the interface.

Why use generics?

 To write type-safe and reusable code


 You don’t have to cast objects manually
 Prevents runtime ClassCastException

Generic Type Examples:

 In each case, T becomes:


o String
o Integer
o User (custom class)
STRATEGY
Intent

Define a family of algorithms, encapsulate each one, and make them interchangeable.
Strategy lets the algorithm vary independently from clients that use it.

Motivation

Imagine you’re developing a food delivery application. The PaymentService class is


responsible for processing customer payments. Initially, the logic only supports credit card
payments. The method processOrder() collects card details, validates them, and performs the
transaction.

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.

To resolve this, you apply the Strategy Pattern:

 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.

Now you can:

 Add new payment methods without modifying the service.


 Swap strategies at runtime.
 Keep each strategy focused and reusable.

Applicability

Use the Strategy pattern when:

 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

 Context delegates behavior to the strategy via the interface.


 The client selects or switches the strategy based on application needs.
 Strategies can be reused or extended independently.

Consequences

✅ Advantages:

 Clean separation of algorithm logic from application logic.


 Promotes code reuse and testability.
 Makes behavior interchangeable at runtime.
 Supports Open-Closed Principle: extend without modifying existing code.

⚠️Disadvantages:

 Introduces additional objects.


 Client must be aware of strategy differences to choose appropriately.

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...");
}

public boolean validatePaymentDetails() {


[Link]("Validating credit card...");
return true;
}

public void pay(double amount) {


[Link]("Paid $" + amount + " using Credit Card");
}
}
public class PaymentByPayPal implements PaymentStrategy {
public void collectPaymentDetails() {
[Link]("Collecting PayPal credentials...");
}

public boolean validatePaymentDetails() {


[Link]("Validating PayPal account...");
return true;
}

public void pay(double amount) {


[Link]("Paid $" + amount + " using PayPal");
}
}

// Context
public class PaymentService {
private PaymentStrategy strategy;

public void setStrategy(PaymentStrategy strategy) {


[Link] = strategy;
}

public void processOrder(double amount) {


[Link]();
if ([Link]()) {
[Link](amount);
}
}
}

MEDIATOR
Intent

Define an object that encapsulates how a set of objects interact.


Mediator promotes loose coupling by preventing objects from referring to each other explicitly,
and lets you vary their interaction independently.

Motivation

Consider a login screen with several UI components:

 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:

 Hard to reuse the button elsewhere


 Difficult to modify interactions
 Prone to spaghetti-like dependencies as the UI grows

To avoid this mess, we introduce a Mediator object (e.g., Dialog) to manage the interactions.

Instead of components talking directly to each other, they:

1. Notify the mediator of events (e.g., button click).


2. The Mediator handles the coordination logic (e.g., validation).

This results in:

 Decoupled UI components
 Reusability across different contexts
 Easier control over interaction logic

Applicability

Use the Mediator pattern when:

 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

 Components interact only with the Mediator.


 The Mediator orchestrates interactions based on component events.
 Changing interaction logic is localized to the Mediator class.

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;

public Component(Dialog dialog) {


[Link] = dialog;
}
}

// Concrete Components
public class LoginButton extends Component {
public LoginButton(Dialog dialog) {
super(dialog);
}

public void click() {


[Link](this, "click");
}
}

public class TextField extends Component {


private String text = "";

public TextField(Dialog dialog) {


super(dialog);
}

public void setText(String text) {


[Link] = text;
[Link](this, "input");
}

public String getText() {


return text;
}
}

// 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);
}

public void notify(Component sender, String event) {


if (sender instanceof LoginButton && [Link]("click")) {
if ([Link]().equals("admin") &&
[Link]().equals("1234")) {
[Link]("Login successful!");
} else {
[Link]("Invalid credentials.");
}
}
}
}

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

Use the Observer pattern when:

 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

 Client (Store): Triggers events (e.g., newItemPromotion) that cause notifications.


 Publisher (NotificationService): Manages subscribers by event type, supports
subscribe/unsubscribe, and notifies relevant listeners.
 Subscriber Interface (EventListener): Defines update() method for all listeners.
 Concrete Subscribers (EmailMessageListener, MobileAppListener): Implement
update() and handle notification logic (e.g., send email or push notification).

Collaborations

 The store triggers an event like newItemPromotion() or saleAnnouncement().


 The publisher looks up subscribers for the event and calls their update() method.
 Each subscriber executes its own logic (send email, push alert, etc.).
 Subscribers can dynamically register/unregister for specific events.

Consequences

✅ Pros:

 Promotes loose coupling between publisher and subscribers.


 Supports dynamic subscriptions (add/remove listeners at runtime).
 Complies with the Open-Closed Principle (new listeners without modifying publisher).
 Encourages reusability and separation of concerns.

⚠️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;

public EmailMessageListener(String email) {


[Link] = email;
}

public void update(Event eventType, String data) {


// send email logic
}
}

// Publisher
public class NotificationService {
private Map<Event, List<EventListener>> listeners = new HashMap<>();

public void subscribe(Event eventType, EventListener listener) {


[Link](eventType, k -> new
ArrayList<>()).add(listener);
}

public void unsubscribe(Event eventType, EventListener listener) {


List<EventListener> users = [Link](eventType);
if (users != null) [Link](listener);
}

public void notify(Event eventType, String data) {


for (EventListener listener : [Link](eventType,
[Link]())) {
[Link](eventType, data);
}
}
}

You might also like