Skip to content
  • Design Patterns: Strategy, Observer & More · Java

Design Patterns: Strategy, Observer & More

LearnViaNotes @learnvianotes· 4 min read

Behavioural and structural patterns in modern Java — most of them are a lambda or a small interface away.

Strategy

Java
import java.util.Map;
import java.util.function.DoubleUnaryOperator;

public class Main {
    static final Map<String, DoubleUnaryOperator> DISCOUNTS = Map.of(
            "none", price -> price,
            "festive", price -> price * 0.8,
            "flat50", price -> Math.max(0, price - 50));

    static double checkout(double price, String code) {
        return DISCOUNTS.getOrDefault(code, DISCOUNTS.get("none")).applyAsDouble(price);
    }

    public static void main(String[] args) {
        System.out.println(checkout(200, "festive"));
        System.out.println(checkout(200, "flat50"));
        System.out.println(checkout(200, "bogus"));
    }
}
Output
160.0
150.0
200.0

Comparator is the JDK's best-known strategy: list.sort(Comparator.comparing(Person::age)) plugs a comparison algorithm into a sort.

Observer

Java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class Main {
    static final class StockTicker {
        private final List<Consumer<Double>> listeners = new ArrayList<>();

        Runnable subscribe(Consumer<Double> l) {
            listeners.add(l);
            return () -> listeners.remove(l);          // handle to unsubscribe
        }

        void publish(double price) {
            listeners.forEach(l -> l.accept(price));
        }
    }

    public static void main(String[] args) {
        StockTicker ticker = new StockTicker();
        ticker.subscribe(p -> System.out.println("display: " + p));
        Runnable cancel = ticker.subscribe(p -> {
            if (p > 100) System.out.println("alert: above 100");
        });

        ticker.publish(99.5);
        ticker.publish(101.0);
        cancel.run();                                  // stop the alerts
        ticker.publish(120.0);
    }
}
Output
display: 99.5
display: 101.0
alert: above 100
display: 120.0

Decorator

Wrap an object in another object with the same interface to add behaviour. java.io is built this way: each stream adds one feature (buffering, decoding, line reading).

Java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;

public class Main {
    interface Coffee { double cost(); String describe(); }

    record Espresso() implements Coffee {
        public double cost() { return 2.0; }
        public String describe() { return "espresso"; }
    }

    record WithMilk(Coffee inner) implements Coffee {
        public double cost() { return inner.cost() + 0.5; }
        public String describe() { return inner.describe() + " + milk"; }
    }

    record WithSyrup(Coffee inner) implements Coffee {
        public double cost() { return inner.cost() + 0.75; }
        public String describe() { return inner.describe() + " + syrup"; }
    }

    public static void main(String[] args) throws IOException {
        Coffee order = new WithSyrup(new WithMilk(new Espresso()));
        System.out.println(order.describe() + " = " + order.cost());

        // the JDK version: a Reader decorated with buffering and readLine()
        try (BufferedReader r = new BufferedReader(new StringReader("line 1\nline 2"))) {
            System.out.println(r.readLine());
        }
    }
}
Output
espresso + milk + syrup = 3.25
line 1

Template method

An abstract class fixes the skeleton of an algorithm in a final method and lets subclasses fill in the steps.

Java
public class Main {
    static abstract class Report {
        final String render() {                      // the template — not overridable
            return header() + "\n" + body() + "\n-- end --";
        }
        String header() { return "REPORT"; }         // hook with a default
        abstract String body();                      // step subclasses must supply
    }

    static final class SalesReport extends Report {
        @Override String body() { return "sales: 42 units"; }
    }

    public static void main(String[] args) {
        System.out.println(new SalesReport().render());
    }
}
Output
REPORT
sales: 42 units
-- end --

Adapter

Convert one interface into another that a client expects. Arrays.asList adapts an array to List; InputStreamReader adapts a byte stream to a character Reader.

Java
import java.util.function.Supplier;

public class Main {
    static final class LegacyThermometer {                  // can't change this class
        double readFahrenheit() { return 98.6; }
    }

    interface CelsiusSensor { double celsius(); }

    static CelsiusSensor adapt(LegacyThermometer t) {     // the adapter
        return () -> (t.readFahrenheit() - 32) * 5 / 9;
    }

    public static void main(String[] args) {
        CelsiusSensor s = adapt(new LegacyThermometer());
        Supplier<String> fmt = () -> String.format("%.1f C", s.celsius());
        System.out.println(fmt.get());
    }
}
Output
37.0 C

Where the JDK uses them

PatternKindIn the JDK
StrategyBehaviouralComparator, ThreadPoolExecutor's RejectedExecutionHandler
ObserverBehaviouralPropertyChangeListener, Swing listeners, Flow.Subscriber
IteratorBehaviouralIterator / Iterable behind every for-each loop
Template methodBehaviouralAbstractList (get + size → everything else), InputStream.read(byte[])
CommandBehaviouralRunnable, Callable submitted to executors
DecoratorStructuralBufferedInputStream, Collections.unmodifiableList, synchronizedMap
AdapterStructuralArrays.asList, InputStreamReader, OutputStreamWriter
ProxyStructuraljava.lang.reflect.Proxy (Spring AOP, mocks)
Factory / static factoryCreationalList.of, Calendar.getInstance, NumberFormat.getInstance
BuilderCreationalStringBuilder, HttpClient.newBuilder()
SingletonCreationalRuntime.getRuntime()

Strategy vs template method?

Decorator vs inheritance for adding behaviour?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report