Design Patterns Used in the CompletableFuture Examples

1. Builder Pattern

Used in: Combining multiple futures

 

// UserAccountBuilder and DashboardBuilder use the Builder pattern

 

UserAccount.builder()

    .user(user)

    .account(account)

    .build();

 

Dashboard.builder()

    .user(userAccount.getUser())

    .account(userAccount.getAccount())

    .orders(orders)

    .build();

 

 

2. Factory Pattern (Static Factory Methods)

Used in: Creating CompletableFutures

// Static factory methods creating different types of futures

CompletableFuture.supplyAsync(() -> "Hello")    // Factory method

CompletableFuture.completedFuture("Invalid")    // Pre-completed factory

CompletableFuture.allOf(futures)                // Composite factory

CompletableFuture.anyOf(futures)                // Composite factory

 

3. Chain of Responsibility

Used in: Async pipelines with sequential processing

CompletableFuture.supplyAsync(() -> "Hello")

    .thenApply(String::toUpperCase)    // Handler 1

    .thenApply(s -> s + " Java")       // Handler 2

    .thenApply(result -> "Final: " + result) // Handler 3

    .exceptionally(ex -> "Fallback");   // Error Handler

 

4. Observer Pattern (Callback Pattern)

Used in: WhenComplete and callback handlers

 

future.whenComplete((result, ex) -> {

    // This is an observer/callback that gets notified

    // when the future completes

    if (ex != null) {

        log("Error: " + ex.getMessage());

    } else {

        log("Success: " + result);

    }

});

 

6. Template Method Pattern

Used in: Custom thread pool with standardized processing

 

// The skeleton of parallel processing is defined

List<CompletableFuture<Integer>> futures = data.stream()

    .map(item -> CompletableFuture.supplyAsync(() -> {

        System.out.println("Processing " + item);

        return processItem(item);  // Subclasses/implementations provide this

    }, executor))

    .collect(Collectors.toList());

 

    public final void process() {

        validate();

        calculateTotal();

        applyDiscounts();

        chargePayment();

        ship();

    }

 

Decorator Pattern

 

interface Coffee { double cost(); String desc(); }

class SimpleCoffee implements Coffee { ... }

abstract class CoffeeDecorator implements Coffee {
   
protected Coffee c;
   
public CoffeeDecorator(Coffee c) { this.c = c; }
   
public double cost() { return c.cost(); }
   
public String desc() { return c.desc(); }
}

class MilkDecorator extends CoffeeDecorator {
   
public MilkDecorator(Coffee c) { super(c); }
   
public double cost() { return super.cost() + 0.5; }
   
public String desc() { return super.desc() + " + Milk"; }
}

 

Use when:

·        You need to add behavior at runtime, per object.

·        Subclassing would lead to a combinatorial explosion of classes.

 

Builder Pattern

Intent: Separate the construction of a complex object from its representation so the same

construction process can create different representations.linkedin

Structure:

 

class Pizza {

    private final List<String> toppings;

    private final String size;

   

    // private constructor

    private Pizza(Builder b) {

        this.toppings = List.copyOf(b.toppings);

        this.size = b.size;

    }

 

    static class Builder {

        private List<String> toppings = new ArrayList<>();

        private String size = "M";

       

        public Builder addTopping(String t) {

            toppings.add(t);

            return this;

        }

       

        public Builder size(String s) {

            size = s;

            return this;

        }

       

        public Pizza build() {

            return new Pizza(this);

        }

    }

}

 

// Usage

Pizza p = new Pizza.Builder()

    .size("L")

    .addTopping("Cheese")

    .addTopping("Pepperoni")

    .build();