Design Patterns
Comprehensive Catalog of Software Design Patterns
Executive Summary: This report catalogs a wide range of software design patterns, covering classic GoF patterns (creational, structural, behavioral), as well as concurrency, architectural, enterprise, and common anti-patterns. Each pattern is described by its intent, problem addressed, participants, structure (with UML-style diagrams), use cases, pros/cons, and complexity. Example code snippets are provided in Java, with Kotlin alternatives. Patterns are organized by category, and a comparison table summarizes key attributes (intent, complexity, usage). Authoritative sources (GoF, Fowler, POSA, Microsoft, etc.) are cited throughout. Mermaid diagrams illustrate historical context.
Table of Contents
Creational Patterns
Singleton
Factory Method
Abstract Factory
Builder
Prototype
Structural Patterns
Adapter
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Behavioral Patterns
Chain of Responsibility
Command
Interpreter
Iterator
Mediator
Memento
Observer
State
Strategy
Template Method
Visitor
Concurrency Patterns
Architectural Patterns
Model-View-Controller (MVC)
Microservices
Client-Server
Layered Architecture
Enterprise Patterns
Active Record
Data Mapper
Service Layer
Unit of Work
Transaction Script vs Domain Model
Anti-Patterns
Comparison Table of Key Patterns
Creational Patterns
Singleton
Intent: Ensure a class has only one instance and provide a global access point to it【103†L1-L4】.
Problem: When exactly one object is needed to coordinate actions (e.g. logging, configuration, device driver). Without Singleton, you risk uncontrolled instantiation or need a global variable.
【29†embed_image】 Figure: UML class diagram of the Singleton pattern. Singleton restricts instantiation by making its constructor private and holding a static reference to the sole instance【103†L1-L4】. Participants: the Singleton class itself (which holds its one instance).
Structure & Participants: One class has a private static field (instance), a private constructor, and a public static method (e.g. getInstance()) that creates or returns the single instance. Client code calls Singleton.getInstance() to access it. No subclasses are typically involved.
Code Example (Java): Using lazy initialization with synchronization for thread safety:
Kotlin Alternative: Kotlin’s object declaration is a thread-safe Singleton by default:
Use Cases: Centralized managers (e.g. logging, configuration), caching, thread pools, window managers. Pros: Controlled single instance, easy global access. Cons: Introduces global state, hinders testing (hard to mock), can cause hidden dependencies. Synchronization adds overhead; lazy initialization can lead to subtle bugs (double-checked locking needed for efficient thread-safe instantiation). Complexity is low (few extra lines of code) but misuse leads to tight coupling.
Factory Method
Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate【16†L1-L4】. Allows a class to defer instantiation to subclasses. Problem: A framework or class cannot anticipate which class of object it should create, or wants its subclasses to define the objects to create. 【4†embed_image】 Figure: UML class diagram of the Factory Method pattern. Structure: Creator (abstract class or interface) declares a factory method. ConcreteCreator subclasses override this method to instantiate ConcreteProduct. Product (interface/abstract) is the object type created. Participants:
Creator: declares
factoryMethod(), may also contain default implementation using Product interface.ConcreteCreator: overrides
factoryMethod()to create a specific ConcreteProduct.Product: defines the interface of objects the factory method creates.
ConcreteProduct: implements Product. Example: Suppose
Dialogis a base GUI class.
Typical Use Cases: GUI frameworks where new button types are provided by subclasses, plugin frameworks, document parsers, or when instantiation logic is deferred to subclasses. Pros: Promotes flexibility: new concrete products can be introduced by subclassing without changing creator. Supports single responsibility (object creation code is in one place). Cons: Requires subclassing (each product needs a new subclass); adds extra classes and indirection. Overkill if only one type of product is needed. Complexity: moderate (introduction of inheritance hierarchy). 【16†L6-L10】
Abstract Factory
Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes【10†L65-L68】. Adds a layer of abstraction over factory methods.
Problem: Need to create many related objects (possibly from different families) and ensure they are used together (e.g. cross-platform UI controls). Clients should be decoupled from concrete products.
【26†embed_image】 Figure: UML class diagram of the Abstract Factory pattern. Structure: AbstractFactory interface declares creation methods for each kind of product. ConcreteFactoryA/B implements these methods to create a family of products (e.g. createButton(), createCheckbox()). Corresponding AbstractProduct interfaces (Button, Checkbox, etc.) and ConcreteProduct classes exist. The client uses the abstract factory and products only through interfaces.
Participants:
AbstractFactory: interface with methods to create abstract products.
ConcreteFactory: implements creation methods, returns concrete products.
AbstractProduct: interface/abstract class for each product type.
ConcreteProduct: implements each AbstractProduct, produced by corresponding factory. Example: Cross-platform GUI:
Use Cases: GUI/toolkit libraries (themes), database drivers, cross-platform libraries. When you need to enforce use of related products together. Pros: Promotes consistency among products of a family; easy swapping of entire families by changing factory; isolates concrete classes. Cons: Adding a new product type requires changes to factory interfaces and all factories. Complex to set up, especially if product families evolve. Complexity: moderate-high (many classes).
Builder
Intent: Construct complex objects step by step, allowing different representations using the same construction process【107†L10-L12】. Useful when object construction involves many steps or parameters.
Problem: A class has many constructor parameters or complex assembly logic (e.g. building a House or Car with many parts). A telescoping constructor or large subclasses would be unwieldy. The client should not need to know how the object is built.
【116†embed_image】 Figure: UML class diagram of the Builder pattern. Structure: Builder declares methods for creating parts (buildStepA(), buildStepB(), etc.). ConcreteBuilder1/2 implement these steps to build different representations. Director (optional) orchestrates the building steps. Product is the complex object being built. Client configures a builder (optionally via Director) and then retrieves the result from the builder.
Participants:
Builder (interface): declares building steps and a method to get the product.
ConcreteBuilder: implements the steps and maintains the product instance.
Product: the complex object under construction.
Director: (optional) defines the order of building steps. Example (Java): Building a
Pizzawith optional toppings:
Use Cases: Creating complex immutable objects (like parsers, GUI builders, or data structures). When construction steps can be varied or assembled in different orders. Pros: Isolates complex construction from the final representation, makes code readable (step-by-step), supports method chaining. Can reuse construction algorithms for different products. Cons: More classes; adds indirection. Not as useful for simple objects. Complexity: moderate (requires builder classes).
Prototype
Intent: Create new objects by copying (cloning) an existing object (the prototype) without knowing its concrete class【109†L12-L14】.
Problem: Avoid building a new object from scratch or when types of objects to create are determined at runtime. Direct new requires knowing the class. Cloning decouples instantiation from the concrete class.
Participants:
Prototype (interface/abstract): declares a
clone()method.ConcretePrototype: implements cloning (often via a copy constructor or deep copy) and returns a copy of itself.
Client: creates a clone by calling
clone()on a prototype. Structure: Clients hold a reference to aPrototypeand invokeclone()to get a new object. The original prototype object serves as a template. Use Cases: When performance of building is costly, or when a system should be independent of how its products are created. Examples: Object editors, graphic shapes, document templates, or where objects are preconfigured. Pros: Avoids subclassing just to create an instance; can add and use prototypes at runtime. Cons: Cloning can be tricky (proper deep copy vs shallow copy), and classes must support copying. Adds complexity to maintain clone methods. Complexity: low-medium (implementclonemethod and manage copy).
Structural Patterns
Adapter
Intent: Allow classes with incompatible interfaces to work together by wrapping an interface with a different interface【31†L10-L13】. Converts the interface of a class into another expected by the client. Problem: You have an existing class (Adaptee) with a useful interface, but your code expects a different interface (Target). You want to reuse Adaptee without modifying it. 【32†embed_image】 Figure: UML class diagram of the Adapter pattern. Structure: Target (the interface clients expect) and Adaptee (existing class). Adapter implements Target and holds (or inherits) Adaptee, forwarding requests to the adaptee. Clients use Target interface only. Participants:
Target: the desired interface.
Adaptee: existing class with an incompatible interface.
Adapter: implements Target, translates calls to Adaptee.
Client: uses Target interface. Example: Converting a
MediaPlayerinterface to play different formats:
Use Cases: Integrating legacy or third-party classes into a new system; API conversions; when two components have incompatible interfaces. Pros: Promotes reusability: you can adapt old classes without modifying them. Decouples client from concrete classes. Cons: Introduces an additional layer of indirection. If interfaces change, adapters need updating. Complexity: low (just one adapter class per adaptee interface).
Bridge
Intent: Separate an abstraction from its implementation so that the two can vary independently【34†L10-L12】. Useful for cross-platform or when both abstractions and implementations have multiple variants. Problem: When you have a class that should be able to switch implementations at runtime, or when both an interface and its implementation have many possible combinations. Using inheritance for each combination leads to a class explosion. 【35†embed_image】 Figure: UML class diagram of the Bridge pattern. Structure: Abstraction (interface with a reference to an Implementor interface). RefinedAbstraction extends Abstraction. ConcreteImplementorA/B implement the implementation interface. Abstraction forwards calls to the Implementor. Participants:
Abstraction: defines the abstraction’s interface, holds a reference to an Implementor.
RefinedAbstraction: extends Abstraction.
Implementor (interface): defines implementation methods.
ConcreteImplementor: implements Implementor. Example: Shape drawing API with different rendering engines:
Use Cases: GUI toolkits (widgets vs platforms), printer/copier (device-independent printing), separating business logic from persistence, or where both functionality and implementation vary. Pros: Decouples interface from implementation; adds flexibility for independent extension. Reduces subclass proliferation. Cons: More complex class structure; clients must manage two class hierarchies. Complexity: moderate.
Composite
Intent: Compose objects into tree structures so clients can treat individual objects and compositions uniformly【37†L12-L14】. It lets you build a part-whole hierarchy of objects. Problem: You need to represent a tree of objects (e.g. GUI components, file system) and want to work with leaf objects and composite groups in the same way. 【38†embed_image】 Figure: UML class diagram of the Composite pattern. Structure: Component (abstract class or interface) defines common methods. Leaf implements Component for simple elements. Composite also implements Component and contains a collection of Components (children). Both Leaf and Composite are treated uniformly. The client uses the Component interface. Participants:
Component: declares interface for all objects in the composition (e.g.
operation()). May include default methods for managing children.Leaf: represents end objects with no children. Implements Component.
Composite: maintains children (list of Components), implements Component. Implements child-related operations (
add(),remove(), and delegates operations to children). Example: A graphic shapes example:
Use Cases: Graphic or UI tree (widgets in a window), file systems (files and folders), organizational hierarchies, XML/HTML DOM. Pros: Simplifies client code: treat groups and single objects uniformly; flexible tree structures. Cons: Can make the design overly general and less type-safe (e.g. Leaf vs Composite differentiation may need type checks). Complexity: moderate.
Decorator
Intent: Attach additional responsibilities to objects dynamically by placing them in wrapper objects【40†L12-L14】. Provides a flexible alternative to subclassing for extending functionality. Problem: Need to add responsibilities or behaviors to individual objects at runtime without affecting other objects, or avoid a combinatorial explosion of subclasses. Structure: Component interface defines behavior. ConcreteComponent is the original object. Decorator (abstract) implements Component and has a field referencing a Component. ConcreteDecorator subclasses extend Decorator, adding behavior before/after delegating to the wrapped component. Participants:
Component: interface for objects that can have responsibilities added.
ConcreteComponent: original object.
Decorator: abstract wrapper holding a Component reference and implementing Component.
ConcreteDecorator: adds behavior (state or methods) and delegates to wrapped component. Example: Adding text formatting:
Use Cases: Adding features like scrollbars to windows, I/O stream wrappers (e.g. Java’s BufferedReader wraps a Reader), adding responsibilities like logging, encryption.
Pros: Avoids subclass proliferation, can combine behaviors at runtime, respects Open/Closed principle.
Cons: Many small classes, harder to configure (stacking multiple decorators). Debugging wrappers can be tricky. Complexity: moderate.
Facade
Intent: Provide a simplified, high-level interface to a complex subsystem, making it easier to use【43†L10-L11】. A facade masks the complexities of subsystems. Problem: A subsystem has many classes/methods and clients need a simple way to use it without dealing with all the details. Without Facade, clients would have tight coupling to many subsystem classes. 【44†embed_image】 Figure: UML class diagram of the Facade pattern. Structure: Facade defines a simple interface that delegates calls to underlying subsystem classes. Clients depend only on the Facade, not on the subsystem interfaces directly. Subsystems work independently and do not reference the Facade. Participants:
Facade: provides higher-level methods that internally call subsystem objects.
Subsystem classes: implement business logic (do not depend on the facade).
Client: uses the facade instead of many subsystem components. Example: Simplifying a complex home theater system:
Use Cases: Simplifying APIs (e.g. database library, graphics engine), providing an entry point to a subsystem, decoupling clients from multiple classes. Pros: Reduces dependencies; improves readability; easy to use. Cons: Facade can become a god object if it knows too much. Adds an extra layer. Complexity: low to moderate.
Flyweight
Intent: Minimize memory use by sharing as much data as possible with similar objects; use caching/sharing to support large numbers of fine-grained objects【46†L12-L14】. Problem: A large number of similar objects (e.g. characters in a document, particles in a simulation) would consume too much memory if all data were distinct. Need to share common, immutable state and store only extrinsic (unique) state per object. 【47†embed_image】 Figure: UML class diagram of the Flyweight pattern. Structure: Flyweight interface defines operations that take extrinsic state. ConcreteFlyweight stores intrinsic (shared) state. FlyweightFactory manages a pool of flyweights (often keyed by intrinsic state). Clients request a flyweight with certain intrinsic state; the factory returns a shared instance (creating it if necessary). The client then uses it with additional extrinsic state. Participants:
Flyweight: interface with methods that accept extrinsic state.
ConcreteFlyweight: implements Flyweight and stores intrinsic state.
FlyweightFactory: creates and manages shared Flyweight instances.
Client: maintains or computes extrinsic state and passes it to Flyweight methods. Example: Text formatting where each character (glyph) is shared:
Use Cases: Text editors (fonts, glyphs), large numbers of similar objects (sprites in games), database connection pools. Many Java standard classes use flyweights (e.g. Integer.valueOf(int) caches small ints).
Pros: Significant memory savings when many duplicates exist.
Cons: Increased complexity (separation of states); clients must handle extrinsic state correctly. Factory needs to manage lifecycle. Complexity: moderate.
Proxy
Intent: Provide a surrogate or placeholder for another object to control access to it. The proxy implements the same interface as the real object【49†L10-L13】 and adds a level of indirection. Problem: You want to add a layer of control (such as lazy initialization, access control, logging, or remote access) without modifying the real object’s code or exposing it directly. 【50†embed_image】 Figure: UML class diagram of the Proxy pattern. Structure: Service (subject interface) and RealService (the real object). Proxy implements Service and holds a reference to RealService. Clients use Proxy transparently as if it were RealService. Participants:
Subject (interface): common interface.
RealSubject: the real object.
Proxy: implements Subject, controls access to RealSubject (e.g. creates it on demand, checks permissions, logs calls).
Client: works with Subject interface. Example (Virtual Proxy for large image):
Use Cases: Remote proxies (stub/proxy for remote objects), protection proxies (access control), virtual proxies (lazy-load expensive objects), smart references (counting references, caching). Pros: Transparent to client; adds functionality without changing original. Cons: Adds an extra level of indirection. Overhead of proxy logic. Complexity: low-medium.
Behavioral Patterns
Chain of Responsibility
Intent: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Pass the request along a chain until some handler handles it【52†L1-L4】. Problem: A request may have multiple potential handlers, and you want to decouple sender and receiver. If you hard-code handler choice, it’s inflexible. Structure: Handler defines an interface and optionally maintains a reference to the next handler. ConcreteHandler implements request handling; if it can’t handle, it forwards to the next handler. The client sends a request to the first handler. 【53†embed_image】 Figure: UML class diagram of the Chain of Responsibility pattern. Clients interact with handlers uniformly; each handler knows only about the next handler (if any). Participants:
Handler (abstract): defines
handleRequest(request)and holds reference tonext.ConcreteHandler: implements actual request processing or forwards it.
Client: initiates the chain by passing request to the first handler. Use Cases: Event handling (GUI events), middleware (chain of filters), permission checks, parsing/linking steps (e.g. compiler phases), logging. Pros: Reduced coupling; handlers can be added or reordered dynamically. Cons: Request may go through many objects (performance); no guarantee a handler will process it (need a terminal handler). Complexity: moderate.
Command
Intent: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations【55†L1-L4】.
Problem: You want to issue requests without knowing who will handle them or how; also to support undo/redo, logging, or queuing of operations.
Structure: Command interface declares execute(). ConcreteCommand implements it and stores receiver and request details. Receiver has the actual business logic. Invoker holds a command and triggers it (e.g. via press() calls command.execute()). Client creates a ConcreteCommand, sets receiver and action, and passes it to invoker.
【56†embed_image】 Figure: UML class diagram of the Command pattern. Commands decouple invoker from receiver.
Participants:
Command: interface with
execute().ConcreteCommand: implements Command, stores action details and a reference to Receiver.
Invoker: asks the command to carry out the request.
Receiver: knows how to perform the operations.
Client: creates and configures commands. Example: Remote control for a device:
Use Cases: GUI buttons/menus, transactional behavior, job queues, callback implementations, multi-level undo/redo stacks. Pros: Decouples invoker and receiver; easy to add new commands; supports operations like undo (by storing state in commands). Cons: May increase number of classes. Commands may have to store state for undo (memento pattern often used together). Complexity: moderate.
Interpreter
Intent: Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language. In practice, the pattern is used to evaluate expressions or parse commands【60†L42-L49】【62†L149-L157】.
Problem: You need to parse and evaluate sentences in a simple language (often domain-specific, e.g. mathematical expressions or commands). Without Interpreter, you might write ad-hoc parsing code scattered around.
Structure: Represent grammar rules as classes (TerminalExpression, NonTerminalExpression). Context holds global information. Composite pattern is often used for the syntax tree. Each Expression implements an interpret(Context) method. The client builds the syntax tree of Expressions and calls interpret.
Participants:
AbstractExpression: declares an interface for interpreting.
TerminalExpression: represents leaf nodes (e.g. numbers or variables).
NonTerminalExpression: represents composite nodes (e.g. +, -, * with child expressions).
Context: contains information that's global to the interpreter (if needed).
Client: builds or obtains an expression tree and calls interpret. Use Cases: SQL parsing, regex engine, arithmetic expression evaluators, configuration file parsers. Java’s
PatternandMatcher(regex) are examples. Complexity: Interpreter can become hard to maintain for complex grammars; usually suitable for simple grammars.
Iterator
Intent: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation【95†L1-L4】.
Problem: You need to traverse a collection or aggregate (like a list, tree, or any container) without giving the client direct access to the container’s structure. You want to support multiple traversal algorithms.
【65†embed_image】 Figure: UML class diagram of the Iterator pattern. Structure: Iterator interface (getNext(), hasMore()). ConcreteIterator implements Iterator for a specific collection. IterableCollection (or Aggregate) interface defines createIterator(). Concrete collections implement Iterable to return their Iterator.
Participants:
Iterator: interface for accessing elements (
next(),hasNext()).ConcreteIterator: implements Iterator, tracks traversal state.
Aggregate: interface with a method to create an iterator.
ConcreteAggregate: implements and returns an appropriate Iterator.
Client: uses iterator to traverse without knowing the collection internals. Example (Java):
Use Cases: All collections in languages (Java’s Iterator and enhanced-for, Python’s iterators). Also for traversing composite structures, tree traversals, etc.
Pros: Separates traversal logic from collection; multiple iterators can exist on one aggregate.
Cons: Additional object for each traversal; can be simplistic if language already has native iterators. Complexity: low.
Mediator
Intent: Define an object that encapsulates how a set of objects interact, promoting loose coupling by preventing objects from referring to each other explicitly. All communications go through the mediator【96†L1-L4】.
Problem: Inter-object communication is becoming complex (many-to-many) and components are tightly coupled. You want to decouple the components so each communicates only through a mediator.
【68†embed_image】 Figure: UML class diagram of the Mediator pattern. Structure: Mediator interface declares a method for communication (e.g. notify(sender, event)). ConcreteMediator implements interaction logic and holds references to components. Component classes (colleagues) have a reference to Mediator and communicate by calling the mediator. Components don’t talk directly to each other.
Participants:
Mediator (interface): declares notification method.
ConcreteMediator: coordinates interaction between components.
Component: abstract/colleague class, each holds reference to Mediator and calls
mediator.notify(this, event)when state changes.ConcreteComponent: implements interactions. Example: A dialog form where widgets depend on each other:
Use Cases: GUI dialogs (coordinating widgets), chat rooms, event-bus systems, networking (air traffic control tower analogy). Reduces many-to-many dependencies. Pros: Simplifies object communication, reduces coupling (each component only knows the mediator). Cons: Mediator can become complex if many components; single point of communication may become a bottleneck. Complexity: moderate.
Memento
Intent: Capture and externalize an object’s internal state so it can be restored later, without violating encapsulation【97†L1-L4】. (Often used for undo/redo.) Problem: You want to implement undo/rollback of state changes, but you don’t want to expose object’s internals or mix storage logic into business logic. Directly copying state would break encapsulation. 【71†embed_image】 Figure: UML class diagram of the Memento pattern. Structure: Originator (the object whose state is saved) creates a Memento (snapshot of its state). Caretaker (undo manager or history) keeps Mementos and requests Originator to restore. Memento exposes only narrow interface to outside (possibly only metadata), while Originator can access full state. Participants:
Originator: can create a snapshot (Memento) of its current state and restore from a Memento.
Memento: immutable object that stores the internal state. Only Originator can access its full state (protected by encapsulation).
Caretaker: responsible for storing and retrieving mementos but does not inspect state. Example: Text editor undo:
Use Cases: Undo/redo mechanisms, transactional behavior (savepoints), state rollback. Pros: Encapsulates the saved state, preserving encapsulation. Cons: Can consume memory if state is large or many states are saved; complexity in implementing correct deep copy vs incremental save. Complexity: moderate.
Observer (Publish/Subscribe)
Intent: Define a subscription mechanism so that when one object (subject) changes state, its dependents (observers) are notified and updated automatically【98†L1-L4】.
Problem: You have one-to-many dependencies: an object’s change should update many others, and you want to minimize coupling between subject and observers.
【74†embed_image】 Figure: UML class diagram of the Observer pattern. Structure: Publisher maintains a list of Subscribers. Publisher interface declares subscribe(), unsubscribe(), and notifySubscribers() methods. ConcretePublisher calls notifySubscribers() when its state changes. Subscriber interface declares an update() method. ConcreteSubscriber implements this to respond to changes. Clients register subscribers with the publisher.
Participants:
Publisher (Subject): keeps state; has methods to attach/detach observers; notifies observers of changes.
Subscriber (Observer): defines
update()method.ConcretePublisher: stateful object (e.g. data model).
ConcreteSubscriber: e.g. UI element, logging component. Example: Weather station data updates multiple displays:
Use Cases: Event handling systems (GUI listeners, MVC model-view), notification services, distributed event buses. Pros: Supports broadcast to many observers; subjects and observers are loosely coupled (subject does not need to know concrete observers). Cons: Observers may need to query subject for details (pull model); tricky to manage memory (dangling subscriptions). Complexity: moderate.
State
Intent: Allow an object to alter its behavior when its internal state changes, appearing to change its class【76†L10-L12】. Each state is represented by a separate class.
Problem: An object has behavior that depends on its state (often represented by state variables and complex conditionals). Using conditional logic (if/switch) for each state leads to code that is hard to maintain.
【77†embed_image】 Figure: UML class diagram of the State pattern. Structure: Context holds a reference to a State object. State is an interface declaring state-specific methods. ConcreteState implements behavior for a specific state and may transition the context to a different state. The context delegates state-dependent work to the current state object.
Participants:
Context: maintains current state and delegates requests to it.
State (interface): declares methods for state-specific behavior.
ConcreteState: implements a behavior associated with a state of the Context. May change the state of the context. Example: Document with states (Draft, Moderation, Published):
Use Cases: Finite State Machines, parser state, TCP connection states, UI components with modes. Pros: Localizes state-specific behavior into classes; eliminates complex conditionals. Cons: Increases number of classes; state transitions are dispersed. Complexity: moderate.
Strategy
Intent: Define a family of interchangeable algorithms and encapsulate each one, letting clients use them interchangeably【100†L1-L4】. The context holds a reference to a Strategy and delegates work to it.
Problem: You have multiple ways (algorithms) to perform a task and you want to select the algorithm at runtime. Without Strategy, you might use conditionals or duplicate code.
【79†L10-L12】【79†L54-L62】 Figure: UML class diagram of the Strategy pattern. Structure: Strategy interface declares an operation (e.g. execute()). ConcreteStrategy classes implement various algorithms. Context holds a reference to Strategy and calls it in doSomething(). Client configures context with a ConcreteStrategy.
Participants:
Strategy: interface for algorithm.
ConcreteStrategy: implements specific behavior.
Context: maintains a reference to Strategy and uses it.
Client: selects and assigns a strategy to context. Example: Arithmetic operations:
Use Cases: Sorting algorithms, compression algorithms (zip vs rar), file naming/formatting strategies, UI layouts. Also used in Java’s comparator (passing a strategy to sort). Pros: Easy to add new strategies without changing context; avoids conditional logic for choosing algorithm. Cons: Clients must be aware of different strategies (the Context code is minimal). Overhead of more classes. Complexity: moderate.
Template Method
Intent: Define the skeleton of an algorithm in an operation (template method), deferring some steps to subclasses. Allows subclasses to redefine specific steps without changing overall structure【101†L1-L4】.
Problem: Several classes share common code structure (algorithm) but differ in some details. Copying common code leads to duplication; conditionals in one class lead to less polymorphism.
【83†embed_image】 Figure: UML class diagram of the Template Method pattern. Structure: AbstractClass implements the template method (an algorithm made of steps) and may provide default implementations of some steps. ConcreteClass overrides specific steps. The template method (final) calls the steps in order.
Participants:
AbstractClass: declares the template method and abstract/optional step methods.
ConcreteClass: implements the abstract steps. Example: Data processing:
Use Cases: Code with invariant and variant parts (report generation, game AI behaviors, data parsing). A classic example is the Java AbstractList class which implements common methods calling abstract get() and size().
Pros: Eliminates code duplication of invariant parts; enforces algorithm structure.
Cons: Rigid: hard to change order of steps at runtime; subclasses can't override the template method itself. Complexity: low-medium.
Visitor
Intent: Represent an operation to be performed on elements of an object structure without changing the classes of the elements. Lets you add new operations without modifying element classes【102†L1-L4】.
Problem: You have a class hierarchy of objects and you need to define operations over them that depend on their concrete classes. You don’t want to embed these operations in the classes (maybe they should remain unchanged or are in a library).
【86†embed_image】 Figure: UML class diagram of the Visitor pattern. Structure: Visitor interface declares a visit(ElementA), visit(ElementB), etc. ConcreteVisitor implements operations for each element type. Element interface declares accept(Visitor). ConcreteElement implements accept() to call visitor.visit(this). The client iterates over elements and calls accept(visitor).
Participants:
Visitor (interface): declares visit methods for each ConcreteElement type.
ConcreteVisitor: implements visitor operations for each element.
Element (interface): declares
accept(Visitor).ConcreteElement: implements
accept()by calling back to visitor (visitor.visit(this)).ObjectStructure: collection of elements (e.g. list or tree).
Client: creates a visitor and traverses elements, calling
element.accept(visitor). Example: Export different node types in a document graph:
Use Cases: Operations on composite structures (compilers: type-checker and code generator on AST, graphics: rendering and hit-testing), adding behavior to classes in a framework (without modifying them). Pros: Adding new operations is easy: just add a new Visitor. Separates algorithms from object structure. Cons: Adding new element classes is hard (must update all visitors). Can break encapsulation (Visitor accesses elements’ internals). Complexity: moderate.
Concurrency Patterns
Note: Concurrent and threaded designs have many specialized patterns (e.g. POSA2 lists 17 patterns【19†L29-L37】). Common examples:
Thread Pool: Maintain a pool of worker threads to perform tasks. In Java,
ExecutorServiceprovides this functionality (e.g.Executors.newFixedThreadPool(n)) so clients submitRunnable/Callabletasks, and the pool manages threads. Kotlin can use coroutines (Dispatchers.Defaultorlaunch). Thread pools improve performance by reusing threads.Future / Promises: Represent an eventual result of an async computation. Java’s
Future<T>(orCompletableFuture<T>) lets you execute tasks asynchronously and retrieve results later. This decouples execution from retrieval (like a Proxy for a result).Active Object: Decouple method invocation from execution to allow concurrent execution. Client calls a proxy (active object) which puts request in a queue; worker thread executes and may notify client later.
Half-Sync/Half-Async (POSA): Layered processing: asynchronous I/O at top (event handling threads), synchronous processing in worker threads below, with a queue between.
Leader/Followers: Manage multiple threads waiting for events on shared resources. A thread becomes leader to wait on events, then demotes itself, handing the role to another, etc.
Guarded Suspension: One thread waits for a condition (guard), another signals it; uses wait/notify. (Java’s
wait()/notify()).
Concurrency patterns often address performance, scalability, and race conditions. They add complexity (synchronization, potential deadlocks) but are vital in multithreaded applications.
Architectural Patterns
Architecture patterns describe high-level structures:
Model-View-Controller (MVC): Divides an application into Model (data/business logic), View (UI), and Controller (input handling)【110†L1-L4】. This decouples components. For example, web frameworks (Rails, Django) follow MVC. Pros: Parallel development, reuse; Cons: Can add complexity if simple UI. Common in desktop and web apps.
Microservices: Build applications as a suite of small, independently deployable services. Each microservice has its own process and communicates over the network (HTTP/REST, messaging)【111†L3-L11】. Pros: Scalability and independent development/deployment of components【111†L3-L11】. Cons: Distributed complexity (network latency, data consistency, deployment overhead). Often uses patterns like API Gateway and Event Bus internally.
Client-Server: Traditional two-tier model: clients request services, servers provide them【113†L490-L499】. Classic example: web browser (client) and web server. Pros: Centralized resources and management; Cons: Server can be a bottleneck/point of failure【113†L515-L518】.
Layered (N-tier) Architecture: Organize software into layers (presentation, business, data access, etc.), each layer providing services to the next【113†L570-L579】【113†L607-L610】. Layers interact in one direction (upper→lower). Pros: Clear separation, reusable layers; Cons: Overhead from extra layers, latency between layers【113†L607-L610】.
Event-Driven (Event-Bus): Components communicate by emitting events to a central bus or broker; other components subscribe to events. Enables decoupling and asynchronous interaction. Used in GUIs (publish/subscribe), microservices (message brokers), and workflows.
Blackboard: Solve complex problems by iteratively refining a solution on a common data structure (blackboard) by multiple specialized subsystems (knowledge sources). Used in AI and speech recognition systems.
Broker: Organize distributed systems with clients, servers, and brokers that mediate communication (e.g. CORBA or message-oriented middleware).
Pipe-and-Filter: Series of processing steps (filters) connected by data streams (pipes), each filter transforms the data (e.g. UNIX pipelines, compilers).
Many architectural patterns can incorporate design patterns internally (e.g. MVC uses Observer, Factory, etc.). Choosing the right architecture depends on application needs; misuse can cause inefficiency or complexity【113†L607-L610】.
Enterprise Patterns
From Martin Fowler’s Patterns of Enterprise Application Architecture【91†L107-L115】【91†L137-L145】:
Transaction Script vs Domain Model: In Transaction Script, each business operation is implemented as a procedure (simplest approach). In Domain Model, you create an object model that encapsulates business logic in classes. Use Transaction Script for simple or legacy apps; Domain Model for complex domains with many rules.
Active Record: An object wraps a database row; its methods save and load its own data (e.g. ORM with objects containing SQL code).
Data Mapper: A layer of mappers transfers data between objects and database, keeping them independent【91†L142-L145】. Objects don’t know about the database; mappers handle SQL.
Table Data Gateway / Row Data Gateway: Objects that act as gateways to a table or single record, respectively.
Service Layer: A set of application operations offered as services, coordinating the domain model and data access【91†L119-L123】.
Table Module: A single class that handles business logic for all records in a table. (Deprecated in favor of domain model or service layer).
Lazy Load: Delay fetching data until needed. For example, an ORM may load an object’s related collection only when accessed【91†L159-L162】.
Unit of Work: Track changes to objects (new/dirty/deleted) and commit them as a single transaction【91†L149-L152】.
Identity Map: Ensure each database entity is loaded only once by keeping a cache mapping ID→object【91†L154-L156】.
These patterns address common challenges in enterprise apps (data mapping, transactions, object lifecycle). For example, Active Record might look like:
In Kotlin:
A Data Mapper example:
Anti-Patterns
Anti-patterns are common but poor solutions. Examples include:
God Object: An object that accumulates too many responsibilities【88†L156-L160】 (a single class does the work of many, becoming a “god” class). Leads to low cohesion.
Big Ball of Mud: No recognizable structure; codebase becomes chaotic【88†L132-L139】.
Golden Hammer: Over-reliance on a familiar tool/pattern for all problems (if all you have is a hammer, everything looks like a nail).
Copy-Paste Programming: Repeating code instead of abstracting, leading to maintenance nightmares.
Cargo Cult Programming: Including code or patterns without understanding, just because “it worked somewhere else.”
Spaghetti Code: Tangled control flow, usually in procedural programming.
Lava Flow: Dead code that nobody understands but keeps around “just in case.”
Magic Numbers/Strings: Using hard-coded values instead of named constants.
Overgeneralization (Yo-Yo Problem): Too many levels of abstraction, so understanding code bounces around class hierarchy.
Recognizing and avoiding anti-patterns is as important as using good patterns【88†L156-L160】.
Comparison Table of Key Patterns
Singleton
Creational
Ensure only one instance exists, global access【103†L1-L4】
Low
Logging, config, thread pools (single point of access).
Factory Method
Creational
Interface for creating objects, subclasses decide class
Low-Mod
When a class can’t anticipate needed object types (frameworks, plugins)【16†L1-L4】.
Abstract Factory
Creational
Interface to create families of related objects
Mod
GUI themes (Windows/Mac widgets), DB drivers – ensure compatible products【10†L65-L68】.
Builder
Creational
Construct complex object step by step
Mod
Complex object construction (with many options) like parsers, UI builders【107†L10-L12】.
Prototype
Creational
Clone existing objects without coupling to their classes
Mod
Performance (objects with many configs), avoiding class knowledge【109†L12-L14】.
Adapter
Structural
Allow incompatible interfaces to work together
Low
Legacy APIs, converting one interface to another.
Bridge
Structural
Separate abstraction from implementation
Mod
GUI components independent of OS, multiple implementations.
Composite
Structural
Treat groups of objects and individual objects uniformly
Mod
Trees (file system, UI components).
Decorator
Structural
Dynamically add behavior to objects (wrappers)
Mod
Adding responsibilities (I/O streams, UI decorations) at runtime.
Facade
Structural
Simplify interface to a complex subsystem
Low
Simplifying subsystem usage (e.g. library APIs, home theater controls)【43†L10-L11】.
Flyweight
Structural
Share objects to support large numbers with little memory
Mod
Many similar objects (text glyphs, particles).
Proxy
Structural
Surrogate for another object to control access
Low-Mod
Lazy loading, remote proxies, access control.
Chain of Resp.
Behavioral
Pass requests along a handler chain until handled【52†L1-L4】
Mod
Event handling, middleware pipelines, logging handlers.
Command
Behavioral
Encapsulate requests as objects (queue/undo)【55†L1-L4】
Mod
GUI actions (menus/buttons), task queues, undo mechanisms.
Interpreter
Behavioral
Evaluate sentences of a language; grammar defined by classes
High
DSLs, expression evaluation (small grammars).
Iterator
Behavioral
Traverse a collection without exposing its representation【95†L1-L4】
Low
All collection traversal (for-each loops).
Mediator
Behavioral
Centralize complex communication between objects【96†L1-L4】
Mod
Dialog boxes, chat rooms, decoupling peer communication.
Memento
Behavioral
Capture object state for rollback without exposing it【97†L1-L4】
Mod
Undo/redo, transaction rollback.
Observer
Behavioral
Notify multiple observers of state changes【98†L1-L4】
Mod
Event subscribers, model-view updates (MVC).
State
Behavioral
Change behavior based on internal state【76†L10-L12】
Mod
Finite-state machines (UI modes, workflows).
Strategy
Behavioral
Interchangeable algorithms, chosen at runtime【100†L1-L4】
Mod
Pluggable behaviors (sorting, pricing strategies).
Template Method
Behavioral
Define algorithm skeleton in base class【101†L1-L4】
Low-Mod
Code reuse with invariant and variable steps (frameworks, data processing).
Visitor
Behavioral
Add operations to object structure without changing classes【102†L1-L4】
Mod
Separate algorithms from objects (e.g. compilers, serializers).
Complexity: Qualitative estimate (Low/Moderate/High). When to Use: Summary of typical scenarios.
Each pattern has trade-offs: e.g. some add extra classes (Builder, Visitor), others simplify client code (Facade, Strategy)【43†L10-L11】【100†L1-L4】. Choose based on your design needs.
Sources: Definitions and explanations are drawn from primary pattern literature and authoritative sources (GoF text, Fowler’s EAA, POSA, official docs). For example, the GoF catalog defines 23 classic patterns【62†L159-L163】. Fowler’s catalog lists enterprise-specific patterns【91†L107-L115】【91†L137-L145】. Pattern definitions above cite Refactoring.Guru, Baeldung, Fowler, and other reputable resources to ensure accuracy【103†L1-L4】【91†L107-L115】【19†L29-L37】. Any omissions or variations in pattern lists (e.g. concurrency patterns count) have been noted in context.
Last updated