AMSS Lecture 11: Design Patterns (II)

Traian-Florin Șerbănuță

2025

Agenda

  1. Recall
    1. What are Design Patterns?
    2. Classification of Patterns
  2. Patterns
    1. Visitor
    2. Mediator
    3. Bridge
    4. Adapter
    5. Decorator
    6. Proxy
    7. Composite
  3. Wrap-up

What Are Design Patterns?

Definition

Reusable solutions to common software design problems.

Origin

Popularized by the “Gang of Four” (Gamma, Helm, Johnson, Vlissides, 1994).

Purpose

Example

Instead of reinventing how to traverse a collection, we apply the Iterator pattern.

Pattern Classification

Design patterns are typically grouped into three main categories:

Category Description Example Patterns
Creational How objects are created Builder, Factory, Singleton
Structural How classes and objects are composed Adapter, Bridge, Composite, Decorator, Proxy
Behavioral How objects interact and communicate Visitor, Mediator, State

Visitor Pattern

Type

Behavioral pattern

Intent

Problem Solved

Solution

Visitor Pattern - key ideas

Elements

Objects you want to operate on (e.g., nodes in an AST, shapes in a graphics editor).

Each element implements an accept(visitor) method.
Visitor

An object that implements different operations for each concrete element type.

Example methods: visitCircle(circle), visitSquare(square), visitTriangle(triangle)
Double Dispatch

A crucial mechanism:

The element calls visitor.visitXYZ(this)

The visitor chooses the correct method based on the element type

This avoids type-checking or if/instanceof cascades.

Visitor Pattern — concrete example scenario

Problem

. . .

Solution

The Visitor pattern lets you add new operations by creating new Visitor classes, while the shapes themselves remain unchanged and simply “accept” visitors.

Visitor Pattern — UML class diagram

Visitor Pattern — concrete example code

Source file

// Element
interface Shape {
    void accept(Visitor v);
}

// Concrete Elements
class Circle implements Shape {
    double radius = 5;
    public void accept(Visitor v) { v.visit(this); }
}

class Rectangle implements Shape {
    double width = 4, height = 3;
    public void accept(Visitor v) { v.visit(this); }
}

// Visitor
interface Visitor {
    void visit(Circle c);
    void visit(Rectangle r);
}

// Concrete Visitor
class AreaCalculator implements Visitor {
    public void visit(Circle c) {
        System.out.println("Circle area = " + Math.PI * c.radius * c.radius);
    }
    public void visit(Rectangle r) {
        System.out.println("Rectangle area = " + r.width * r.height);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(), new Rectangle() };
        Visitor areaVisitor = new AreaCalculator();

        for (Shape s : shapes) s.accept(areaVisitor);
    }
}

Visitor Pattern - benefits and drawbacks

Benefits

Drawbacks

Visitor Pattern Exercise

Task

Mediator Pattern

Type

Behavioral pattern

Intent

Problem Solved

How to reduce direct dependencies and complex communication between many interacting objects?

Solution

Mediator Pattern — key concepts

Mediator (Interface / Abstract Class)

Defines how components communicate through the mediator.

Concrete Mediator

Implements coordination logic.

Receives events from components and decides how to react.
Colleague Components

Objects that interact only through the mediator.

They notify the mediator when something happens instead of contacting each other directly.

Mediator Pattern — concrete example scenario

Problem

. . .

Solution

Mediator Pattern — UML class diagram

Mediator Pattern — concrete example code

Source file

// Mediator
interface ChatMediator {
    void sendMessage(String msg, User user);
}

// Concrete Mediator
class ChatRoom implements ChatMediator {
    public void sendMessage(String msg, User user) {
        System.out.println(user.getName() + ": " + msg);
    }
}

// Colleague
abstract class User {
    protected ChatMediator mediator;
    protected String name;
    User(String name, ChatMediator mediator) {
        this.name = name; this.mediator = mediator;
    }
    String getName() { return name; }
    void send(String msg) { mediator.sendMessage(msg, this); }
}

// Concrete Colleague
class ChatUser extends User {
    ChatUser(String name, ChatMediator mediator) { super(name, mediator); }
}

// Usage
public class Main {
    public static void main(String[] args) {
        ChatMediator room = new ChatRoom();
        User alice = new ChatUser("Alice", room);
        alice.send("Hello everyone!");
    }
}

Mediator Pattern — Benefits & Tradeoffs

Benefits

Drawbacks

Mediator Pattern Exercise

Task

Bridge Pattern

Type

Structural pattern

Intent

Problem Solved

How to avoid a class explosion caused by combining multiple abstractions with multiple implementations?

Solution

Bridge Pattern — key components

Abstraction

Defines high-level control logic.

Maintains a reference to an implementation.

Refined Abstraction

Specialized abstractions that extend the base abstraction.

Implementor (Interface or Abstract Class)

Defines low-level platform-specific operations.

Concrete Implementor

Actual implementation details.

Bridge Pattern — concrete example scenario

Problem

. . .

Solution

Bridge Pattern — concrete example diagram

Bridge Pattern — concrete example code

Source file

// Implementor
interface Device {
    void turnOn();
    void turnOff();
}

// Concrete Implementors
class TV implements Device {
    public void turnOn() { System.out.println("TV ON"); }
    public void turnOff() { System.out.println("TV OFF"); }
}

// Abstraction
abstract class Remote {
    protected Device device;
    Remote(Device d) { this.device = d; }
    abstract void toggle();
}

// Refined Abstraction
class SimpleRemote extends Remote {
    private boolean on = false;
    SimpleRemote(Device d) { super(d); }

    void toggle() {
        if (on) device.turnOff();
        else device.turnOn();
        on = !on;
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Remote remote = new SimpleRemote(new TV());
        remote.toggle();
        remote.toggle();
    }
}

Bridge Pattern — benefits and drawbacks

️ Benefits

Drawbacks

Bridge Pattern Exercise

Task

You are building a drawing tool with two dimensions of variability:

Explain how to apply the Bridge pattern so all shapes can be rendered with any rendering method without class explosion.

Goal

Identify separate dimensions of change and design a usable abstraction/ implementation split.

Adapter Pattern

Type

Structural pattern

Intent

Convert the interface of one class into another interface clients expect.

Problem Solved

How to make incompatible interfaces work together without changing existing code?

Solution

Create an Adapter that wraps an existing class and exposes the desired target interface.

Adapter Pattern — key components

Target

The interface your code expects and uses.

Adaptee

The existing class with an incompatible interface.

Adapter

The wrapper that Implements the Target interface

Internally calls the Adaptee method(s), translating data or behavior

Adapter Pattern — concrete example scenario

Problem

. . .

Solution

The Adapter pattern wraps the incompatible class and exposes the interface the client expects, allowing the two systems to work together seamlessly.

Adapter Pattern — UML class diagram

Adapter Pattern — concrete example code

Source file

// Target interface
interface MediaPlayer {
    void play(String file);
}

// Adaptee
class LegacyPlayer {
    void playMp3(String filename) {
        System.out.println("Playing MP3: " + filename);
    }
}

// Adapter
class MediaAdapter implements MediaPlayer {
    private LegacyPlayer legacy = new LegacyPlayer();
    public void play(String file) { legacy.playMp3(file); }
}

// Usage
public class Main {
    public static void main(String[] args) {
        MediaPlayer player = new MediaAdapter();
        player.play("song.mp3");
    }
}

Adapter Pattern — benefits and drawbacks

Benefits

Drawbacks

Adapter Pattern Exercise

Task

Decorator Pattern

Type

Structural pattern

Intent

Attach additional responsibilities to an object dynamically without modifying its class.

Problem Solved

How to add flexible, combinable features to objects without subclass explosion?

Solution

Wrap objects with decorator classes that implement the same interface and add behavior before/after delegating calls.

Decorator Pattern — key components

Component (interface or abstract class)

Defines the main operations.

Concrete Component

The core object you want to decorate.

Decorator (abstract class)

Wraps a component and delegates calls to it.

Concrete Decorators

Add additional behavior before or after delegating to the wrapped object.

Decorator Pattern — concrete example scenario

Problem

. . .

Solution

Decorator Pattern — UML class diagram

Decorator Pattern — concrete example code

Source file

// Component
interface Beverage {
    String getDescription();
    double cost();
}

// Concrete Component
class Coffee implements Beverage {
    public String getDescription() { return "Coffee"; }
    public double cost() { return 2.0; }
}

// Decorator
abstract class AddOn implements Beverage {
    protected Beverage beverage;
    AddOn(Beverage b) { beverage = b; }
}

// Concrete Decorators
class Milk extends AddOn {
    Milk(Beverage b) { super(b); }
    public String getDescription() { return beverage.getDescription() + ", Milk"; }
    public double cost() { return beverage.cost() + 0.5; }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Beverage coffee = new Milk(new Coffee());
        System.out.println(coffee.getDescription() + " = $" + coffee.cost());
    }
}

Decorator Pattern — benefits and drawbacks

Benefits

Drawbacks

Decorator Pattern Exercise

Task

Proxy Pattern

Type

Structural pattern

Intent

Provide a surrogate or placeholder for another object to control access to it.

Problem Solved

How to manage access to a resource-heavy or remote object (e.g., lazy loading, caching, security)?

Solution

Implement a proxy that implements the same interface as the real subject and controls access before forwarding requests.

Proxy Pattern - key components

Subject (interface)

Defines the operations available to both Proxy and RealSubject.

RealSubject

The actual object that does the real work.

Proxy

Implements the same interface but performs extra steps before/after delegating to RealSubject: Access control, Lazy initialization, Logging / auditing, Remote communication, Caching

Clients cannot tell whether they’re talking to the proxy or the real subject.

Proxy Pattern — concrete example scenario

Problem

. . .

Solution

Proxy Pattern — UML class diagram

Proxy Pattern — concrete example code

Source file

// Subject
interface Database {
    void query(String sql);
}

// Real Subject
class RealDatabase implements Database {
    public RealDatabase() {
        System.out.println("Connecting to database...");
    }
    public void query(String sql) {
        System.out.println("Executing query: " + sql);
    }
}

// Proxy
class DatabaseProxy implements Database {
    private RealDatabase db;

    public void query(String sql) {
        if (db == null) db = new RealDatabase(); // lazy initialization
        db.query(sql);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Database db = new DatabaseProxy();
        db.query("SELECT * FROM users");
    }
}

Proxy Pattern — benefits and drawbacks

Benefits

Drawbacks

Proxy Pattern Exercise

Task

Goal

Identify opportunities for lazy loading, access control, and indirection.

Composite Pattern

Type

Structural pattern

Intent

Compose objects into tree structures to represent part–whole hierarchies.

Problem Solved

How to treat individual objects and groups of objects uniformly?

Solution

Define a common component interface

Composite Pattern — key components

Component (common interface)

Defines operations available to both leaf and composite objects.

Leaf

A simple object with no children.

Composite

A container object that can hold components (both leaves and other composites).

Client

Interacts with components uniformly, without caring if they’re leaves or composites.

Composite Pattern — concrete example scenario

Problem

. . .

Solution

Composite Pattern — UML class diagram

Composite Pattern — concrete example code

Source file

// Component
interface FileSystemNode {
    void show();
}

// Leaf
class FileNode implements FileSystemNode {
    private String name;
    FileNode(String name) { this.name = name; }
    public void show() { System.out.println("File: " + name); }
}

// Composite
class Folder implements FileSystemNode {
    private String name;
    private java.util.List<FileSystemNode> children = new java.util.ArrayList<>();

    Folder(String name) { this.name = name; }

    public void add(FileSystemNode node) { children.add(node); }

    public void show() {
        System.out.println("Folder: " + name);
        for (FileSystemNode child : children) child.show();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Folder root = new Folder("root");
        root.add(new FileNode("file1.txt"));

        Folder sub = new Folder("subfolder");
        sub.add(new FileNode("file2.txt"));
        root.add(sub);

        root.show();
    }
}
# Composite Pattern — benefits and drawbacks
## Benefits
- Treat leaves and composites the same
- Simplifies client code
- Makes tree structures easy to build and manipulate
- Supports recursive operations elegantly
- Encourages flexible, extensible system architecture
## Drawbacks
- Can make type distinctions harder when you do need to handle leaf/composite differently
- Risk of making Composite overly general
- May expose child-management methods even for leaves (depending on design)

Composite Pattern Exercise

Task

Wrap-Up — Key Insights

Visitor

Add operations to existing class hierarchies without modifying them by externalizing behavior.

Mediator

Reduce tangled, many-to-many communication by centralizing interaction logic inside a mediator object.

Bridge

Separate abstraction from implementation; avoid class explosion and allow sides to vary independently.

Adapter

Make incompatible interfaces work together: wrap one interface to match expectations of another.

Decorator

Dynamically add responsibilities or behavior to objects without subclassing or modifying original classes.

Proxy

Control/enhance access to an object (lazy loading, security, caching) without changing the real object.

Composite

Treat individual items and groups uniformly through a shared component interface.

Wrap-up diagram