Index

Functional Design Patterns

Java Functional Programming

8.1 Strategy Pattern with Lambdas

The Strategy Pattern is a classic behavioral design pattern that enables selecting an algorithm’s behavior at runtime. Traditionally, this involves defining a family of algorithms, encapsulating each one in its own class, and making them interchangeable via a common interface. This allows the client to choose or switch the algorithm without changing the context code.

Traditional Strategy Pattern Structure

Typically, you create an interface representing the strategy, e.g.,

interface SortingStrategy {
    void sort(int[] array);
}

Then provide multiple implementations:

class BubbleSort implements SortingStrategy {
    public void sort(int[] array) {
        // Bubble sort implementation
    }
}

class QuickSort implements SortingStrategy {
    public void sort(int[] array) {
        // Quick sort implementation
    }
}

The client code uses the strategy via the interface:

class Sorter {
    private SortingStrategy strategy;

    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void sortArray(int[] array) {
        strategy.sort(array);
    }
}

While effective, this pattern involves boilerplate classes and verbosity.

Simplifying with Lambdas and Functional Interfaces

Java 8’s introduction of functional interfaces and lambdas greatly simplifies the Strategy pattern. Instead of creating multiple concrete classes, you can define a single functional interface and pass behavior as a lambda expression directly.

For example, the SortingStrategy interface can be replaced by a functional interface like:

@FunctionalInterface
interface SortingStrategy {
    void sort(int[] array);
}

Using lambdas, you can now easily provide different strategies inline without separate classes:

SortingStrategy bubbleSort = array -> {
    // Bubble sort logic
    for (int i = 0; i < array.length - 1; i++) {
        for (int j = 0; j < array.length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                int temp = array[j];
                array[j] = array[j + 1];
                array[j + 1] = temp;
            }
        }
    }
};

SortingStrategy quickSort = array -> Arrays.sort(array); // Using built-in sort

The client can switch strategies effortlessly:

class Sorter {
    private SortingStrategy strategy;

    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array) {
        strategy.sort(array);
    }
}

Usage:

public static void main(String[] args) {
    int[] data = {5, 2, 8, 3, 1};
    Sorter sorter = new Sorter(bubbleSort);

    sorter.sort(data);
    System.out.println("Bubble sorted: " + Arrays.toString(data));

    sorter.setStrategy(quickSort);
    int[] data2 = {9, 7, 4, 6};
    sorter.sort(data2);
    System.out.println("Quick sorted: " + Arrays.toString(data2));
}
Click to view full runnable Code

import java.util.Arrays;

// Define functional interface for sorting strategies
@FunctionalInterface
interface SortingStrategy {
    void sort(int[] array);
}

// Sorter class using a strategy
class Sorter {
    private SortingStrategy strategy;

    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public void sort(int[] array) {
        strategy.sort(array);
    }
}

public class StrategyPatternWithLambda {
    public static void main(String[] args) {
        // Lambda-based bubble sort strategy
        SortingStrategy bubbleSort = array -> {
            for (int i = 0; i < array.length - 1; i++) {
                for (int j = 0; j < array.length - 1 - i; j++) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
        };

        // Lambda-based quick sort strategy using built-in method
        SortingStrategy quickSort = Arrays::sort;

        // Using bubble sort
        int[] data = {5, 2, 8, 3, 1};
        Sorter sorter = new Sorter(bubbleSort);
        sorter.sort(data);
        System.out.println("Bubble sorted: " + Arrays.toString(data));

        // Using quick sort
        int[] data2 = {9, 7, 4, 6};
        sorter.setStrategy(quickSort);
        sorter.sort(data2);
        System.out.println("Quick sorted: " + Arrays.toString(data2));
    }
}

Other Practical Examples

Discount Strategy

@FunctionalInterface
interface DiscountStrategy {
    double applyDiscount(double price);
}

DiscountStrategy noDiscount = price -> price;
DiscountStrategy seasonalDiscount = price -> price * 0.9;
DiscountStrategy clearanceDiscount = price -> price * 0.5;

double originalPrice = 100.0;
System.out.println("Seasonal price: " + seasonalDiscount.applyDiscount(originalPrice));

Logging Behavior

@FunctionalInterface
interface Logger {
    void log(String message);
}

Logger consoleLogger = msg -> System.out.println("[Console] " + msg);
Logger fileLogger = msg -> System.out.println("[File] " + msg); // Simplified

consoleLogger.log("App started");
fileLogger.log("File saved");

Summary

By using Java lambdas and functional interfaces, the Strategy pattern becomes lightweight and flexible. Instead of multiple boilerplate classes, behaviors are expressed concisely as lambdas, making your code more readable and maintainable while retaining full expressiveness and runtime flexibility. This functional approach suits many use cases like sorting, discounts, logging, and more.

Index

8.2 Command Pattern using Functional Interfaces

The Command Pattern is a behavioral design pattern that encapsulates a request or an operation as an object. This allows you to parameterize clients with different requests, queue or log operations, and support undoable actions. The core idea is to separate the invoker (who triggers the command) from the executor (the code that performs the action), promoting loose coupling and greater flexibility.

Traditional Command Pattern

Traditionally, you define a Command interface:

public interface Command {
    void execute();
}

Each operation implements this interface as a concrete class:

class TurnOnLightCommand implements Command {
    private Light light;

    public TurnOnLightCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.turnOn();
    }
}

The invoker holds a reference to the Command and calls execute() without knowing the details.

Using Functional Interfaces and Lambdas

Java’s functional interfaces like Runnable, Consumer<T>, or custom ones perfectly fit the Command pattern’s single-method interface. Lambdas enable writing commands concisely without boilerplate classes.

For example, a simple command can be represented as a Runnable lambda:

Runnable turnOnLight = () -> System.out.println("Light turned ON");
Runnable turnOffLight = () -> System.out.println("Light turned OFF");

An invoker class might simply store and execute these commands:

class RemoteControl {
    private Runnable command;

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

    public void pressButton() {
        if (command != null) {
            command.run();
        }
    }
}

Usage:

public static void main(String[] args) {
    RemoteControl remote = new RemoteControl();

    remote.setCommand(turnOnLight);
    remote.pressButton();  // Output: Light turned ON

    remote.setCommand(turnOffLight);
    remote.pressButton();  // Output: Light turned OFF
}
Click to view full runnable Code

public class CommandPatternWithLambdas {

    public static void main(String[] args) {
        // Define commands as lambdas
        Runnable turnOnLight = () -> System.out.println("Light turned ON");
        Runnable turnOffLight = () -> System.out.println("Light turned OFF");

        // Create the invoker
        RemoteControl remote = new RemoteControl();

        // Use the ON command
        remote.setCommand(turnOnLight);
        remote.pressButton();  // Output: Light turned ON

        // Use the OFF command
        remote.setCommand(turnOffLight);
        remote.pressButton();  // Output: Light turned OFF
    }

    // Invoker class
    static class RemoteControl {
        private Runnable command;

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

        public void pressButton() {
            if (command != null) {
                command.run();
            }
        }
    }
}

Advantages of Functional Command Pattern

Composing Macro Commands

Using Runnable's default method andThen, you can compose commands:

Runnable startEngine = () -> System.out.println("Engine started");
Runnable playMusic = () -> System.out.println("Music playing");

Runnable morningRoutine = startEngine.andThen(playMusic);

morningRoutine.run();
// Output:
// Engine started
// Music playing

Using Custom Functional Interfaces with Parameters

Sometimes commands require parameters or return results. Define a custom interface:

@FunctionalInterface
interface CommandWithArg<T> {
    void execute(T arg);
}

CommandWithArg<String> printMessage = msg -> System.out.println("Message: " + msg);

printMessage.execute("Hello, Command Pattern!");

Summary

Java functional interfaces simplify the Command pattern by eliminating boilerplate command classes, making commands easy to create, pass, and compose as lambdas. This approach supports deferred execution, flexible workflows, and features like undo or macros, all with concise and readable code. It’s a natural fit for event handling, task scheduling, and any context where actions need to be encapsulated and executed flexibly.

Index

8.3 Observer Pattern with Functional Callbacks

The Observer Pattern is a behavioral design pattern that establishes a one-to-many relationship between objects: when the subject changes its state, all its observers are notified and updated automatically. This pattern is fundamental in event-driven programming, GUIs, and reactive systems where components react asynchronously to events or data changes.

Traditional Observer Pattern

In classic Java, the pattern often involves interfaces like Observer and Observable or listener interfaces such as PropertyChangeListener. Observers implement these interfaces and register themselves with the subject to receive updates.

For example, using PropertyChangeListener:

PropertyChangeSupport support = new PropertyChangeSupport(this);

support.addPropertyChangeListener(evt -> {
    System.out.println("Property " + evt.getPropertyName() + " changed to " + evt.getNewValue());
});

Functional Programming Simplifies Observers

Java 8 introduced lambdas and method references, which simplify observers dramatically by letting you register behavior inline without implementing entire classes. Observers become functional callbacks—just functions invoked on event occurrence.

Instead of writing verbose listener classes, you register a lambda that reacts to events:

subject.addListener(event -> System.out.println("Event received: " + event));

This promotes more readable, concise, and maintainable code.

Example: Custom Event System with Functional Callbacks

Let's implement a simple observable subject that accepts functional observers.

import java.util.*;
import java.util.function.Consumer;

class EventSource<T> {
    private final List<Consumer<T>> observers = new ArrayList<>();

    public void subscribe(Consumer<T> observer) {
        observers.add(observer);
    }

    public void unsubscribe(Consumer<T> observer) {
        observers.remove(observer);
    }

    public void notifyObservers(T event) {
        for (Consumer<T> observer : observers) {
            observer.accept(event);
        }
    }
}

Usage:

public class ObserverExample {
    public static void main(String[] args) {
        EventSource<String> eventSource = new EventSource<>();

        // Register observers with lambdas
        eventSource.subscribe(event -> System.out.println("Observer 1 received: " + event));
        eventSource.subscribe(event -> System.out.println("Observer 2 received: " + event.toUpperCase()));

        eventSource.notifyObservers("Hello Observers!");

        // Output:
        // Observer 1 received: Hello Observers!
        // Observer 2 received: HELLO OBSERVERS!
    }
}
Click to view full runnable Code

import java.util.*;
import java.util.function.Consumer;

// Observable subject
class EventSource<T> {
    private final List<Consumer<T>> observers = new ArrayList<>();

    public void subscribe(Consumer<T> observer) {
        observers.add(observer);
    }

    public void unsubscribe(Consumer<T> observer) {
        observers.remove(observer);
    }

    public void notifyObservers(T event) {
        for (Consumer<T> observer : observers) {
            observer.accept(event);
        }
    }
}

// Demo with functional observers
public class ObserverExample {
    public static void main(String[] args) {
        EventSource<String> eventSource = new EventSource<>();

        // Register observers using lambdas
        eventSource.subscribe(event -> System.out.println("Observer 1 received: " + event));
        eventSource.subscribe(event -> System.out.println("Observer 2 received: " + event.toUpperCase()));

        eventSource.notifyObservers("Hello Observers!");
    }
}

Real-World Relevance

Managing Subscriptions Easily

Because observers are functions, you can store and remove them effortlessly. This is especially useful in long-lived systems where listeners must be unsubscribed to prevent memory leaks:

Consumer<String> observer = event -> System.out.println("Received event: " + event);
eventSource.subscribe(observer);

// Later, unsubscribe if needed
eventSource.unsubscribe(observer);

Summary

Functional callbacks transform the Observer pattern into a lightweight, flexible mechanism for event handling. By leveraging lambdas and method references, Java developers can implement event subscription and notification without boilerplate listener classes, making event-driven code more concise, readable, and maintainable. This functional approach aligns perfectly with GUI frameworks, reactive streams, and modern event-driven architectures.

Index

8.4 Example: Event Handling in GUI Applications

Java’s GUI frameworks, such as Swing, rely heavily on event-driven programming where components notify listeners of user actions like button clicks or text input changes. With Java 8 and later, functional interfaces and lambdas simplify event handling by replacing verbose anonymous classes with concise, readable functional callbacks.

Below is a runnable example demonstrating a simple Swing GUI that uses lambdas to handle button clicks and window closing events.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class FunctionalSwingExample {

    public static void main(String[] args) {
        // Create main frame (window)
        JFrame frame = new JFrame("Functional Event Handling Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);
        
        // Create a button
        JButton clickButton = new JButton("Click Me");
        
        // Create a label to show click count
        JLabel label = new JLabel("Button not clicked yet");
        
        // Use lambda for button click event handler (ActionListener)
        clickButton.addActionListener(e -> {
            String currentText = label.getText();
            int count = 0;
            if (currentText.contains("clicked")) {
                count = Integer.parseInt(currentText.replaceAll("\\D+", "")) + 1;
            }
            label.setText("Button clicked " + count + " times");
        });
        
        // Use lambda for window close event (WindowListener)
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.out.println("Window is closing. Bye!");
            }
        });
        
        // Layout components vertically
        frame.setLayout(new FlowLayout());
        frame.add(clickButton);
        frame.add(label);
        
        // Show the window
        frame.setVisible(true);
    }
}

Explanation

Why functional event handling?

This example illustrates how functional design patterns mesh naturally with GUI event handling, making your Java desktop apps more elegant and easier to maintain. Lambdas help keep event-driven code clear and focused, boosting developer productivity in GUI development.

Index