CSCI 370 - Lecture 13: Decorator Pattern, Builder Pattern

Tuesday, April 1


Overview

Lecture 13 covered two major software design patterns: the Decorator Pattern and the Builder Pattern. Both are important for organizing code in ways that promote flexibility, reusability, and clarity. This lecture focused heavily on practical examples and live coding to reinforce the core ideas.


Decorator Pattern

Core Idea

The Decorator Pattern allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class. It is used to wrap objects so that additional behaviors can be applied at runtime.

Motivation Example: Logging

A logging system was used to demonstrate the decorator pattern. The logger could send logs to:

Rather than hardcoding combinations of these outputs, decorators allow us to chain these logging behaviors flexibly.

Initial Naive Approach

A base interface Logger defines a method:

void log(String message);

Concrete implementations include:

Each class logs in its respective way. A SendLog class might call all three manually:

new LogToFile().log(msg);
new LogToDatabase().log(msg);
new LogToMail().log(msg);

Problems with This Approach

Decorator-Based Solution

Each Logger holds a reference to a “previous” logger and delegates the log() call down the chain:

class LogToFile implements Logger {
    private Logger previousLogger;
    public LogToFile(Logger previous) {
        this.previousLogger = previous;
    }
    public void log(String message) {
        if (previousLogger != null) previousLogger.log(message);
        System.out.println("Logging to file: " + message);
    }
}

This pattern allows dynamic chaining:

Logger logger = new LogToMail(new LogToFile(new LogToDatabase(null)));
logger.log("Some message");

Key Benefits

Comparison with Coffee Example

The professor mentioned the classic “coffee and condiments” example (adding milk, sugar, caramel, etc.) but noted it’s less intuitive than the logger example. Still, it’s a textbook illustration of how decorators can represent layered functionality.

Takeaway

Decorator pattern enables treating a chain of components as a single unit while maintaining modular, pluggable behavior.


Builder Pattern

Core Idea

The Builder Pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Problems it Solves

  1. Invalid Object States: When constructors allow empty or placeholder values, users may misuse them (e.g., pass null or dummy data just to compile).
  2. Unclear Parameters: It’s not always obvious what each constructor parameter does, leading to confusion or misuse.

Example: Person Class

A Person class might have fields:

Traditional constructor:

public Person(String name, int age, String phone, String street, String category)

Problems:

Builder-Based Solution

Use a builder class to explicitly construct a valid person:

public class PersonBuilder {
    private Person person = new Person();

    public void setImportantInfo(String phone, String name) {
        person.phone = phone;
        person.name = name;
    }

    public void setOptionalInfo(int age, String street) {
        person.age = age;
        person.street = street;
    }

    public void setCategory(String category) {
        // explain what the category means
        person.category = category;
    }

    public void validate() {
        // Ensure mandatory fields are set, throw error if not
    }

    public Person build() {
        validate();
        return person;
    }
}

Benefits

Summary of Differences

Aspect Decorator Pattern Builder Pattern
Purpose Add behavior dynamically Create complex objects step-by-step
Flexibility Runtime composition Compile-time validation
Focus Behavior extension Object construction

Final Thoughts

Up Next


Reminder: Always think about why a pattern is used, not just how to implement it. Understanding the motivation helps apply it in new contexts effectively.