Generated: Tue Feb 3 17:15:34 UTC 2026
A curated, production-oriented collection of classic software
design patterns implemented in Java.
Each pattern is expressed as small, focused, runnable
code intended to demonstrate intent,
structure, and trade-offs β not just theory.
This repository is designed as a long-term reference for backend engineers, architects, and senior developers.
mvn clean testOpen the project as a Maven project using pom.xml in IntelliJ IDEA or Eclipse.
| Pattern | Folder | Typical Use Case |
|---|---|---|
| Abstract Factory | abstract-factory/ |
Families of related objects |
| Builder | builder/ |
Complex object construction |
| Factory Method | factory-method/ |
Delegating object creation |
| Prototype | prototype/ |
Cloning costly objects |
| Singleton | singleton/ |
Controlled global access |
| Pattern | Folder | Typical Use Case |
|---|---|---|
| Adapter | adapter/ |
Interface compatibility |
| Bridge | bridge/ |
Decoupling abstraction & impl |
| Composite | composite/ |
Tree structures |
| Decorator | decorator/ |
Runtime behavior extension |
| Facade | facade/ |
Simplified subsystem access |
| Flyweight | flyweight/ |
Memory optimization |
| Proxy | proxy/ |
Controlled access |
| Pattern | Folder | Typical Use Case |
|---|---|---|
| Chain of Responsibility | chain/ |
Request pipelines |
| Command | command/ |
Action encapsulation |
| Interpreter | interpreter/ |
DSL-like grammars |
| Iterator | iterator/ |
Collection traversal |
| Mediator | mediator/ |
Interaction centralization |
| Memento | memento/ |
State snapshots |
| Observer | observer/ |
Event-driven updates |
| State | state/ |
Workflow/state machines |
| Strategy | strategy/ |
Algorithm selection |
| Template Method | template-method/ |
Algorithm skeletons |
| Visitor | visitor/ |
Operations on object graphs |
| MVP | model-view-presenter/ |
Presentation separation |
state-machine/pipes-and-filters/service-locator/double-checked-locking/method-object/For each pattern folder, focus on:
Patterns are tools, not goals. Overuse is a design smell.
Contributions are welcome and encouraged!
see CONTRIBUTING.md for details.
The Abstract Factory pattern - Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
Problem Solved:
Use When:
| Role | Responsibility |
|---|---|
| AbstractFactory | Interface for creating products |
| ConcreteFactory | Implements product creation |
| AbstractProduct | Product interface |
| ConcreteProduct | Concrete implementations |
// Abstract Factory
public interface KingdomFactory {
Castle createCastle();
King createKing();
Army createArmy();
}
// Concrete Factories
public class ElfKingdomFactory implements KingdomFactory {
public Castle createCastle() { return new ElfCastle(); }
public King createKing() { return new ElfKing(); }
public Army createArmy() { return new ElfArmy(); }
}
public class DwarfKingdomFactory implements KingdomFactory {
public Castle createCastle() { return new DwarfCastle(); }
public King createKing() { return new DwarfKing(); }
public Army createArmy() { return new DwarfArmy(); }
}
// Usage
KingdomFactory factory = new ElfKingdomFactory();
Castle castle = factory.createCastle();Reasoning: Client code doesnβt know concrete classes. Entire family changes by swapping factory. Ensures type-safe product combinations.
classDiagram
class Client
class AbstractFactory {
<<interface>>
+createProductA()
+createProductB()
}
class ConcreteFactory1
class ConcreteFactory2
class AbstractProductA {
<<interface>>
}
class AbstractProductB {
<<interface>>
}
class ProductA1
class ProductA2
class ProductB1
class ProductB2
AbstractFactory <|-- ConcreteFactory1
AbstractFactory <|-- ConcreteFactory2
AbstractProductA <|-- ProductA1
AbstractProductA <|-- ProductA2
AbstractProductB <|-- ProductB1
AbstractProductB <|-- ProductB2
ConcreteFactory1 --> ProductA1
ConcreteFactory1 --> ProductB1
ConcreteFactory2 --> ProductA2
ConcreteFactory2 --> ProductB2
AbstractFactory --> AbstractProductA
AbstractFactory --> AbstractProductB
Client --> AbstractFactory
Client --> AbstractProductA
Client --> AbstractProductB
sequenceDiagram
actor Client
Client->>AbstractFactory: createProductA()
AbstractFactory-->>Client: AbstractProductA
Client->>AbstractFactory: createProductB()
AbstractFactory-->>Client: AbstractProductB
Client->>AbstractProductA: use()
Client->>AbstractProductB: use()
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Factory returns null | use Optional | Use appropriate implementation |
| Leaky abstractions | stick to interfaces | Use appropriate implementation |
| Single Responsibility violation | separate concerns | Use appropriate implementation |
| Alternative | When to Use |
|---|---|
| Factory Method | When⦠|
| Builder | When⦠|
| Prototype | When⦠|
This folder contains the Adapter design pattern implementation.
The adapter pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Target {
<<interface>>
+request()
}
class Adapter {
+request()
}
class Adaptee {
+specificRequest()
}
Client --> Target
Target <|-- Adapter
Adapter --> Adaptee
sequenceDiagram
actor Client
Client->>Target: request()
Target->>Adapter: request()
Adapter->>Adaptee: specificRequest()
Adaptee-->>Adapter: result
Adapter-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
This folder contains the Bridge design pattern implementation.
The bridge pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Abstraction {
-implementor: Implementor
+operation()
}
class RefinedAbstraction
class Implementor {
<<interface>>
+operationImpl()
}
class ConcreteImplementorA
class ConcreteImplementorB
Client --> Abstraction
Abstraction <|-- RefinedAbstraction
Implementor <|-- ConcreteImplementorA
Implementor <|-- ConcreteImplementorB
Abstraction --> Implementor
sequenceDiagram
actor Client
Client->>Abstraction: operation()
Abstraction->>Implementor: operationImpl()
Implementor-->>Abstraction: result
Abstraction-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
The Builder Pattern is a creational pattern that constructs complex objects step-by-step, separating the construction logic from the representation. It provides a fluent, readable interface for building objects with many optional parameters or complex initialization logic.
Problem Solved: - Creating objects with many parameters is error-prone and produces unreadable code - Objects have optional and mandatory parameters; telescoping constructors become unwieldy - Complex initialization logic should be separated from object representation - You need to construct immutable objects while maintaining readability
Use When: - Objects have 3+ optional parameters - You need to build immutable objects with validation - Construction logic is complex or multi-step - You want to improve code readability over constructor overloading
| Role | Responsibility |
|---|---|
| Product | The complex object being constructed (e.g., Hero) |
| Builder | Abstract interface defining construction steps |
| Concrete Builder | Implements construction steps, accumulates result |
| Director | (Optional) Orchestrates the builder to construct products in a specific order |
| Client | Uses builder to construct products |
Key Characteristics: - Fluent interface enabling method chaining - Separation of construction from representation - Step-by-step object assembly - Validation at build stage
// Product: Complex object to build
public class Hero {
private final String name;
private final Profession profession;
private final HairType hairType;
private final HairColor hairColor;
private final Armor armor;
private final Weapon weapon;
private Hero(HeroBuilder builder) {
this.name = builder.name;
this.profession = builder.profession;
this.hairType = builder.hairType;
this.hairColor = builder.hairColor;
this.armor = builder.armor;
this.weapon = builder.weapon;
}
// Builder: Inner class managing construction
public static class HeroBuilder {
private final String name;
private final Profession profession;
private HairType hairType;
private HairColor hairColor;
private Armor armor;
private Weapon weapon;
// Mandatory parameters in constructor
public HeroBuilder(Profession profession, String name) {
this.profession = profession;
this.name = name;
}
// Fluent methods for optional parameters
public HeroBuilder withHairType(HairType hairType) {
this.hairType = hairType;
return this;
}
public HeroBuilder withHairColor(HairColor hairColor) {
this.hairColor = hairColor;
return this;
}
public HeroBuilder withArmor(Armor armor) {
this.armor = armor;
return this;
}
public HeroBuilder withWeapon(Weapon weapon) {
this.weapon = weapon;
return this;
}
// Build: Final step returning the constructed product
public Hero build() {
return new Hero(this);
}
}
}
// Usage: Clean, readable, chainable construction
Hero mage = new Hero.HeroBuilder(Profession.MAGE, "Merlin")
.withHairColor(HairColor.BLACK)
.withWeapon(Weapon.STAFF)
.build();Reasoning: - Separates object construction from its representation - Fluent interface improves readability dramatically - Mandatory parameters enforced in constructor - Optional parameters via fluent methods - Immutability achieved through private constructor
classDiagram
class Client
class Director {
-builder: Builder
+construct()
}
class Builder {
<<interface>>
+buildPartA()
+buildPartB()
+getResult()
}
class ConcreteBuilder
class Product
Client --> Director
Director --> Builder
Builder <|-- ConcreteBuilder
ConcreteBuilder --> Product
sequenceDiagram
actor Client
Client->>Director: construct()
Director->>Builder: buildPartA()
Director->>Builder: buildPartB()
Builder-->>Director: done
Client->>Builder: getResult()
Builder-->>Client: Product
| Principle | How Applied |
|---|---|
| Single Responsibility | Builder manages construction; Product manages data |
| Separation of Concerns | Object representation separated from construction logic |
| Composition over Inheritance | Builder uses composition to accumulate object state |
| Fluent Interface | Method chaining improves readability |
| Immutability | Constructor is private; object state set via builder |
| Scenario | Why Avoid |
|---|---|
| Simple Objects | Objects with 1-2 parameters; use direct constructors |
| Mutable Objects | Builder is designed for immutability; doesnβt benefit mutable objects |
| Performance Critical | Memory overhead of builder may matter in high-frequency scenarios |
| All Parameters Optional | If no mandatory parameters, builder doesnβt enforce constraints |
| Frequent Modification | If object needs modification after creation, use setters instead |
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Shared Builder Across Threads | Race conditions when building concurrent products | Create new builder per thread; builders not thread-safe |
| Builder Without Validation | Invalid objects created if validation deferred | Implement validation in build() method |
| All-Optional Builder | No constraints on required parameters | Enforce mandatory params in builder constructor |
| Complex Directors | Director hides builder complexity instead of simplifying it | Director should orchestrate, not complicate |
| Mutable Product After Build | Product state modified after construction defeats builder purpose | Keep product immutable; add new builder if changes needed |
| Builder with Setters | Contradicts immutability goal | Use only fluent builder methods, no setters on product |
// StringBuilder is a builder for immutable String
String text = new StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.toString();// Guava uses builder for complex immutable collections
ImmutableList<String> list = ImmutableList.builder()
.add("item1")
.add("item2")
.build();// Lombok annotation auto-generates builder
@Builder
public class User {
private String name;
private String email;
private int age;
}
// Usage
User user = User.builder()
.name("Alice")
.email("alice@example.com")
.age(30)
.build();// OkHttp Request builder
Request request = new Request.Builder()
.url("https://example.com")
.addHeader("User-Agent", "MyApp")
.post(body)
.build();// Configuration builder with fluent interface
Configuration config = new Configuration.Builder()
.setDatabaseUrl("jdbc:mysql://localhost")
.setMaxConnections(50)
.setReadTimeout(5000)
.build();| Alternative | When to Prefer |
|---|---|
| Telescoping Constructor | For simple objects with few parameters |
| JavaBeans with Setters | When mutability is acceptable; not ideal for immutability |
| Factory Method | For creating simple objects or choosing implementations |
| Abstract Factory | When building families of related objects |
| Fluent Configuration | Lightweight alternative to builder for configuration objects |
this for
fluent interface@Builder
annotation to auto-generate builder codeThe Chain of Responsibility pattern passes a request along a chain of handlers. Each handler decides either to process the request or pass it to the next handler in the chain.
Problem Solved: - You need to process a request by multiple handlers without knowing which handler will process it in advance - You want to decouple the sender of a request from its receiver - You want to allow adding/removing handlers dynamically
Use When: - Multiple objects may handle a request - You donβt know in advance which object should handle the request - You want to issue a request to multiple handlers without specifying the receiver explicitly
| Role | Responsibility |
|---|---|
| Handler | Defines interface for handling requests and linking handlers |
| ConcreteHandler | Handles requests or passes them to next handler |
| Client | Initiates request into the chain |
public abstract class RequestHandler {
protected RequestHandler nextHandler;
public void setNextHandler(RequestHandler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(Request request);
}
public class ConcreteHandler extends RequestHandler {
@Override
public void handleRequest(Request request) {
if (canHandle(request)) {
process(request);
} else if (nextHandler != null) {
nextHandler.handleRequest(request);
}
}
private boolean canHandle(Request request) {
return request.getType() == RequestType.TYPE_A;
}
private void process(Request request) {
System.out.println("Processing request: " + request);
}
}
// Usage
RequestHandler handler1 = new ConcreteHandler();
RequestHandler handler2 = new ConcreteHandler();
handler1.setNextHandler(handler2);
handler1.handleRequest(new Request(RequestType.TYPE_A));Reasoning: Decouples sender from receiver; handlers decide whether to handle or pass on request dynamically.
classDiagram
class Client
class Request
class Handler {
<<abstract>>
-next: Handler
+setNext(h: Handler)
+handle(r: Request)
}
class ConcreteHandlerA
class ConcreteHandlerB
Client --> Handler
Handler <|-- ConcreteHandlerA
Handler <|-- ConcreteHandlerB
Handler --> Handler
sequenceDiagram
actor Client
Client->>HandlerA: handle(request)
alt canHandle
HandlerA-->>Client: handled
else pass
HandlerA->>HandlerB: handle(request)
HandlerB-->>Client: handled
end
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Infinite Loops | Circular chain references | Prevent circular chains |
| Silent Failures | Request disappears without handling | Always handle or log |
| God Handler | Handler does too much | Split into multiple handlers |
The Command pattern encapsulates a request as an object, allowing parameterization of clients with different requests, queuing of requests, and logging of requests.
Problem Solved: - Encapsulate a request as an object - Decouple the object that invokes an operation from the one that performs it - Allow undo/redo functionality - Queue commands for deferred execution
| Role | Responsibility |
|---|---|
| Command | Declares interface for executing operation |
| ConcreteCommand | Binds receiver to action |
| Receiver | Knows how to perform actual work |
| Invoker | Asks command to carry out request |
| Client | Creates ConcreteCommand |
public interface Command {
void execute();
void undo();
}
public class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
// Invoker
public class RemoteControl {
private List<Command> commands = new ArrayList<>();
public void pressButton(Command command) {
command.execute();
commands.add(command);
}
public void undo() {
if (!commands.isEmpty()) {
commands.remove(commands.size() - 1).undo();
}
}
}
// Usage
RemoteControl remote = new RemoteControl();
remote.pressButton(new LightOnCommand(light));
remote.undo();Reasoning: Requests encapsulated as objects enabling queuing, undoing, and macro commands.
classDiagram
class Client
class Invoker {
-command: Command
+setCommand(c: Command)
+invoke()
}
class Command {
<<interface>>
+execute()
}
class ConcreteCommand
class Receiver {
+action()
}
Client --> Invoker
Invoker --> Command
Command <|-- ConcreteCommand
ConcreteCommand --> Receiver
sequenceDiagram
actor Client
Client->>Invoker: setCommand(cmd)
Client->>Invoker: invoke()
Invoker->>Command: execute()
Command->>Receiver: action()
This folder contains the Composite design pattern implementation.
The composite pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Component {
<<interface>>
+operation()
}
class Leaf
class Composite {
-children: List<Component>
+add(c: Component)
+operation()
}
Client --> Component
Component <|-- Leaf
Component <|-- Composite
Composite --> Component
sequenceDiagram
actor Client
Client->>Composite: operation()
loop children
Composite->>Component: operation()
end
Composite-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
This folder contains the Decorator design pattern implementation.
The decorator pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Component {
<<interface>>
+operation()
}
class ConcreteComponent
class Decorator {
-component: Component
+operation()
}
class ConcreteDecorator
Client --> Component
Component <|-- ConcreteComponent
Component <|-- Decorator
Decorator <|-- ConcreteDecorator
Decorator --> Component
sequenceDiagram
actor Client
Client->>Decorator: operation()
Decorator->>Component: operation()
Component-->>Decorator: result
Decorator-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
The Double-Checked Locking pattern is a thread-safe lazy initialization technique that minimizes synchronization overhead.
Problem Solved: - Thread-safe lazy initialization - Minimize synchronization overhead - Defer object creation until needed - Efficient repeated access
public class DoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if (instance == null) {
synchronized(DoubleCheckedLocking.class) {
if (instance == null) {
instance = new Instance();
}
}
}
return instance;
}
}classDiagram
class Client
class Singleton {
-static instance: Singleton
+getInstance(): Singleton
}
Client --> Singleton
sequenceDiagram
actor Client
Client->>Singleton: getInstance()
alt instance == null
Client->>Singleton: synchronize
Singleton->>Singleton: create instance
end
Singleton-->>Client: instance
This folder contains the Facade design pattern implementation.
The facade pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Facade {
+operation()
}
class SubsystemA
class SubsystemB
class SubsystemC
Client --> Facade
Facade --> SubsystemA
Facade --> SubsystemB
Facade --> SubsystemC
sequenceDiagram
actor Client
Client->>Facade: operation()
Facade->>SubsystemA: doA()
Facade->>SubsystemB: doB()
Facade->>SubsystemC: doC()
Facade-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
The Factory Method Pattern defines an interface for creating an object, letting subclasses decide which class to instantiate.
Problem Solved: - You need to create objects but donβt know the concrete class until runtime - You want subclasses to specify the objects they create - Object creation logic should be separate from usage
Use When: - A class cannot anticipate the type of objects it needs to create - You want subclasses to specify the objects they create - Object creation logic should be separate from business logic
| Role | Responsibility |
|---|---|
| Creator | Declares factory method; may provide default implementation |
| ConcreteCreator | Overrides factory method to create specific product |
| Product | Declares interface for objects |
| ConcreteProduct | Implements product interface |
// Product Interface
public interface Transport {
void deliver(String cargo);
}
// Concrete Products
public class Truck implements Transport {
@Override
public void deliver(String cargo) {
System.out.println("Truck delivering: " + cargo);
}
}
// Creator (Abstract)
public abstract class Logistics {
abstract Transport createTransport();
public void planDelivery(String cargo) {
Transport transport = createTransport();
transport.deliver(cargo);
}
}
// Concrete Creator
public class RoadLogistics extends Logistics {
@Override
Transport createTransport() {
return new Truck();
}
}
// Usage
Logistics logistics = new RoadLogistics();
logistics.planDelivery("Package");Reasoning: Clients depend on abstractions, not concrete classes. Subclasses decide what product to create. Promotes loose coupling and flexibility.
classDiagram
class Client
class Creator {
<<abstract>>
+factoryMethod(): Product
+anOperation()
}
class ConcreteCreator
class Product {
<<interface>>
+use()
}
class ConcreteProduct
Client --> Creator
Creator <|-- ConcreteCreator
Product <|-- ConcreteProduct
Creator --> Product
sequenceDiagram
actor Client
Client->>Creator: anOperation()
Creator->>Creator: factoryMethod()
Creator->>Product: use()
Product-->>Creator: result
Creator-->>Client: result
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Over-Abstracting | Creating factory for every object | Use only when creation logic is complex |
| Leaky Abstractions | Creator exposes product implementation | Keep abstractions strict |
| Mixing Responsibilities | Creator also validates/processes | Separate concerns |
List.of(),
Set.of()LoggerFactory.getLogger()DriverManager.getConnection()DocumentBuilderFactory.newInstance()| Alternative | When to Use |
|---|---|
| Abstract Factory | When managing families of related products |
| Builder | When object construction is complex |
| Prototype | When cloning is more efficient |
The Filter pattern (or Criteria pattern) provides a way to filter collections of objects using different criteria in a flexible and reusable manner.
Problem Solved: - Filter collections by different criteria - Combine multiple filter criteria - Avoid multiple if-else statements - Enable reusable filter components
| Role | Responsibility |
|---|---|
| Criteria | Defines filtering interface |
| ConcreteCriteria | Implements specific filter logic |
| Filter | Applies criteria to collection |
classDiagram
class Client
class Criteria {
<<interface>>
+meetCriteria(items)
}
class ConcreteCriteriaA
class ConcreteCriteriaB
class AndCriteria
class OrCriteria
Client --> Criteria
Criteria <|-- ConcreteCriteriaA
Criteria <|-- ConcreteCriteriaB
Criteria <|-- AndCriteria
Criteria <|-- OrCriteria
AndCriteria --> Criteria
OrCriteria --> Criteria
sequenceDiagram
actor Client
Client->>Criteria: meetCriteria(items)
Criteria-->>Client: filteredItems
This folder contains the Flyweight design pattern implementation.
The flyweight pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Flyweight {
<<interface>>
+operation(extrinsic)
}
class ConcreteFlyweight
class FlyweightFactory {
-pool: Map
+getFlyweight(key)
}
Client --> FlyweightFactory
Flyweight <|-- ConcreteFlyweight
FlyweightFactory --> Flyweight
sequenceDiagram
actor Client
Client->>FlyweightFactory: getFlyweight(key)
FlyweightFactory-->>Client: Flyweight
Client->>Flyweight: operation(extrinsicState)
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
The Interpreter pattern defines a grammatical representation for a language and an interpreter to interpret sentences in that language.
Problem Solved: - Define grammar for domain-specific language - Parse and execute expressions - Build abstract syntax trees - Evaluate language sentences
| Role | Responsibility |
|---|---|
| AbstractExpression | Defines interpret interface |
| TerminalExpression | Implements primitive expressions |
| NonTerminalExpression | Implements composite expressions |
| Context | Global information for interpreter |
classDiagram
class Client
class Context
class AbstractExpression {
<<interface>>
+interpret(ctx: Context)
}
class TerminalExpression
class NonterminalExpression {
-left: AbstractExpression
-right: AbstractExpression
}
Client --> AbstractExpression
AbstractExpression <|-- TerminalExpression
AbstractExpression <|-- NonterminalExpression
AbstractExpression --> Context
NonterminalExpression --> AbstractExpression
sequenceDiagram
actor Client
Client->>AbstractExpression: interpret(context)
AbstractExpression->>AbstractExpression: interpret(child)
AbstractExpression-->>Client: result
The Iterator pattern provides a way to access elements of a collection sequentially without exposing its underlying representation.
Problem Solved: - Access collection elements without exposing structure - Support multiple simultaneous traversals - Provide uniform interface for different collections - Hide collection implementation details
| Role | Responsibility |
|---|---|
| Iterator | Defines interface for traversal |
| ConcreteIterator | Implements traversal logic |
| Collection | Defines interface for creating iterator |
| ConcreteCollection | Returns ConcreteIterator |
Javaβs Iterator interface: - hasNext(): Check if more elements - next(): Get next element - remove(): Remove current element
classDiagram
class Client
class Iterator {
<<interface>>
+hasNext()
+next()
}
class ConcreteIterator
class Aggregate {
<<interface>>
+createIterator(): Iterator
}
class ConcreteAggregate
Client --> Aggregate
Aggregate <|-- ConcreteAggregate
Iterator <|-- ConcreteIterator
ConcreteAggregate --> ConcreteIterator
sequenceDiagram
actor Client
Client->>Aggregate: createIterator()
Aggregate-->>Client: Iterator
loop until done
Client->>Iterator: hasNext()
Client->>Iterator: next()
end
The Lazy Sequence pattern defers sequence element computation until theyβre actually accessed, enabling efficient processing of potentially infinite sequences.
Problem Solved: - Defer expensive computations - Handle potentially infinite sequences - Support streaming data processing - Reduce memory usage
| Role | Responsibility |
|---|---|
| LazySequence | Defers element computation |
| ElementGenerator | Generates individual elements |
| Client | Accesses elements on demand |
classDiagram
class Client
class LazySequence {
-cache: Map
-generator: ElementGenerator
+get(index)
}
class ElementGenerator {
<<interface>>
+compute(index)
}
Client --> LazySequence
LazySequence --> ElementGenerator
sequenceDiagram
actor Client
Client->>LazySequence: get(index)
alt cached
LazySequence-->>Client: element
else not cached
LazySequence->>ElementGenerator: compute(index)
ElementGenerator-->>LazySequence: element
LazySequence-->>Client: element
end
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly, and lets you vary their interaction independently.
Problem Solved: - Objects communicate in complex ways - Direct references create tight coupling - Communication logic scattered across classes - Adding new interaction types requires modifications
| Role | Responsibility |
|---|---|
| Mediator | Defines interface for communication |
| ConcreteMediator | Implements interaction logic |
| Colleague | Communicates only with mediator |
| - ConcreteColleague | Implements colleague behavior |
classDiagram
class Mediator {
<<interface>>
+notify(sender, event)
}
class ConcreteMediator
class Colleague {
-mediator: Mediator
+send(event)
}
class ColleagueA
class ColleagueB
Mediator <|-- ConcreteMediator
Colleague <|-- ColleagueA
Colleague <|-- ColleagueB
Colleague --> Mediator
ConcreteMediator --> ColleagueA
ConcreteMediator --> ColleagueB
sequenceDiagram
participant ColleagueA
participant Mediator
participant ColleagueB
ColleagueA->>Mediator: notify(A, event)
Mediator->>ColleagueB: receive(event)
The Memento pattern captures and externalizes an objectβs internal state without violating encapsulation, and allows the object to be restored to this state later.
Problem Solved: - Save and restore object state - Implement undo/redo functionality - Without exposing internal structure - Without violating encapsulation
| Role | Responsibility |
|---|---|
| Originator | Creates memento, uses memento to restore state |
| Memento | Stores state snapshot |
| Caretaker | Manages mementos |
classDiagram
class Client
class Originator {
+createMemento(): Memento
+restore(m: Memento)
}
class Memento
class Caretaker {
-history: List<Memento>
}
Client --> Originator
Client --> Caretaker
Originator --> Memento
Caretaker --> Memento
sequenceDiagram
actor Client
Client->>Originator: createMemento()
Originator-->>Client: Memento
Client->>Caretaker: store(memento)
Client->>Originator: restore(memento)
The Method Object pattern converts a method into an object, enabling flexible method parameter passing and invocation.
Problem Solved: - Complex methods with many parameters - Need to pass methods as parameters - Deferred method execution - Method state management
classDiagram
class Client
class MethodObject {
<<interface>>
+execute()
}
class ConcreteMethodObject
class Host {
+helper()
}
Client --> MethodObject
MethodObject <|-- ConcreteMethodObject
ConcreteMethodObject --> Host
sequenceDiagram
actor Client
Client->>MethodObject: execute()
MethodObject->>Host: helper()
The Model-View-Presenter (MVP) pattern separates presentation logic from business logic by introducing a presenter component that mediates between the view and model.
Problem Solved: - Separate UI from business logic - Make UI components testable - Enable code reuse across UI frameworks - Improve maintainability of UI code
| Role | Responsibility |
|---|---|
| Model | Business logic and data |
| View | Displays data, handles user interaction |
| Presenter | Coordinates Model and View |
classDiagram
class Model
class View {
<<interface>>
+render(data)
}
class Presenter {
-model: Model
-view: View
+onViewEvent()
}
class ConcreteView
View <|-- ConcreteView
Presenter --> Model
Presenter --> View
ConcreteView --> Presenter
sequenceDiagram
actor User
participant View
participant Presenter
participant Model
User->>View: interact()
View->>Presenter: event()
Presenter->>Model: update()
Model-->>Presenter: data
Presenter->>View: render(data)
The Null Object pattern provides an object as a surrogate for null references, avoiding null checks throughout the code.
Problem Solved: - Eliminate null reference checks - Provide default behavior for null case - Improve code clarity - Reduce NullPointerException risks
Instead of:
if (logger != null) {
logger.log("message");
}Use:
logger.log("message"); // Works with NullLoggerclassDiagram
class Client
class AbstractObject {
<<interface>>
+operation()
}
class RealObject
class NullObject
Client --> AbstractObject
AbstractObject <|-- RealObject
AbstractObject <|-- NullObject
sequenceDiagram
actor Client
Client->>AbstractObject: operation()
AbstractObject-->>Client: result
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically.
Problem Solved: - An objectβs state changes and other objects need to be notified - Notify multiple objects without tight coupling - Enable event-driven architecture
| Role | Responsibility |
|---|---|
| Subject | Knows observers, provides interface to attach/detach |
| Observer | Defines interface for update notification |
| ConcreteObserver | Stores reference to Subject, implements update |
| ConcreteSubject | Stores state, sends notifications on change |
public interface Observer {
void update(String message);
}
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserver implements Observer {
private String name;
@Override
public void update(String message) {
System.out.println(name + " received: " + message);
}
}
// Usage
Subject subject = new Subject();
Observer obs1 = new ConcreteObserver();
subject.attach(obs1);
subject.notifyObservers("Event occurred");Reasoning: Loose coupling between Subject and Observers; enables dynamic subscription model.
classDiagram
class Subject {
+attach(o: Observer)
+detach(o: Observer)
+notify()
}
class ConcreteSubject
class Observer {
<<interface>>
+update()
}
class ConcreteObserver
Subject <|-- ConcreteSubject
Observer <|-- ConcreteObserver
Subject --> Observer
sequenceDiagram
participant Observer
participant Subject
Observer->>Subject: attach()
Subject->>Subject: changeState()
Subject->>Observer: notify()
Observer->>Observer: update()
The Pipes and Filters pattern processes data through a series of independent, modular processing components (filters) connected by data flow channels (pipes).
Problem Solved: - Process complex workflows through independent stages - Enable modular, reusable components - Support different processing orders - Parallel and sequential processing
| Role | Responsibility |
|---|---|
| Filter | Independent processing component |
| Pipe | Data flow channel between filters |
| Source | Produces data |
| Sink | Consumes final result |
classDiagram
class Source
class Pipe
class Filter {
+process(data)
}
class Sink
Source --> Pipe
Pipe --> Filter
Filter --> Pipe
Pipe --> Sink
sequenceDiagram
participant Source
participant Pipe1
participant Filter
participant Pipe2
participant Sink
Source->>Pipe1: emit(data)
Pipe1->>Filter: data
Filter->>Pipe2: processed
Pipe2->>Sink: processed
This folder contains the Prototype design pattern implementation.
The prototype pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Prototype {
<<interface>>
+clone()
}
class ConcretePrototype
Client --> Prototype
Prototype <|-- ConcretePrototype
sequenceDiagram
actor Client
Client->>Prototype: clone()
Prototype-->>Client: copy
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
This folder contains the Proxy design pattern implementation.
The proxy pattern solves a specific structural or behavioral problem in software design by providing reusable solutions.
This pattern involves multiple roles working together to achieve separation of concerns and flexibility.
Please see the src/ directory for complete, executable
code examples demonstrating this pattern.
classDiagram
class Client
class Subject {
<<interface>>
+request()
}
class RealSubject
class Proxy {
-real: RealSubject
+request()
}
Client --> Subject
Subject <|-- RealSubject
Subject <|-- Proxy
Proxy --> RealSubject
sequenceDiagram
actor Client
Client->>Proxy: request()
Proxy->>RealSubject: request()
RealSubject-->>Proxy: result
Proxy-->>Client: result
| Anti-Pattern | Issue | Solution |
|---|---|---|
| Overuse | Using pattern unnecessarily | Apply YAGNI principle |
| Misapplication | Wrong context for pattern | Study pattern requirements |
| Complexity | Pattern makes code harder | Simplify or choose different pattern |
This pattern appears frequently in: - Enterprise applications - Framework and library design - System integration scenarios
For detailed implementation, see the source files in
src/.
The Servant pattern defines common functionality in a separate class (servant) that serves multiple classes, enabling code reuse without inheritance.
Problem Solved: - Provide common functionality to multiple unrelated classes - Avoid code duplication without inheritance - Reduce class hierarchy complexity - Enable flexible behavior sharing
| Role | Responsibility |
|---|---|
| Servant | Provides common functionality |
| Served Classes | Use servant functionality |
| Client | Initiates servant behavior |
classDiagram
class Client
class Servant {
+operation(served)
}
class ServedA
class ServedB
Client --> Servant
Servant --> ServedA
Servant --> ServedB
sequenceDiagram
actor Client
Client->>Servant: operation(served)
Servant->>ServedA: doWork()
Servant-->>Client: result
The Service Locator pattern centralizes logic for creating and accessing services, providing a single point for service instantiation and discovery.
Problem Solved: - Decouple service creation from usage - Provide single service access point - Enable service substitution - Centralize service configuration
| Role | Responsibility |
|---|---|
| ServiceLocator | Provides service access |
| Service | Defines service interface |
| ServiceImpl | Concrete service implementation |
Service Locator is often considered an anti-pattern. Dependency Injection is the preferred modern approach.
classDiagram
class Client
class ServiceLocator {
+getService(name)
}
class Service {
<<interface>>
+execute()
}
class ConcreteServiceA
class ConcreteServiceB
Client --> ServiceLocator
Service <|-- ConcreteServiceA
Service <|-- ConcreteServiceB
ServiceLocator --> Service
sequenceDiagram
actor Client
Client->>ServiceLocator: getService(name)
ServiceLocator-->>Client: Service
Client->>Service: execute()
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it. This is a creational pattern that restricts object instantiation to a single instance while providing a mechanism to access that instance universally.
Problem Solved: - You need to guarantee that only one instance of a class exists throughout the application lifecycle - Multiple instances would cause logical errors or resource conflicts (e.g., database connections, configuration managers, logging services) - You need a globally accessible point of access to this single instance without passing references through all layers
Use When: - You need a single, shared resource manager (connection pools, thread pools, caches) - You need a centralized configuration holder or registry - You need exactly one instance of a service to coordinate system-wide activities
| Role | Responsibility |
|---|---|
| Singleton | Ensures only one instance exists via private constructor; provides static method to access the single instance |
| Client | Accesses the singleton instance through the public static accessor method |
Key Characteristics: - Private constructor prevents
external instantiation - Static instance variable holds the single
object - Public static accessor method (getInstance())
returns the instance - Thread-safety considerations for multi-threaded
environments
This pattern has multiple implementations, each with different trade-offs:
public class EagerInitializedSingleton {
private static final EagerInitializedSingleton instance =
new EagerInitializedSingleton();
private EagerInitializedSingleton() { }
public static EagerInitializedSingleton getInstance() {
return instance;
}
}Reasoning: Instance created when class is loaded. Thread-safe by default but allocates memory immediately.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() { }
public static synchronized ThreadSafeSingleton getInstance() {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}Reasoning: Creates instance on first use. Synchronized method ensures thread-safety but causes synchronization overhead on every call.
public class ThreadSafeSingleton {
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() { }
public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}Reasoning: Checks instance twiceβonce outside lock, once inside. Reduces synchronization overhead while ensuring thread-safety.
public class BillPughSingleton {
private BillPughSingleton() { }
private static class SingletonHelper {
private static final BillPughSingleton instance =
new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.instance;
}
}Reasoning: Uses inner class to achieve lazy initialization. Thread-safe by design and efficient (no synchronization overhead).
classDiagram
class Client
class Singleton {
-static instance: Singleton
+getInstance(): Singleton
}
Client --> Singleton
sequenceDiagram
actor Client
Client->>Singleton: getInstance()
Singleton-->>Client: instance
| Principle | How Applied |
|---|---|
| Single Responsibility | Singleton class has one job: manage the single instance |
| Dependency Inversion | Clients depend on the abstraction (interface) rather than concrete class |
| Separation of Concerns | Instance management is separated from business logic |
| Composition over Inheritance | Rather than subclassing, dependency is injected or accessed via static method |
| Scenario | Why Avoid |
|---|---|
| Stateful Objects | Sharing state across all clients leads to unexpected side effects |
| Testable Systems | Global dependencies make unit testing extremely difficult |
| Multiple Instances Needed | If business logic later requires multiple instances, refactoring is painful |
| Distributed Systems | Each JVM/process gets its own instance; doesnβt guarantee global uniqueness |
| Simple Services | Dependency Injection (DI) containers provide same benefits with better flexibility |
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Mutable Singleton | Shared state gets corrupted by concurrent modifications | Make singleton immutable or use thread-safe collections |
| Singleton as Service Locator | Hides dependencies and couples code to singleton | Use constructor injection instead |
| Testing Without Mock/Spy | Tests affect each other via shared state | Provide reset method or use test-specific implementations |
| Singleton Inheritance | Each subclass becomes a different singleton | Use composition; wrap the actual singleton |
| Unnecessary Singletons | Over-use creates tight coupling | Prefer dependency injection via constructors |
| Non-Thread-Safe Singleton | Race conditions in multi-threaded apps | Use double-checked locking or eager initialization |
// Spring beans are singletons by default
@Service
public class UserService {
// Only one instance in Spring Container
}
// Access via Dependency Injection
@Component
public class UserController {
@Autowired
private UserService userService; // Singleton instance injected
}// Logger is typically a singleton
Logger logger = LoggerFactory.getLogger(MyClass.class);
// Returns same logger instance for same class name// ConnectionPool as singleton to manage all connections
public class ConnectionPool {
private static final ConnectionPool instance = new ConnectionPool();
public static ConnectionPool getInstance() {
return instance;
}
}// Singleton holding application configuration
public class AppConfig {
private static final AppConfig instance = new AppConfig();
public String getDatabaseUrl() { /* ... */ }
public int getMaxConnections() { /* ... */ }
}| Alternative | When to Prefer |
|---|---|
| Dependency Injection | When using a DI container (Spring, Guice); provides testability and flexibility |
| Static Class | For stateless utilities; simpler than singleton but canβt implement interfaces |
| Enum Singleton | For thread-safety and serialization guarantees in Java |
| Factory Pattern | When you need flexibility to create different implementations |
| Service Locator | When you need dynamic service registration (though often considered an anti-pattern) |
enum
provides the most robust singleton implementationThe State Machine pattern models complex workflows and state transitions using explicit state objects and transitions.
Problem Solved: - Model complex workflows with multiple states - Manage state transitions - Enforce valid state transitions - Handle state-dependent behavior
| Role | Responsibility |
|---|---|
| State | Defines state interface |
| ConcreteState | Implements state-specific behavior |
| StateMachine | Context managing states |
classDiagram
class StateMachine {
-current: State
+handle(event)
}
class State {
<<interface>>
+onEvent(event)
}
class ConcreteStateA
class ConcreteStateB
class Event
StateMachine --> State
State <|-- ConcreteStateA
State <|-- ConcreteStateB
StateMachine --> Event
sequenceDiagram
actor Client
Client->>StateMachine: handle(event)
StateMachine->>State: onEvent(event)
State-->>StateMachine: nextState
StateMachine-->>Client: transitioned
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Problem Solved: - Object behavior varies based on state - Large conditional statements checking state - State transitions need management
| Role | Responsibility |
|---|---|
| Context | Defines interface, delegates to State |
| State | Defines interface for state-specific behavior |
| ConcreteState | Implements state-specific behavior |
public interface State {
void handle(Context context);
}
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("State A handling");
context.setState(new ConcreteStateB());
}
}
// Usage
Context context = new Context(new ConcreteStateA());
context.request();
context.request();Reasoning: Encapsulates state-dependent behavior; enables state-specific transitions.
classDiagram
class Context {
-state: State
+request()
+setState(s: State)
}
class State {
<<interface>>
+handle(ctx: Context)
}
class ConcreteStateA
class ConcreteStateB
Context --> State
State <|-- ConcreteStateA
State <|-- ConcreteStateB
sequenceDiagram
actor Client
Client->>Context: request()
Context->>State: handle(context)
State-->>Context: maybe change state
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Problem Solved: - Multiple algorithms for a task - Algorithm selection varies - Avoid large conditional statements
| Role | Responsibility |
|---|---|
| Strategy | Defines interface for algorithm |
| ConcreteStrategy | Implements specific algorithm |
| Context | Uses Strategy |
public interface SortStrategy {
void sort(int[] array);
}
public class Context {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void executeSort(int[] array) {
strategy.sort(array);
}
}
public class QuickSort implements SortStrategy {
@Override
public void sort(int[] array) {
// QuickSort implementation
}
}
// Usage
Context context = new Context();
context.setStrategy(new QuickSort());
context.executeSort(array);Reasoning: Encapsulates algorithms; enables runtime selection; promotes composition.
classDiagram
class Context {
-strategy: Strategy
+setStrategy(s: Strategy)
+execute()
}
class Strategy {
<<interface>>
+algorithm()
}
class ConcreteStrategyA
class ConcreteStrategyB
Context --> Strategy
Strategy <|-- ConcreteStrategyA
Strategy <|-- ConcreteStrategyB
sequenceDiagram
actor Client
Client->>Context: setStrategy(strategy)
Client->>Context: execute()
Context->>Strategy: algorithm()
The Template Method pattern defines the skeleton of an algorithm in a base class, letting subclasses override specific steps.
Problem Solved: - Algorithm structure is fixed, but steps vary - Avoid code duplication in algorithm implementations - Control subclass overrides
| Role | Responsibility |
|---|---|
| AbstractClass | Defines template method and steps |
| ConcreteClass | Implements specific steps |
public abstract class DataProcessor {
public final void process() {
readData();
validateData();
processData();
writeData();
}
abstract void readData();
abstract void validateData();
abstract void processData();
abstract void writeData();
}
public class XMLProcessor extends DataProcessor {
@Override
void readData() { /* XML reading */ }
@Override
void validateData() { /* XML validation */ }
@Override
void processData() { /* XML processing */ }
@Override
void writeData() { /* XML writing */ }
}
// Usage
DataProcessor processor = new XMLProcessor();
processor.process();Reasoning: Defines algorithm structure; lets subclasses implement steps; prevents duplication.
classDiagram
class AbstractClass {
+templateMethod()
#primitiveOp1()
#primitiveOp2()
}
class ConcreteClass
AbstractClass <|-- ConcreteClass
sequenceDiagram
actor Client
Client->>AbstractClass: templateMethod()
AbstractClass->>AbstractClass: primitiveOp1()
AbstractClass->>AbstractClass: primitiveOp2()
AbstractClass-->>Client: done
The Visitor pattern represents an operation to be performed on elements of an object structure. It lets you define a new operation without changing the classes of the elements on which it operates.
Problem Solved: - Perform operations on complex object structures without changing their classes - Add new operations to class hierarchy without modification - Operations are related but scattered across classes
| Role | Responsibility |
|---|---|
| Visitor | Declares visit methods for each element type |
| ConcreteVisitor | Implements specific operations |
| Element | Accepts visitor |
| ConcreteElement | Implements accept method |
| ObjectStructure | Provides access to elements |
Visitor pattern is particularly useful when you have: - Complex object hierarchies requiring multiple operations - Operations that change frequently - Classes that shouldnβt be modified with new methods - Need to gather data from object structure
classDiagram
class Visitor {
<<interface>>
+visitConcreteElementA(e)
+visitConcreteElementB(e)
}
class ConcreteVisitor
class Element {
<<interface>>
+accept(v: Visitor)
}
class ConcreteElementA
class ConcreteElementB
Visitor <|-- ConcreteVisitor
Element <|-- ConcreteElementA
Element <|-- ConcreteElementB
Element --> Visitor
sequenceDiagram
actor Client
Client->>Element: accept(visitor)
Element->>Visitor: visit(element)