Design Patterns
there are three main categories of Design Patterns as decreed by the ‘Gang of Four’1 (the authors of a seminal work Design Patterns | Elements of Reusable Object-Oriented Software). the content here is largely based on Dive into Design Patterns by Alexander Shvets2.
| Creational | Structural | Behavioural |
|---|---|---|
| 1. Factory Method | 6. Adapter | 13. Interpreter |
| 2. Abstract Factory | 7. Bridge | 14. Template Method |
| 3. Builder | 8. Composite | 15. Chain of Responsibility |
| 4. Prototype | 9. Decorator | 16. Command |
| 5. Singleton | 10. Façade | 17. Iterator |
| 11. Flyweight | 18. Mediator | |
| 12. Proxy | 19. Memento | |
| 20. Observer | ||
| 21. State | ||
| 22. Strategy | ||
| 23. Visitor |
Creational
provide object creation mechanisms that increase flexibility and reuse of existing code.
Factory Method
provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
structure
- product declares the interface common to all objects that can be produced
- concrete products are different implementations of the product interface
- creator declares the factory method that returns new product objects
- the return type must match the product interface
- concrete creators override the base factory method to return a different type of product
- the factory method doesn’t have to create new instances every time; it can return cached objects
when to use
- when you don’t know ahead of time the exact types and dependencies of objects your code should work with
- when you want to provide users of your library with a way to extend its internal components
- when you want to save system resources by reusing existing objects instead of rebuilding them
example
interface Button { void render(); }
class WindowsButton implements Button {
public void render() { System.out.println("Windows button"); }
}
class MacButton implements Button {
public void render() { System.out.println("Mac button"); }
}
abstract class Dialog {
public void renderWindow() {
Button btn = createButton(); // factory method call
btn.render();
}
protected abstract Button createButton();
}
class WindowsDialog extends Dialog {
protected Button createButton() { return new WindowsButton(); }
}
class MacDialog extends Dialog {
protected Button createButton() { return new MacButton(); }
}
relations with other patterns
- many designs start with Factory Method and evolve towards Abstract Factory, Prototype, or Builder
- Factory Method is a specialisation of Template Method
- pairs well with Iterator
Abstract Factory
lets you produce families of related objects without specifying their concrete classes.
structure
- abstract factory declares a set of methods for creating each of the abstract products
- concrete factories implement creation methods of the abstract factory, producing a family of products
- abstract products declare interfaces for distinct but related products (a product family)
- concrete products are various implementations grouped by variants
when to use
- when your code needs to work with various families of related products without depending on concrete classes
- when you have a class with a set of Factory Methods that blur its primary responsibility
example
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WinFactory implements GUIFactory {
public Button createButton() { return new WinButton(); }
public Checkbox createCheckbox() { return new WinCheckbox(); }
}
class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
class Application {
private Button button;
public Application(GUIFactory factory) {
button = factory.createButton();
}
}
relations with other patterns
- often implemented with Factory Methods, but can also use Prototype
- can serve as an alternative to Façade when you want to hide platform-specific classes
- works well with Bridge: the factory can create the implementations
Builder
lets you construct complex objects step-by-step. the pattern allows you to produce different types and representations of an object using the same construction code.
structure
- builder interface declares product construction steps common to all types of builders
- concrete builders provide different implementations of the construction steps
- products are resulting objects; products constructed by different builders don’t have to share a common interface
- director defines the order in which to call construction steps (optional)
when to use
- when you want to avoid a “telescoping constructor” (constructor with many optional parameters)
- when you want your code to create different representations of some product
- when constructing Composite trees or other complex objects
example
class Car {
private int seats;
private String engine;
private boolean gps;
// ... more fields
}
class CarBuilder {
private Car car = new Car();
public CarBuilder setSeats(int n) { car.seats = n; return this; }
public CarBuilder setEngine(String e) { car.engine = e; return this; }
public CarBuilder setGPS(boolean g) { car.gps = g; return this; }
public Car build() { return car; }
}
// usage: fluent interface
Car car = new CarBuilder()
.setSeats(4)
.setEngine("V8")
.setGPS(true)
.build();
relations with other patterns
- Builder focuses on constructing complex objects step-by-step; Abstract Factory emphasises families of products
- can be combined with Bridge: the director acts as abstraction, builders as implementations
- often used to build Composite trees
Prototype
lets you copy existing objects without making your code dependent on their classes.
structure
- prototype interface declares the cloning methods (usually just
clone()) - concrete prototypes implement the cloning method, handling edge cases like cloning linked objects
- client can produce a copy of any object that follows the prototype interface
when to use
- when your code shouldn’t depend on the concrete classes of objects you need to copy
- when you want to reduce the number of subclasses that only differ in how they initialise their objects
example
abstract class Shape implements Cloneable {
public int x, y;
public String colour;
public Shape() {}
public Shape(Shape source) { // copy constructor
this.x = source.x;
this.y = source.y;
this.colour = source.colour;
}
public abstract Shape clone();
}
class Circle extends Shape {
public int radius;
public Circle(Circle source) {
super(source);
this.radius = source.radius;
}
public Shape clone() { return new Circle(this); }
}
relations with other patterns
- Prototype is not based on inheritance (unlike Factory Method)
- Abstract Factory, Builder, and Prototype can all be implemented using this pattern
- designs that make heavy use of Composite and Decorator often benefit from Prototype
Singleton
ensures a class has only one instance while providing a global access point to this instance.
structure
- singleton class declares the static method
getInstance()that returns the same instance - the constructor is private to prevent other objects from using
new - the static field stores the singleton instance
when to use
- when a class should have only a single instance available to all clients (e.g., a shared database connection)
- when you need stricter control over global variables
example
class Database {
private static volatile Database instance;
private Connection connection;
private Database() {
connection = new Connection("server");
}
public static Database getInstance() {
if (instance == null) {
synchronized (Database.class) {
if (instance == null) {
instance = new Database();
}
}
}
return instance;
}
}
caveats
- violates the Single Responsibility Principle (manages lifecycle + business logic)
- can mask bad design (components knowing too much about each other)
- requires special treatment in multithreaded environments (double-checked locking)
- difficult to unit test (hard to mock)
relations with other patterns
- Façade objects can often be transformed into Singletons
- Abstract Factories, Builders, and Prototypes can all be implemented as Singletons
Structural
assemble objects and classes into larger structures, while keeping the structures flexible and efficient.
Adapter
allows objects with incompatible interfaces to collaborate. acts as a wrapper between two objects.
structure
- client contains the existing business logic
- client interface describes the protocol other classes must follow to collaborate with the client
- service is some useful class (often third-party or legacy) with an incompatible interface
- adapter wraps the service and implements the client interface, translating calls
when to use
- when you want to use an existing class but its interface isn’t compatible with the rest of your code
- when you want to reuse several existing subclasses that lack common functionality that can’t be added to the superclass
example
interface MediaPlayer {
void play(String filename);
}
class VlcPlayer { // third-party, incompatible interface
void playVlc(String filename) { /* ... */ }
}
class VlcAdapter implements MediaPlayer {
private VlcPlayer vlc = new VlcPlayer();
public void play(String filename) {
vlc.playVlc(filename); // translate the call
}
}
relations with other patterns
- Bridge is designed up-front; Adapter makes things work after they’re designed
- Adapter provides a different interface; Decorator provides an enhanced interface; Proxy provides the same interface
- Façade defines a new interface; Adapter reuses an existing one
Bridge
lets you split a large class or a set of closely related classes into two separate hierarchies: abstraction and implementation.
structure
- abstraction provides high-level control logic; relies on implementation object for low-level work
- implementation declares the interface common to all concrete implementations
- concrete implementations contain platform-specific code
- refined abstractions provide variants of control logic
when to use
- when you want to divide and organise a monolithic class with several variants of some functionality
- when you need to extend a class in several orthogonal (independent) dimensions
- when you need to switch implementations at runtime
example
interface Device {
void enable();
void disable();
int getVolume();
void setVolume(int v);
}
class TV implements Device { /* ... */ }
class Radio implements Device { /* ... */ }
class RemoteControl {
protected Device device;
public RemoteControl(Device d) { this.device = d; }
public void togglePower() {
if (device.isEnabled()) device.disable();
else device.enable();
}
}
class AdvancedRemote extends RemoteControl {
public void mute() { device.setVolume(0); }
}
relations with other patterns
- Bridge is designed up-front; Adapter retrofits existing classes
- can be used together with Abstract Factory to hide platform-specific implementations
- Strategy is similar but focuses on behaviour; Bridge focuses on structure
Composite
lets you compose objects into tree structures and then work with these structures as if they were individual objects.
structure
- component interface describes operations common to both simple and complex elements
- leaf is a basic element that doesn’t have sub-elements
- composite (container) has sub-elements and delegates work to them
- client works with all elements through the component interface
when to use
- when you have to implement a tree-like object structure
- when you want clients to treat simple and complex elements uniformly
example
interface Graphic {
void draw();
}
class Dot implements Graphic {
public void draw() { System.out.println("Draw dot"); }
}
class CompoundGraphic implements Graphic {
private List<Graphic> children = new ArrayList<>();
public void add(Graphic g) { children.add(g); }
public void remove(Graphic g) { children.remove(g); }
public void draw() {
for (Graphic child : children) {
child.draw(); // delegate to children
}
}
}
relations with other patterns
- Builder can be used to construct Composite trees step-by-step
- Iterator can traverse Composite trees
- Visitor can execute an operation over an entire Composite tree
- often used with Decorator
Decorator
lets you attach new behaviours to objects by placing them inside wrapper objects that contain the behaviours.
structure
- component declares the common interface for wrappers and wrapped objects
- concrete component is the class of objects being wrapped
- base decorator has a field for referencing a wrapped object
- concrete decorators define extra behaviours that can be added dynamically
when to use
- when you need to assign extra behaviours to objects at runtime without breaking code that uses them
- when inheritance is awkward or impossible (e.g.,
finalclasses)
example
interface DataSource {
void writeData(String data);
String readData();
}
class FileDataSource implements DataSource { /* ... */ }
class DataSourceDecorator implements DataSource {
protected DataSource wrappee;
public DataSourceDecorator(DataSource source) { this.wrappee = source; }
public void writeData(String data) { wrappee.writeData(data); }
public String readData() { return wrappee.readData(); }
}
class EncryptionDecorator extends DataSourceDecorator {
public void writeData(String data) {
super.writeData(encrypt(data)); // add behaviour
}
}
// stack decorators
DataSource source = new EncryptionDecorator(
new CompressionDecorator(
new FileDataSource("file.txt")));
relations with other patterns
- Adapter changes interface; Decorator enhances without changing
- Composite and Decorator have similar structures; Decorator has a single component
- Strategy changes the guts; Decorator changes the skin
- Proxy manages lifecycle; Decorator is controlled by the client
Façade
provides a simplified interface to a library, framework, or any other complex set of classes.
structure
- façade provides convenient access to a particular part of the subsystem’s functionality
- additional façade can be created to prevent polluting a single façade with unrelated features
- complex subsystem consists of dozens of objects
- client uses the façade instead of calling subsystem objects directly
when to use
- when you need a limited but straightforward interface to a complex subsystem
- when you want to structure a subsystem into layers
example
class VideoConverter {
public File convert(String filename, String format) {
VideoFile file = new VideoFile(filename);
Codec sourceCodec = CodecFactory.extract(file);
Codec destCodec;
if (format.equals("mp4")) destCodec = new MPEG4Codec();
else destCodec = new OggCodec();
VideoFile buffer = BitrateReader.read(file, sourceCodec);
VideoFile result = BitrateReader.convert(buffer, destCodec);
return new AudioMixer().fix(result);
}
}
// client uses simple interface
VideoConverter converter = new VideoConverter();
File mp4 = converter.convert("video.ogg", "mp4");
relations with other patterns
- Façade defines a new simple interface; Adapter wraps existing interfaces
- Abstract Factory can serve as an alternative when you want to hide platform-specific classes
- Façade is often a Singleton
- Mediator organises collaboration; Façade wraps complexity
Flyweight
lets you fit more objects into memory by sharing common parts of state between multiple objects.
structure
- flyweight contains the portion of original object’s state that can be shared (intrinsic state)
- context contains the extrinsic state, unique across all original objects
- flyweight factory manages a pool of existing flyweights, returning cached ones when possible
intrinsic state :: stored in flyweight, immutable, shared extrinsic state :: varies per context, passed to flyweight methods
when to use
- when your program must support a huge number of objects that barely fit in RAM
- when objects contain duplicate state that can be extracted and shared
example
class TreeType { // flyweight: shared state
private String name, colour, texture;
public void draw(int x, int y) { /* draw tree at x,y */ }
}
class TreeFactory {
private static Map<String, TreeType> cache = new HashMap<>();
public static TreeType getTreeType(String name, String colour, String texture) {
String key = name + colour + texture;
if (!cache.containsKey(key)) {
cache.put(key, new TreeType(name, colour, texture));
}
return cache.get(key);
}
}
class Tree { // context: unique state
private int x, y;
private TreeType type; // reference to flyweight
public void draw() { type.draw(x, y); }
}
relations with other patterns
- can combine with Composite to implement shared leaf nodes
- often combined with Singleton (the factory) and Factory Method
Proxy
provides a substitute or placeholder for another object. controls access to the original object.
structure
- service interface declares the interface of the service
- service provides useful business logic
- proxy has a reference field pointing to a service object; after processing, passes the request to the service
types of proxies
- virtual proxy: lazy initialisation (delays creation of heavy objects)
- protection proxy: access control (checks credentials)
- remote proxy: local representative for a remote object
- logging proxy: keeps history of requests to the service
- caching proxy: caches results of client requests
when to use
- lazy initialisation (virtual proxy)
- access control (protection proxy)
- local execution of a remote service (remote proxy)
- logging requests (logging proxy)
- caching request results (caching proxy)
example
interface ThirdPartyYouTubeLib {
List<Video> listVideos();
Video getVideoInfo(String id);
}
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib {
public List<Video> listVideos() { /* slow network request */ }
public Video getVideoInfo(String id) { /* slow network request */ }
}
class CachedYouTubeClass implements ThirdPartyYouTubeLib {
private ThirdPartyYouTubeLib service;
private List<Video> listCache;
public List<Video> listVideos() {
if (listCache == null) listCache = service.listVideos();
return listCache;
}
}
relations with other patterns
- Adapter provides different interface; Proxy provides the same interface; Decorator provides enhanced interface
- Façade is similar but doesn’t implement the same interface
- Proxy manages service lifecycle; Decorator is controlled by the client
Behavioural
allow effective communication and the assignment of responsibilities between objects.
Interpreter
defines a grammar for a language and an interpreter that uses the grammar to interpret sentences in the language.
when to use
- when you have a simple language to interpret and efficiency is not critical
- for parsing expressions, rules, or configuration files
example
interface Expression {
int interpret();
}
class Number implements Expression {
private int value;
public Number(int v) { this.value = v; }
public int interpret() { return value; }
}
class Add implements Expression {
private Expression left, right;
public Add(Expression l, Expression r) { left = l; right = r; }
public int interpret() { return left.interpret() + right.interpret(); }
}
// "1 + 2" represented as:
Expression expr = new Add(new Number(1), new Number(2));
int result = expr.interpret(); // 3
relations with other patterns
- often used with Composite to represent grammar rules
- can use Iterator to traverse the structure
- Visitor can be used to maintain behaviour in one class
Template Method
defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps without changing its structure.
structure
- abstract class declares the template method and abstract primitive operations
- concrete classes implement the primitive operations
when to use
- when you want clients to extend only particular steps of an algorithm, not the whole algorithm
- when you have several classes with almost identical algorithms with some minor differences
example
abstract class DataMiner {
public final void mine() { // template method
openFile();
extractData();
parseData();
analyzeData();
closeFile();
}
abstract void openFile();
abstract void extractData();
void parseData() { /* default implementation */ }
abstract void analyzeData();
abstract void closeFile();
}
class PDFMiner extends DataMiner {
void openFile() { /* open PDF */ }
void extractData() { /* extract from PDF */ }
void analyzeData() { /* analyze */ }
void closeFile() { /* close PDF */ }
}
relations with other patterns
- Factory Method is a specialisation of Template Method
- Template Method uses inheritance; Strategy uses composition
- works at class level (static); Strategy works at object level (dynamic)
Chain of Responsibility
lets you pass requests along a chain of handlers. each handler decides to process the request or pass it to the next handler.
structure
- handler declares the interface and usually contains a field for storing a reference to the next handler
- base handler is an optional class where you can put boilerplate code common to all handlers
- concrete handlers contain the actual code for processing requests
- client may compose chains once or dynamically
when to use
- when your program is expected to process different kinds of requests in various ways, but exact types aren’t known beforehand
- when the set of handlers and their order must be flexible
- when it’s essential to execute several handlers in a particular order
example
abstract class Handler {
protected Handler next;
public void setNext(Handler h) { next = h; }
public abstract void handle(Request req);
}
class AuthHandler extends Handler {
public void handle(Request req) {
if (!req.isAuthenticated()) {
System.out.println("Auth failed");
return;
}
if (next != null) next.handle(req);
}
}
class LoggingHandler extends Handler {
public void handle(Request req) {
System.out.println("Logging: " + req);
if (next != null) next.handle(req);
}
}
// build chain
Handler chain = new AuthHandler();
chain.setNext(new LoggingHandler());
chain.handle(request);
relations with other patterns
- often used with Composite: leaf’s parents can act as chain of handlers
- can be combined with Command: handlers become commands
- related to Decorator: similar structure, different intent
Command
turns a request into a stand-alone object containing all information about the request. lets you parameterise methods, delay or queue execution, and support undoable operations.
structure
- invoker (sender) is responsible for initiating requests; stores a reference to a command object
- command interface usually declares just a single
execute()method - concrete commands implement various requests; not supposed to perform work themselves, but pass the call to a receiver
- receiver contains business logic; almost any object can act as a receiver
when to use
- when you want to parameterise objects with operations
- when you want to queue operations, schedule their execution, or execute them remotely
- when you want to implement reversible operations (undo/redo)
example
interface Command {
void execute();
void undo();
}
class CopyCommand implements Command {
private Editor editor;
private String backup;
public CopyCommand(Editor e) { this.editor = e; }
public void execute() {
backup = editor.getSelection();
editor.clipboard = backup;
}
public void undo() { /* restore from backup */ }
}
class CommandHistory {
private Stack<Command> history = new Stack<>();
public void push(Command c) { history.push(c); }
public Command pop() { return history.pop(); }
}
relations with other patterns
- Chain of Responsibility, Command, Mediator, and Observer address various ways of connecting senders and receivers
- handlers in Chain of Responsibility can be implemented as Commands
- can use Memento to maintain state for undo operations
- Prototype can help when you need to save copies of Commands
Iterator
lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
structure
- iterator interface declares operations for traversing a collection
- concrete iterators implement specific algorithms for traversing
- collection interface declares one or multiple methods for getting iterators
- concrete collections return new instances of a particular concrete iterator class
when to use
- when your collection has a complex data structure and you want to hide it from clients
- to reduce duplication of traversal code across your app
- when you want your code to traverse different data structures or when types are unknown beforehand
example
interface Iterator<T> {
boolean hasNext();
T next();
}
class ArrayIterator<T> implements Iterator<T> {
private T[] items;
private int position = 0;
public ArrayIterator(T[] items) { this.items = items; }
public boolean hasNext() { return position < items.length; }
public T next() { return items[position++]; }
}
interface Collection<T> {
Iterator<T> createIterator();
}
class WordsCollection implements Collection<String> {
private String[] words;
public Iterator<String> createIterator() {
return new ArrayIterator<>(words);
}
}
relations with other patterns
- can use Iterator to traverse Composite trees
- can use along with Factory Method to let subclasses return different iterators
- can use Memento to capture iteration state
- can use Visitor with Iterator to traverse and execute an operation
Mediator
lets you reduce chaotic dependencies between objects by restricting direct communication and forcing them to collaborate via a mediator object.
structure
- components are various classes containing some business logic; each has a reference to the mediator
- mediator interface declares methods of communication with components
- concrete mediator encapsulates relations between various components
when to use
- when it’s hard to change some classes because they are tightly coupled to other classes
- when you can’t reuse a component in another program because it’s too dependent on others
- when you find yourself creating tons of component subclasses just to reuse basic behaviour
example
interface Mediator {
void notify(Component sender, String event);
}
class AuthDialog implements Mediator {
private Button loginBtn;
private Textbox username, password;
private Checkbox rememberMe;
public void notify(Component sender, String event) {
if (sender == loginBtn && event.equals("click")) {
if (checkCredentials()) login();
}
if (sender == rememberMe && event.equals("check")) {
// toggle remember state
}
}
}
class Button extends Component {
public void click() {
mediator.notify(this, "click");
}
}
relations with other patterns
- Chain of Responsibility, Command, Mediator, and Observer address various ways of connecting senders and receivers
- Façade defines a simplified interface; Mediator centralises communication
- difference with Observer: Mediator eliminates mutual dependencies; Observer allows one-way subscriptions
Memento
lets you save and restore the previous state of an object without revealing the details of its implementation.
structure
- originator can produce snapshots of its own state and restore its state from snapshots
- memento is a value object that acts as a snapshot of the originator’s state
- caretaker knows “when” and “why” to capture the originator’s state, and when to restore
when to use
- when you want to produce snapshots of an object’s state to restore a previous state
- when direct access to the object’s fields/getters/setters violates its encapsulation
example
class Editor {
private String text;
private int cursorX, cursorY;
public EditorMemento save() {
return new EditorMemento(text, cursorX, cursorY);
}
public void restore(EditorMemento m) {
this.text = m.getText();
this.cursorX = m.getCursorX();
this.cursorY = m.getCursorY();
}
}
class EditorMemento {
private final String text;
private final int cursorX, cursorY;
// constructor and getters
}
class History {
private Stack<EditorMemento> history = new Stack<>();
public void backup(Editor e) { history.push(e.save()); }
public void undo(Editor e) { e.restore(history.pop()); }
}
relations with other patterns
- can use along with Command to implement undo
- can use along with Iterator to capture the current iteration state
- sometimes Prototype is a simpler alternative when state is simple
Observer
lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
structure
- publisher issues events to other objects; contains subscription infrastructure
- subscriber interface declares the notification interface (usually a single
updatemethod) - concrete subscribers perform actions in response to notifications
when to use
- when changes to one object require changing others, and you don’t know how many objects need to change
- when some objects need to observe others, but only for a limited time or in specific cases
example
interface EventListener {
void update(String eventType, File file);
}
class EventManager {
private Map<String, List<EventListener>> listeners = new HashMap<>();
public void subscribe(String eventType, EventListener listener) {
listeners.get(eventType).add(listener);
}
public void notify(String eventType, File file) {
for (EventListener l : listeners.get(eventType)) {
l.update(eventType, file);
}
}
}
class Editor {
public EventManager events = new EventManager();
private File file;
public void save() {
// save file
events.notify("save", file);
}
}
class LoggingListener implements EventListener {
public void update(String eventType, File file) {
System.out.println("Logged: " + eventType + " on " + file.getName());
}
}
relations with other patterns
- Chain of Responsibility, Command, Mediator, and Observer address various ways of connecting senders and receivers
- difference between Observer and Mediator: Observer = one-way; Mediator = centralised
- can be implemented using Singleton for the publisher
State
lets an object alter its behaviour when its internal state changes. the object will appear to change its class.
structure
- context stores a reference to one of the concrete state objects; delegates state-specific work to it
- state interface declares state-specific methods
- concrete states implement state-specific behaviour; may store backreference to context to trigger transitions
when to use
- when you have an object that behaves differently depending on its current state
- when you have a lot of duplicate code across similar states and conditional transitions
example
interface State {
void play(AudioPlayer player);
void lock(AudioPlayer player);
}
class LockedState implements State {
public void play(AudioPlayer p) { /* do nothing, locked */ }
public void lock(AudioPlayer p) { p.setState(new ReadyState()); }
}
class ReadyState implements State {
public void play(AudioPlayer p) {
p.startPlayback();
p.setState(new PlayingState());
}
public void lock(AudioPlayer p) { p.setState(new LockedState()); }
}
class AudioPlayer {
private State state = new ReadyState();
public void setState(State s) { this.state = s; }
public void play() { state.play(this); }
public void lock() { state.lock(this); }
}
relations with other patterns
- Strategy changes behaviour by choice; State changes behaviour when internal state changes
- State is an extension of Strategy: both are based on composition
- State can be considered as a way to implement a state machine using classes
Strategy
lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.
structure
- context maintains a reference to one of the concrete strategies; doesn’t know the concrete class
- strategy interface is common to all concrete strategies
- concrete strategies implement different variations of an algorithm
- client creates a specific strategy object and passes it to the context
when to use
- when you want to use different variants of an algorithm within an object and switch between them at runtime
- when you have many similar classes that only differ in how they execute some behaviour
- to isolate business logic from implementation details of algorithms
example
interface RouteStrategy {
Route buildRoute(Point a, Point b);
}
class WalkingStrategy implements RouteStrategy {
public Route buildRoute(Point a, Point b) {
return new Route(/* walking directions */);
}
}
class DrivingStrategy implements RouteStrategy {
public Route buildRoute(Point a, Point b) {
return new Route(/* driving directions */);
}
}
class Navigator {
private RouteStrategy strategy;
public void setStrategy(RouteStrategy s) { this.strategy = s; }
public Route buildRoute(Point a, Point b) {
return strategy.buildRoute(a, b);
}
}
relations with other patterns
- Template Method uses inheritance; Strategy uses composition
- Strategy changes the guts of an object; Decorator changes the skin
- State can be considered an extension of Strategy; states may know about each other and trigger transitions
Visitor
lets you separate algorithms from the objects on which they operate.
structure
- visitor interface declares a set of visiting methods for concrete element classes
- concrete visitors implement several versions of the same behaviours
- element interface declares an
acceptmethod that takes a visitor - concrete elements implement
accept; the goal is to redirect the call to the proper visitor method
when to use
- when you need to perform an operation on all elements of a complex object structure (e.g., object tree)
- to clean up business logic of auxiliary behaviours
- when a behaviour makes sense only in some classes of a class hierarchy, but not others
example
interface Shape {
void accept(Visitor v);
}
class Circle implements Shape {
public int radius;
public void accept(Visitor v) { v.visitCircle(this); }
}
class Rectangle implements Shape {
public int width, height;
public void accept(Visitor v) { v.visitRectangle(this); }
}
interface Visitor {
void visitCircle(Circle c);
void visitRectangle(Rectangle r);
}
class AreaCalculator implements Visitor {
public double area;
public void visitCircle(Circle c) {
area += Math.PI * c.radius * c.radius;
}
public void visitRectangle(Rectangle r) {
area += r.width * r.height;
}
}
// usage
List<Shape> shapes = Arrays.asList(new Circle(), new Rectangle());
AreaCalculator calc = new AreaCalculator();
for (Shape s : shapes) s.accept(calc);
System.out.println(calc.area);
double dispatch
visitor uses a technique called double dispatch:
- first dispatch:
element.accept(visitor)— selects the element’s type - second dispatch:
visitor.visit(this)— selects the visitor method for that element
this allows adding new operations without changing element classes.
relations with other patterns
- can use Visitor to execute an operation over an entire Composite tree
- can use along with Iterator to traverse a complex data structure
- related to Interpreter: Visitor can help maintain behaviour in one class
Backlinks (4)
1. another new note /roam/another_new_note/
here are some words
this is an org file. it is in org-roam. as such it should be linky af Measure, Integration & Real Analysis. hectic
I can refer to design patterns: Design Patterns.
2. Measure, Integration & Real Analysis /words/library/books/axler-measure/
(Friedler, Sorelle A. and Scheidegger, Carlos and Venkatasubramanian, Suresh, 2016)
3. Wiki /wiki/
Knowledge is a paradox. The more one understand, the more one realises the vastness of his ignorance.