Home

Week 12

Structural and Behavioral Design Patterns

Software Design and Architecture

Instructor: Baber Sheikh (Visiting Faculty)

12.1 Recap: Week 11 Summary
12.2 Creational Patterns Crux
12.3 Adapter Pattern
12.4 Bridge Pattern
12.5 Decorator Pattern
12.6 Composite Pattern
12.7 Behavioral Patterns Intro
12.8 Command Pattern
12.9 Iterator Pattern
12.10 Observer Pattern
12.11 Final Summary and Crux

Recap: What exactly is a Design Pattern?

One-line idea

A design pattern is a proven, reusable solution to a recurring design problem. It is not ready-made code; it is a smart template of thinking.

What a pattern gives you

  • A name: "Factory", "Builder", "Singleton"
  • A structure: which classes or objects should exist
  • A purpose: what problem it solves
  • A trade-off: what you gain and what complexity it adds

Real-world analogy

Think of patterns like standard engineering tricks. A civil engineer does not reinvent bridges every time. They reuse proven designs and adapt them for the new context.

Important reminder

Pattern = idea. Code = implementation. Same pattern can be written in any language. The syntax changes, but the design idea stays the same.

Recap: The Three GoF Categories

The Gang of Four (GoF) grouped 23 famous patterns into three families. This family tells you what kind of problem the pattern solves.

Category Main Question Simple meaning Examples
Creational How should objects be created? Object birth Factory Method, Builder, Singleton
Structural How should objects/classes be connected? Object assembly Adapter, Bridge, Decorator, Composite
Behavioral How should objects communicate? Object interaction Observer, Strategy, Command

Week 11 focus

We spent the whole week on Creational patterns because before objects can collaborate, we must understand how to create them correctly.

Week 12 transition

Now we move to Structural patterns: once objects exist, how do we combine them in a clean and flexible way?

Recap: Factory Method

Core idea

Create one product through a common creator interface, but let subclasses choose the concrete class.

Problem it solves

  • You need to create objects, but the exact type changes at runtime
  • You want to avoid long if-else or switch chains
  • You want to add new types without touching stable client logic
Factory Method UML recap

One creator hierarchy decides which product object to create

Simple Week 11 example

A NotificationCreator defines the workflow, while EmailNotificationCreator or SMSNotificationCreator decides what object is actually created.

Recap: Abstract Factory

Core idea

Create families of related objects together, so the client gets a full matching set instead of just one product.

Abstract Factory furniture UML recap

One factory creates a matching chair + table family

What is different from Factory Method?

  • Factory Method: usually one product at a time
  • Abstract Factory: multiple related products together
  • Best when consistency matters, like theme families, platform families, or regional product sets

Furniture store analogy

If the customer picks Modern, they should get a ModernChair and a ModernTable, not a modern chair with a Victorian table.

Recap: Builder Pattern

Core idea

Construct a complex object step by step so code becomes readable, flexible, and less error-prone.

Problem it solves

  • Objects have many optional parameters
  • Constructors become huge and confusing
  • Callers start passing lots of null values or parameters in the wrong order
Builder UML recap

Builder stores construction steps and returns the final product only at the end

Week 11 example

HttpRequest.Builder let us set URL, method, headers, body, and timeout one by one, then call build() at the end.

Recap: Prototype Pattern

Core idea

Create a new object by copying an existing one instead of rebuilding everything from scratch.

Prototype UML recap

The client clones a prototype instead of constructing another full object

Best when

  • Object creation is expensive
  • You need many similar objects with small differences
  • You want a quick way to duplicate a known state

Example: clone a red circle, then change only the color to blue.

Recap: Singleton Pattern

Core idea

Ensure there is only one instance of a class and provide a single place to access it.

Good candidates

  • Logger
  • Configuration manager
  • Application-wide shared service

Important: use it carefully because global state can make testing difficult.

Singleton access recap

Clients do not create new objects directly; they all use the same shared instance

Week 11 takeaway

Singleton is useful for true shared control, but it should not become an excuse to make everything global.

The Crux: Creational Patterns at a Glance

Pattern Main question it answers Best mental model Typical example
Factory Method Which concrete class should I create? Subclass chooses the product Email vs SMS notification creator
Abstract Factory How do I create a matching family of products? One factory, many related products Modern vs Victorian furniture
Builder How do I build one complex object cleanly? Step-by-step assembly HttpRequest or SQL query builder
Prototype How do I get another object like this one? Clone an existing template Copy a shape, then modify one property
Singleton How do I guarantee only one instance exists? Single shared access point Logger or configuration manager

One-line memory trick

Factory chooses, Abstract Factory groups, Builder assembles, Prototype copies, Singleton restricts.

What are Structural Design Patterns?

Big idea

Structural design patterns focus on class and object composition. They help us connect small pieces of code to form larger, cleaner, and more flexible structures. The GoF catalogued 7 structural patterns in their 1994 book.

What problem do they solve?

  • Real systems are built from many classes, not just one
  • Existing classes often do not fit together โ€” different interfaces, different vendors
  • We want to add features without rewriting stable old code
  • We want one simple interface over a complicated subsystem
  • We need tree-like hierarchies where parts and wholes behave the same
The 7 GoF Structural Patterns: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy

The 7 GoF structural patterns โ€” we cover the 4 highlighted this week

๐Ÿช‘ Furniture assembly analogy

Creational patterns gave us the individual wood pieces. Structural patterns are the joinery techniques โ€” dovetail joints, mortise and tenon, screws โ€” that connect those pieces into a stable, beautiful table. The same pieces can be joined in different ways depending on the need.

Why critical?

Modern ecosystems are built on structural patterns: java.io streams use Decorator, Swing/AWT use Composite, JDBC drivers use Adapter, and Spring Framework uses Proxy extensively. Understanding these patterns means understanding the system itself.

Why Structural Patterns Matter

Now that we know how to create objects from Week 11, the next question is: how do we organize and connect those objects? That is exactly where structural patterns help.

Need in software How structural patterns help Code example
Old code and new code do not match Adapter makes incompatible interfaces work together Arrays.asList() adapts arrays to List
Abstraction and implementation change independently Bridge separates the two so both evolve on their own JDBC Driver architecture
Features should be added without modifying the original class Decorator wraps objects and adds behavior dynamically BufferedReader(new FileReader(f))
We need tree-like part-whole structures Composite lets us treat single objects and groups uniformly Swing JPanel containing JButtons

Without structural patterns

  • Classes become tightly coupled โ€” changing one breaks another
  • Adding features means rewriting existing tested code
  • Client code is filled with type-checking logic
  • Integration with third-party libraries becomes a nightmare

With structural patterns

  • Objects are loosely coupled through interfaces
  • New features are added by wrapping, not modifying
  • Client code stays simple and unaware of internal complexity
  • Integration is clean through adapters and bridges

What to expect next

In the coming slides we study each structural pattern with the same method: real-world analogy โ†’ core components โ†’ UML diagram โ†’ Code example โ†’ applicability & pros/cons.

Introduction to the Adapter Pattern

Core idea

The Adapter Pattern makes incompatible interfaces work together without changing their existing code. It acts like a translator between what the client expects and what an old or third-party class already provides.

๐Ÿ”Œ The international travel plug

You land in London with your Pakistani/US charger. The wall has a UK socket. Your charger works perfectly โ€” it is not broken. The socket works perfectly โ€” it is not broken. They just do not match.

You buy a travel adapter from the airport shop. It does not change your charger. It does not change the wall. It sits in between and translates one shape into another.

In software terms

  • Old code and new code expose different method names
  • Third-party libraries may not match your system's interface
  • You want reuse without rewriting stable existing classes
  • You are integrating a legacy system into a modern app
Travel plug adapter: European plug connects to US socket via adapter

The adapter sits between incompatible interfaces without changing either side

Core Components of the Adapter Pattern

1. Target Interface

This is the interface the client expects to use.

In our example: USPlug

2. Adaptee

This is the existing class that already works, but has the wrong interface.

In our example: EuropeanPlug

3. Adapter

This class wraps the adaptee and converts its behavior into the target interface.

In our example: PlugAdapter

Client point of view

The client only sees USPlug. It does not need to know that a European plug is hidden inside the adapter.

How the Adapter Works

Client

Calls the interface it understands:

connectUSSocket()

Adapter

Receives the client call and translates it into the adaptee's language.

Acts like a translator

Adaptee

Runs the existing method it already has:

connectEuropeanSocket()

Why this is powerful

We did not change the old class. We simply added a small wrapper around it. That means we can integrate legacy or third-party code safely.

Adapter Pattern: UML Class Diagram

The formal UML diagram shows how the four participants connect. Study each arrow carefully.

Adapter Pattern UML: Client โ†’ Target โ† Adapter โ†’ Adaptee

Adapter implements Target and delegates to Adaptee internally

UML โ†’ Plug example mapping

UML RolePlug Example
TargetUSPlug interface
AdapterPlugAdapter class
AdapteeEuropeanPlug class
ClientMain class

Key UML arrows

  • Dashed arrow (implements): Adapter implements Target interface
  • Diamond arrow (composition): Adapter holds a reference to Adaptee
  • Simple arrow (uses): Client depends on Target only

Code example: Adaptee and Target Interface

First we define the old class that already exists, and the new interface that the client expects.

Adaptee: existing European plug
public class EuropeanPlug {
    public void connectEuropeanSocket() {
        System.out.println("European plug connected.");
    }
}
Target: interface expected by the client
public interface USPlug {
    void connectUSSocket();
}

Meaning

EuropeanPlug already works, but its method name and expected socket type do not match what the U.S. client code wants.

Code example: The Adapter Class

The adapter implements the target interface and internally holds the adaptee object.

public class PlugAdapter implements USPlug {
    private EuropeanPlug europeanPlug;

    public PlugAdapter(EuropeanPlug europeanPlug) {
        this.europeanPlug = europeanPlug;
    }

    @Override
    public void connectUSSocket() {
        System.out.println("Adapter converts U.S. socket request...");
        europeanPlug.connectEuropeanSocket();
    }
}

Important observation

The adapter does not replace the old class. It wraps it and forwards the call in a compatible way.

Complete Code example

Client code using the adapter
public class Main {
    public static void main(String[] args) {
        EuropeanPlug europeanPlug = new EuropeanPlug();
        USPlug adapter = new PlugAdapter(europeanPlug);

        adapter.connectUSSocket();
    }
}

Console output

Adapter converts U.S. socket request...
European plug connected.

What the client sees

The client believes it is talking to a USPlug. It does not need to care that a EuropeanPlug is inside.

Adapter Pattern: Applicability, Pros & Cons

When to use

  • Two classes should work together, but their interfaces are incompatible
  • Integrating legacy code into a modern system
  • Using a third-party library that does not match your interface
  • You want reuse without modifying trusted existing classes

โœ… Advantages

  • Single Responsibility: separates interface conversion from business logic
  • Open/Closed: add new adapters without changing existing code
  • Improves reusability of old or third-party code
  • Keeps client code clean and unaware of adaptee

โš ๏ธ Disadvantages

  • Adds one extra layer of classes (complexity overhead)
  • Too many adapters can make the design harder to follow
  • If overused, simple mismatches turn into unnecessary wrappers

๐Ÿ”ง Real-world uses

  • Arrays.asList() โ€” adapts an array to a List interface
  • InputStreamReader โ€” adapts byte streams to character streams
  • Collections.enumeration() โ€” adapts Collection to Enumeration

โŒ Common student mistakes

  • Modifying the adaptee class instead of wrapping it
  • Confusing Adapter with Decorator โ€” Adapter changes the interface, Decorator adds behavior
  • Creating an adapter when a simple method rename would do

Additional Example: Adapter Pattern

Memory Card Reader Analogy

You have a MicroSD card from your phone, but your laptop only has a standard USB port. You cannot plug the MicroSD card directly into the USB port.

How the Adapter solves this

You use a USB Card Reader. The Card Reader serves as the Adapter. It implements the USB interface your laptop (the Client) expects, and translates the data to/from the MicroSD card (the Adaptee).

Legacy Systems

A company has a legacy XMLParser class but the new system expects a JSONParser interface. Create an XMLToJSONAdapter that implements JSONParser and internally delegates to XMLParser. The legacy class stays untouched, and the new system gets the JSON interface it expects.

Introduction to the Bridge Pattern

Core idea

The Bridge Pattern separates what something does (abstraction) from how it does it (implementation), so both can change independently without affecting each other.

๐ŸŽฎ Remote control analogy

Think of a TV remote control. The remote (abstraction) has buttons like power, volume up, channel change. The device it controls (implementation) can be a TV, a Radio, or a Smart Speaker.

A basic remote has simple buttons. An advanced remote adds mute and favorites. Both remote types can work with any device. The remote and the device evolve independently.

The problem it solves

  • Without Bridge, you would need: BasicTV, BasicRadio, AdvancedTV, AdvancedRadioโ€ฆ โ€” a class explosion
  • Adding one new remote type means creating N new classes (one per device)
  • Adding one new device means creating M new classes (one per remote)
  • Bridge reduces Mร—N classes to just M + N
Bridge Pattern: Remote controls (Abstraction) connected to Devices (Implementation) via a bridge

Any remote can control any device โ€” they are connected by a bridge, not by inheritance

Core Components of the Bridge Pattern

1. Abstraction

The high-level control layer that the client uses. It holds a reference to an Implementor.

In our example: RemoteControl

2. Refined Abstraction

A specialized version of the abstraction that extends base functionality.

In our example: AdvancedRemote

3. Implementor

The interface that defines what the implementation can do.

In our example: Device

4. Concrete Implementors

The actual platform-specific classes: TV and Radio. Each provides its own way of turning on/off and changing volume.

The "bridge" is the reference

The Abstraction does not inherit from the Implementor. Instead, it holds a reference to it. This composition-over-inheritance approach is the actual "bridge" connecting the two hierarchies.

Bridge Pattern: UML Class Diagram

Bridge Pattern UML: Abstraction holds Implementor, both have subclass hierarchies

Two parallel hierarchies connected by composition, not inheritance

Key insight from the UML

  • The left side (Abstraction) defines what operations exist
  • The right side (Implementor) defines how those operations work
  • The diamond arrow (composition) is the bridge itself
  • Both sides can grow independently without affecting each other

Adapter vs Bridge โ€” important difference

Adapter is applied after the design โ€” to fix an incompatibility. Bridge is designed from the start โ€” to prevent class explosion and keep hierarchies separate.

Code example: Implementor and Concrete Implementors

First, we define the implementation interface and two concrete devices.

Implementor: Device interface
public interface Device {
    void turnOn();
    void turnOff();
    void setVolume(int volume);
}
Concrete Implementor: TV
public class TV implements Device {
    @Override
    public void turnOn() {
        System.out.println("TV is ON");
    }
    @Override
    public void turnOff() {
        System.out.println("TV is OFF");
    }
    @Override
    public void setVolume(int volume) {
        System.out.println("TV volume: " + volume);
    }
}
Concrete Implementor: Radio
public class Radio implements Device {
    @Override
    public void turnOn() {
        System.out.println("Radio is ON");
    }
    @Override
    public void turnOff() {
        System.out.println("Radio is OFF");
    }
    @Override
    public void setVolume(int volume) {
        System.out.println("Radio volume: " + volume);
    }
}

Code example: Abstraction, Refined Abstraction & Client

Abstraction: RemoteControl
public class RemoteControl {
    protected Device device;    // โ† The "bridge"

    public RemoteControl(Device device) {
        this.device = device;
    }

    public void togglePower() {
        device.turnOn();
    }

    public void volumeUp() {
        device.setVolume(10);
    }
}
Refined Abstraction: AdvancedRemote
public class AdvancedRemote extends RemoteControl {
    public AdvancedRemote(Device device) {
        super(device);
    }

    public void mute() {
        device.setVolume(0);
        System.out.println("Device is muted.");
    }
}

Client code

RemoteControl remote = new AdvancedRemote(new TV());
remote.togglePower();    // TV is ON
remote.volumeUp();       // TV volume: 10
((AdvancedRemote) remote).mute(); // TV volume: 0

Notice the flexibility

Swap new TV() with new Radio() and everything works โ€” zero code changes in the remote classes. That is the power of Bridge.

Bridge Pattern: Applicability, Pros & Cons

When to use

  • When you see a class explosion from combining two concepts (e.g., platform ร— feature)
  • When abstraction and implementation should vary independently
  • When you want to switch implementations at runtime
  • When you need to share an implementation among multiple abstractions

โœ… Advantages

  • Platform independence: client code works with abstractions only
  • Open/Closed: add new abstractions and implementations independently
  • Reduces class explosion from Mร—N to M+N
  • Hides implementation details from the client

โš ๏ธ Disadvantages

  • Adds complexity with extra abstraction layers
  • Can be over-engineering for simple scenarios
  • Harder to understand for beginners compared to straightforward inheritance

๐Ÿ”ง Real-world uses

  • JDBC: DriverManager (abstraction) bridges to Driver implementations (MySQL, PostgreSQL, Oracle)
  • AWT/Swing: platform-independent GUI classes bridge to native OS toolkit implementations
  • Logging frameworks: SLF4J (abstraction) bridges to Log4j, Logback (implementations)

Introduction to the Decorator Pattern

Core idea

The Decorator Pattern lets you add new behavior to objects dynamically by wrapping them in special wrapper objects. It is an alternative to subclassing for extending functionality.

โ˜• Coffee shop analogy

You order a simple coffee (Rs. 500). Then you add milk (+Rs. 200). Then whipped cream (+Rs. 200). Each addition wraps the previous order and adds cost and description โ€” but the original coffee object is never modified.

Each topping is a decorator: it adds behavior (extra cost, extra name) without changing the original component.

The subclassing problem

  • Without Decorator: MilkCoffee, WhippedCreamCoffee, MilkWhippedCreamCoffeeโ€ฆ โ€” class explosion!
  • Every combination needs its own class
  • Decorator solves this by stacking wrappers at runtime
Decorator Pattern: Simple coffee โ†’ +Milk โ†’ +Whipped Cream, each layer adds cost

Each decorator wraps the previous one, adding behavior layer by layer

Core Components of the Decorator Pattern

1. Component Interface

Defines the common interface for both original objects and decorators.

In our example: Coffee

2. Concrete Component

The original object being wrapped. Has basic behavior.

In our example: SimpleCoffee

3. Decorator (Abstract)

Implements Component and holds a reference to another Component.

In our example: CoffeeDecorator

4. Concrete Decorators

MilkDecorator and WhippedCreamDecorator โ€” each adds its specific behavior (extra cost, extra description) while delegating base behavior to the wrapped component.

Key rule: same interface

Both the original object and all decorators implement the same interface. This means the client cannot tell whether it is talking to a plain coffee or a triple-decorated coffee โ€” and that is the point!

Decorator Pattern: UML Class Diagram

Decorator Pattern UML: Component interface, ConcreteComponent, Decorator holding Component, ConcreteDecorators

The Decorator both implements and holds a Component โ€” it IS-A and HAS-A Component

Key UML insight

  • The Decorator class has a dual relationship with Component
  • It implements Component (IS-A) โ€” so the client sees the same interface
  • It holds a Component (HAS-A) โ€” so it can delegate and add behavior
  • This recursive structure allows infinite stacking

Decorator vs Adapter โ€” key difference

Adapter changes the interface of an object without adding behavior. Decorator keeps the same interface but adds new behavior. Both use wrapping, but for different purposes.

Code example: Component and Concrete Component

Component interface
public interface Coffee {
    String getDescription();
    double getCost();
}
Concrete Component: SimpleCoffee
public class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double getCost() {
        return 500.0;
    }
}

This is our base object

SimpleCoffee works perfectly on its own. A customer can order it as-is. But we want to add toppings without modifying this class.

Code example: Abstract Decorator and Concrete Decorators

Abstract Decorator
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;   // โ† wraps another Coffee

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
}
MilkDecorator
public class MilkDecorator
        extends CoffeeDecorator {

    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee
            .getDescription() + ", Milk";
    }

    @Override
    public double getCost() {
        return decoratedCoffee
            .getCost() + 200.0;
    }
}
WhippedCreamDecorator
public class WhippedCreamDecorator
        extends CoffeeDecorator {

    public WhippedCreamDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee
            .getDescription()
            + ", Whipped Cream";
    }

    @Override
    public double getCost() {
        return decoratedCoffee
            .getCost() + 200.0;
    }
}

Code example: Client Code โ€” Stacking Decorators

Client code
public class Main {
    public static void main(String[] args) {
        // Start with a simple coffee
        Coffee coffee = new SimpleCoffee();
        System.out.println(coffee.getDescription() + " โ†’ Rs. " + coffee.getCost());

        // Wrap with milk
        coffee = new MilkDecorator(coffee);
        System.out.println(coffee.getDescription() + " โ†’ Rs. " + coffee.getCost());

        // Wrap with whipped cream
        coffee = new WhippedCreamDecorator(coffee);
        System.out.println(coffee.getDescription() + " โ†’ Rs. " + coffee.getCost());
    }
}

Console output

Simple Coffee โ†’ Rs. 500.0
Simple Coffee, Milk โ†’ Rs. 700.0
Simple Coffee, Milk, Whipped Cream โ†’ Rs. 900.0

The magic of stacking

Each decorator wraps the previous result. You can stack as many as you want in any order. Adding a new topping means creating one new class โ€” not rewriting the existing ones.

Important observation

At every stage, the variable coffee still has type Coffee. The client never needs to know how many decorators are wrapped inside. It just calls getCost() and gets the right answer.

Decorator Pattern: Applicability, Pros & Cons

When to use

  • When you need to add behavior dynamically at runtime
  • When subclassing would create a class explosion
  • When you want to combine behaviors in any order
  • When original classes should remain untouched

โœ… Advantages

  • More flexible than inheritance
  • Behavior can be added/removed at runtime
  • Follows Single Responsibility โ€” each decorator does one thing
  • Follows Open/Closed โ€” extend without modifying

โš ๏ธ Disadvantages

  • Many small wrapper objects can be confusing to debug
  • Order of wrapping may matter and cause subtle bugs
  • Removing a specific decorator from the middle is difficult

๐Ÿ”ง Real-world I/O โ€” the best Decorator example

Many I/O libraries are built on the Decorator Pattern:

// Each wrapper adds a new capability โ€” same pattern as our coffee example!
BufferedReader reader = new BufferedReader(      // adds buffering
    new InputStreamReader(                       // adapts bytes โ†’ chars
        new FileInputStream("data.txt")          // reads raw bytes
    )
);

FileInputStream = simple coffee. InputStreamReader = first decorator. BufferedReader = second decorator. Same concept!

Introduction to the Composite Pattern

Core idea

The Composite Pattern lets us build tree-like structures so that we can treat individual objects and groups of objects in the same way.

Where this appears

  • File systems: file and folder
  • GUI frameworks: button and panel
  • Organization charts: employee and team
  • Menus: single item and submenu

Main question

Can I call the same operation on one object and on a whole group of objects? Composite says yes, if both follow the same component interface.

Composite Pattern: File system tree with folders (Composite) and files (Leaf)

A folder can contain files or other folders, forming a tree structure

Understanding Composite with an Organization Tree

Roles in the hierarchy

  • Developer = leaf node, does individual work
  • Manager = composite node, manages many employees
  • Director = higher-level composite, manages managers

Key observation

A manager may contain developers, another manager, or both. That is why the structure becomes a tree.

Director
โ”œโ”€โ”€ Manager A
โ”‚   โ”œโ”€โ”€ Developer 1
โ”‚   โ””โ”€โ”€ Developer 2
โ”œโ”€โ”€ Manager B
โ”‚   โ”œโ”€โ”€ Developer 3
โ”‚   โ””โ”€โ”€ Manager C
โ”‚       โ”œโ”€โ”€ Developer 4
โ”‚       โ””โ”€โ”€ Developer 5

Composite insight

The whole company is made of smaller parts, and those parts may themselves contain smaller parts.

Composite Pattern: UML Class Diagram

Composite Pattern UML: Component, Leaf, Composite

Composite both implements Component and holds a list of Components

The Key Elements

  • Component: The shared interface for both simple and complex objects.
  • Leaf: Individual objects that do the actual work. They have no children.
  • Composite: Complex objects that hold children (Leaves or other Composites) and delegate work to them.

Why is it recursive?

Notice the line from Composite back to Component. Since a Composite holds a list of Components, and a Composite is a Component, it can hold other Composites inside itself indefinitely!

How Function Calls Propagate in Composite

Client

Calls one common method:

showDetails()

Composite Node

The manager/director receives the call and forwards it to every child employee.

Leaf Nodes

Each developer prints its own details when the call reaches it.

Why this is useful

The client does not need separate logic for one employee vs a whole team. It simply calls the same method on the top object.

Code example: Component and Leaf

We start with a common component interface, then create the leaf class for individual employees.

Component interface
public interface Employee {
    void showDetails();
}
Leaf node: Developer
public class Developer implements Employee {
    private String name;
    private String position;

    public Developer(String name, String position) {
        this.name = name;
        this.position = position;
    }

    @Override
    public void showDetails() {
        System.out.println(name + " works as " + position + ".");
    }
}

Code example: Composite Node (Manager)

The composite also implements Employee, but instead of printing one line, it stores a list of child employees and delegates the call.

import java.util.ArrayList;
import java.util.List;

public class Manager implements Employee {
    private List<Employee> employees = new ArrayList<>();

    public void add(Employee employee) {
        employees.add(employee);
    }

    public void remove(Employee employee) {
        employees.remove(employee);
    }

    @Override
    public void showDetails() {
        for (Employee employee : employees) {
            employee.showDetails();
        }
    }
}

Important point

Manager and Developer both implement Employee, so the client can treat both uniformly.

Complete Code example

Client code
public class Main {
    public static void main(String[] args) {
        Developer dev1 = new Developer("Ali", "Backend Developer");
        Developer dev2 = new Developer("Sara", "Frontend Developer");
        Developer dev3 = new Developer("Hamza", "Mobile Developer");

        Manager managerA = new Manager();
        managerA.add(dev1);
        managerA.add(dev2);

        Manager director = new Manager();
        director.add(managerA);
        director.add(dev3);

        director.showDetails();
    }
}

What happens?

The call starts at director.showDetails(), then automatically propagates to managerA and finally to each developer.

Client simplicity

The client never needs separate code for director, manager, and developer. It works with the common Employee interface only.

Composite Pattern: Applicability, Pros & Cons

Applicability

  • When objects naturally form a part-whole hierarchy
  • When you want to treat single objects and groups in the same way
  • When recursive tree processing appears in the design
  • When you need scalable hierarchical models like menus, folders, or company structures

Advantages

  • Simplifies client code through a common interface
  • Makes hierarchical structures easier to build and extend
  • Supports recursive processing naturally

Disadvantages

  • Can make the design too general if every node supports every operation
  • Sometimes it is hard to restrict which objects can contain children
  • Large trees may become harder to debug if recursion is deep

Introduction to Behavioral Design Patterns

Big idea

Behavioral design patterns focus on how objects communicate and cooperate. If structural patterns tell us how to connect objects, behavioral patterns tell us how those connected objects should act together.

Main question they answer

  • How should one object request work from another?
  • How should responsibilities be divided?
  • How can we reduce coupling during communication?
  • How can actions be stored, queued, undone, or reused?

Simple analogy

Think of a company. Structural patterns help organize the departments. Behavioral patterns decide how instructions, messages, and decisions move between employees and managers.

Why Behavioral Patterns Matter

Software systems are full of interactions: button clicks, menu actions, events, service calls, job queues, schedulers, and message passing. Behavioral patterns help make these interactions clear, flexible, and maintainable.

Need in software Behavioral help
Encapsulate actions as reusable units Command
Choose one algorithm at runtime Strategy
Notify many listeners about one event Observer
Pass requests along a chain Chain of Responsibility

Todayโ€™s focus

We start with Command because it is one of the clearest behavioral patterns: it turns a request into an object.

From Structural to Behavioral Thinking

So far in Week 12

  • Adapter made incompatible interfaces work together
  • Bridge separated abstraction from implementation
  • Decorator added features dynamically
  • Composite organized part-whole hierarchies

Now a new question

Once classes are connected and structured properly, how should actions flow through the system? That is where behavioral patterns begin.

Command in one sentence

Command Pattern wraps a request inside an object so that the sender does not need to know the details of the receiver.

Introduction to the Command Pattern

Core idea

The Command Pattern encapsulates a request as an object. This decouples the sender of the request from the receiver that performs the work.

In simple words

Instead of saying โ€œremote, directly turn on the TV,โ€ we create a TurnOnCommand object and hand it to the remote. The remote just executes the command.

Why useful?

Once a request becomes an object, it can be stored, queued, logged, repeated, or undone later.

Command Pattern: Remote Control Analogy

Remote control sends commands to lights, TV, speakers and AC

The remote does not need device details; it just triggers a command

Mapping the analogy

  • User gives the request
  • Remote control is the invoker
  • Command object wraps the action
  • TV / Stereo / AC / Lights are receivers

Key point

The remote does not know how a TV changes channels or how a stereo adjusts volume. It only knows how to call execute() on a command object.

Core Components of the Command Pattern

1. Command

Common interface with one method, usually execute().

2. Concrete Commands

Specific actions like TurnOnCommand or ChangeChannelCommand.

3. Receiver

The real device or object that performs the work, such as TV or Stereo.

4. Invoker

The object that triggers the command. In our example, it is RemoteControl.

Decoupling result

The invoker knows only the command interface, not the receiverโ€™s internal methods.

Command Pattern: Class Diagram

Command pattern class diagram with command interface, concrete commands, remote control invoker and device receiver

Invoker holds a command, commands talk to receivers

Reading the UML

  • Command interface defines execute()
  • Concrete commands connect one action to one receiver
  • RemoteControl stores a command and calls it
  • Device classes do the actual work

Code example: Command Interface and Concrete Commands

Command interface
public interface Command {
    void execute();
}
Concrete commands
class TurnOnCommand implements Command {
    private Device device;

    public TurnOnCommand(Device device) {
        this.device = device;
    }

    @Override
    public void execute() {
        device.turnOn();
    }
}

class TurnOffCommand implements Command {
    private Device device;

    public TurnOffCommand(Device device) {
        this.device = device;
    }

    @Override
    public void execute() {
        device.turnOff();
    }
}

Code example: Receivers and Invoker

Receivers
interface Device {
    void turnOn();
    void turnOff();
}

class TV implements Device {
    public void turnOn() { System.out.println("TV is now on"); }
    public void turnOff() { System.out.println("TV is now off"); }
    public void changeChannel() { System.out.println("Channel changed"); }
}

class Stereo implements Device {
    public void turnOn() { System.out.println("Stereo is now on"); }
    public void turnOff() { System.out.println("Stereo is now off"); }
    public void adjustVolume() { System.out.println("Volume adjusted"); }
}
Invoker
class RemoteControl {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void pressButton() {
        if (command != null) {
            command.execute();
        } else {
            System.out.println("No command assigned");
        }
    }
}

Complete Code example, Uses, Pros & Cons

Client code
public class Main {
    public static void main(String[] args) {
        TV tv = new TV();
        Stereo stereo = new Stereo();

        Command turnOnTV = new TurnOnCommand(tv);
        Command turnOffTV = new TurnOffCommand(tv);

        RemoteControl remote = new RemoteControl();

        remote.setCommand(turnOnTV);
        remote.pressButton();

        remote.setCommand(turnOffTV);
        remote.pressButton();
    }
}

Common uses

  • GUI buttons and menu actions
  • Undo/redo in editors
  • Job queues and scheduled tasks
  • Macro commands and command history

Pros & cons

  • Pros: decouples sender/receiver, easy to add new commands, supports queuing and logging
  • Cons: increases number of classes, may feel heavy for tiny applications

Introduction to the Iterator Pattern

Core idea

The Iterator Pattern provides a way to access the elements of a collection one by one without exposing how that collection is stored internally.

Why useful?

  • The client should not care whether data is stored in an array, list, tree, or another structure
  • Traversal logic is moved into a separate iterator object
  • The collection can change internally without breaking client code
Iterator pattern: TV remote next channel

Pressing next on a remote abstracts the collection of channels

Core Components of the Iterator Pattern

Iterator Pattern UML diagram

The client interacts only with the Iterator and Aggregate interfaces

Key Parts

  • Iterator Interface: Defines traversal (e.g. hasNext(), next()).
  • Concrete Iterator: Implements traversal and keeps track of position.
  • Aggregate: The collection providing a createIterator() method.
  • Concrete Aggregate: The real list/tree holding the elements.

How Iterator Works: Employee Collection Example

Suppose a company stores employees in a collection, and we want to calculate the total salary by visiting each employee one by one.

Problem

  • Employees may be stored in different collection types
  • Client code should not depend on the collection structure
  • We still need a simple sequential traversal

Iterator solution

The Company gives us an iterator. The iterator keeps track of the current position and exposes only hasNext() and next().

Company

Provides an iterator

Iterator

Moves through employees one by one

Client

Reads salaries and calculates the total

Code example: Iterator and Aggregate Interfaces

We begin by defining two small interfaces: one for traversal and one for creating iterators.

Iterator interface
interface Iterator<T> {
    boolean hasNext();
    T next();
}
Aggregate interface
interface Aggregate<T> {
    Iterator<T> createIterator();
}

Meaning

Any collection that implements Aggregate<T> promises that it can create an iterator for its elements.

Code example: Concrete Iterator and Company Aggregate

Concrete iterator
class EmployeeIterator implements Iterator<Employee> {
    private int currentIndex = 0;
    private List<Employee> employees;

    public EmployeeIterator(List<Employee> employees) {
        this.employees = employees;
    }

    @Override
    public boolean hasNext() {
        return currentIndex < employees.size();
    }

    @Override
    public Employee next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        return employees.get(currentIndex++);
    }
}
Concrete aggregate
class Company implements Aggregate<Employee> {
    private List<Employee> employees;

    public Company(List<Employee> employees) {
        this.employees = employees;
    }

    @Override
    public Iterator<Employee> createIterator() {
        return new EmployeeIterator(employees);
    }
}

Complete Code example

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

class Employee {
    private String name;
    private double salary;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public double getSalary() {
        return salary;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("Alice", 50000));
        employees.add(new Employee("Bob", 60000));
        employees.add(new Employee("Charlie", 70000));

        Company company = new Company(employees);
        Iterator<Employee> iterator = company.createIterator();

        double totalSalary = 0;
        while (iterator.hasNext()) {
            totalSalary += iterator.next().getSalary();
        }

        System.out.println("Total salary: " + totalSalary);
    }
}

Output

Total salary: 180000.0

Iterator Pattern: Uses, Advantages & Disadvantages

Common uses

  • Traversing lists, arrays, or custom collections
  • Navigation features like next/previous
  • Multiple simultaneous traversals of the same data
  • Accessing elements without exposing implementation details

Advantages

  • Uniform way to access collection elements
  • Hides internal representation
  • Keeps traversal logic separate from the collection itself

Disadvantages

  • Can increase the number of classes
  • May add extra overhead for simple collections
  • Sometimes unnecessary in languages/frameworks with strong built-in iterators

Introduction to the Observer Pattern

Core idea

The Observer Pattern creates a one-to-many relationship between a subject and its observers. When the subject changes, all registered observers are notified automatically.

Why useful?

  • One object changes, many others should update
  • We want automatic synchronization of dependent objects
  • We want communication without tight coupling

Simple analogy

Think of a YouTube channel. When the channel uploads a new video, all subscribers get notified automatically. The channel does not directly call each subscriberโ€™s personal code.

How Observer Works

Subject notifies multiple observers when its state changes

One subject, many observers, automatic notification on change

The flow

  • Observers subscribe to the subject
  • The subject stores a list of observers
  • When state changes, the subject calls notifyObservers()
  • Each observer reacts through its own update() method

Best fit

Great for event-driven systems, weather alerts, stock updates, GUI listeners, and publish-subscribe style applications.

Core Components of the Observer Pattern

Observer pattern class diagram with Subject, Observer, WeatherStation, PhoneDisplay and TVDisplay

Subject manages observers; concrete observers react to updates

Key parts

  • Subject: add/remove observers, notify them
  • Observer: common interface with update()
  • ConcreteSubject: real data source, e.g. WeatherStation
  • ConcreteObserver: real listeners, e.g. PhoneDisplay, TVDisplay

Code example: Subject and Observer Interfaces

Subject interface
public interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}
Observer interface
public interface Observer {
    void update(String weather);
}

Meaning

The subject controls the subscription list, and every observer promises that it knows how to react when an update arrives.

Code example: Concrete Subject (WeatherStation)

import java.util.ArrayList;
import java.util.List;

class WeatherStation implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String weather = "";

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(weather);
        }
    }

    public void setWeather(String newWeather) {
        this.weather = newWeather;
        notifyObservers();
    }
}

Important point

The weather station does not know how each display shows the weather. It only knows how to notify its observers.

Code example: Concrete Observers and Usage

Concrete observers
class PhoneDisplay implements Observer {
    @Override
    public void update(String weather) {
        System.out.println("Phone Display: Weather updated - " + weather);
    }
}

class TVDisplay implements Observer {
    @Override
    public void update(String weather) {
        System.out.println("TV Display: Weather updated - " + weather);
    }
}
Client code
public class WeatherApp {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();

        Observer phoneDisplay = new PhoneDisplay();
        Observer tvDisplay = new TVDisplay();

        weatherStation.addObserver(phoneDisplay);
        weatherStation.addObserver(tvDisplay);

        weatherStation.setWeather("Sunny");
        weatherStation.setWeather("Rainy");
        weatherStation.setWeather("Cloudy");

        weatherStation.removeObserver(tvDisplay);
        weatherStation.setWeather("Windy");
    }
}

Observer Pattern: Uses, Advantages & Disadvantages

Common uses

  • GUI event listeners
  • Social media notifications
  • Stock market and weather apps
  • Publish-subscribe systems

Advantages

  • Loose coupling between subject and observers
  • Observers can be added or removed at runtime
  • Ensures automatic and consistent updates

Disadvantages

  • Too many observers may create performance overhead
  • Debugging is harder because communication is indirect
  • Observers may receive updates they do not really need

Week 11 Recap: Creational Patterns

Creational patterns answered one main question: How should objects be created? We studied five important patterns, each solving a different creation problem.

Pattern Main use Simple memory line
Factory Method Create one product, but let subclasses choose the exact type Subclass decides what to create
Abstract Factory Create families of related objects One factory creates a matching set
Builder Construct complex objects step by step Build one object gradually
Prototype Create by copying an existing object Clone instead of rebuild
Singleton Ensure only one shared instance exists Exactly one object globally

Crux of Week 11

Creational patterns are all about object birth, but each one solves a different birth problem: choice, families, step-by-step assembly, cloning, or uniqueness.

Week 12 Recap: Structural Patterns

Structural patterns answered the next question: Once objects exist, how should we connect them?

Patterns studied

  • Adapter โ€” make incompatible interfaces work together
  • Bridge โ€” separate abstraction from implementation
  • Decorator โ€” add responsibilities dynamically
  • Composite โ€” build part-whole tree structures

Big picture

If creational patterns are about creating parts, structural patterns are about joining those parts together in flexible ways.

Pattern Main question
Adapter How can two incompatible classes work together?
Bridge How can abstraction and implementation vary independently?
Decorator How can we add features without changing the original class?
Composite How can we treat a whole group the same way as a single object?

Behavioral Patterns Recap

Behavioral patterns focus on communication and responsibility. They answer: How should objects interact?

Patterns studied

  • Command โ€” turn a request into an object
  • Iterator โ€” access collection elements one by one
  • Observer โ€” notify many dependents automatically

Shared theme

Behavioral patterns reduce hard-coded communication. They help actions, traversals, and notifications happen in a cleaner and more decoupled way.

Pattern Simple memory line
Command Package an action as an object
Iterator Walk through a collection without exposing its internals
Observer One object changes, many others update automatically

The Crux: All Patterns Studied So Far

Category Pattern What it really does
Creational Factory Method Chooses one concrete product through subclasses
Abstract Factory Creates matching families of products
Builder Builds one complex object step by step
Prototype Copies an existing object
Singleton Restricts creation to one shared instance
Structural Adapter Makes incompatible interfaces work together
Bridge Separates abstraction from implementation
Decorator Adds behavior dynamically through wrapping
Composite Builds tree-like part-whole structures
Behavioral Command Turns a request into an object
Iterator Traverses a collection safely and uniformly
Observer Synchronizes many dependents automatically

Conclusion

Final takeaway

Design patterns are not about memorizing fancy names. They are about recognizing recurring design problems and applying proven solutions with the right trade-offs.

What you should now be able to do

  • Identify whether a problem is about creation, structure, or behavior
  • Choose a suitable pattern based on the problem shape
  • Understand the main classes/interfaces behind each pattern
  • Read and explain implementations of common patterns

One last memory aid

Creational = how objects are born.
Structural = how objects are joined.
Behavioral = how objects talk.

Most important rule

Do not use a pattern just because it sounds smart. Use it only when it solves a real software design problem cleanly.