Java: strategy and observer with interfaces and lambdas

Swap behavior behind an interface and notify listeners without coupling the sender to them.

Java
import java.util.*;
import java.util.function.*;

class Main {
  interface Discount {
    double apply(double price);
  }

  static class EventBus {
    private final List<Consumer<String>> listeners = new ArrayList<>();

    void subscribe(Consumer<String> listener) {
      listeners.add(listener);
    }

    void publish(String event) {
      for (Consumer<String> listener : listeners) {
        listener.accept(event);
      }
    }
  }

  public static void main(String[] args) {
    Discount none = price -> price;
    Discount tenPercent = price -> price * 0.9;
    Discount[] plans = {none, tenPercent};
    for (int i = 0; i < plans.length; i++) {
      System.out.println("plan " + i + ": " + plans[i].apply(100.0));
    }

    EventBus bus = new EventBus();
    List<String> log = new ArrayList<>();
    bus.subscribe(log::add);
    bus.subscribe(event -> System.out.println("got " + event));
    bus.publish("order_placed");
    System.out.println(log);
  }
}
Output
plan 0: 100.0
plan 1: 90.0
got order_placed
[order_placed]

Explanation

The strategy pattern puts interchangeable behaviors behind one interface, so the caller picks which behavior to use and the code that uses it does not change. A functional interface with one method can be implemented with a lambda, which keeps each strategy short.

The observer pattern lets one object publish events to listeners it knows only as an interface. The publisher does not need to know what the listeners do, which makes it easy to add logging or notifications later. Both patterns depend on abstractions instead of concrete classes.

Explore the dry run
Guided walkthroughReady
1import java.util.*;
2import java.util.function.*;
3
4class Main {
5 interface Discount {
6 double apply(double price);
7 }
8
9 static class EventBus {
10 private final List<Consumer<String>> listeners = new ArrayList<>();
11
12 void subscribe(Consumer<String> listener) {
13 listeners.add(listener);
14 }
15
16 void publish(String event) {
17 for (Consumer<String> listener : listeners) {
18 listener.accept(event);
19 }
20 }
21 }
22
23 public static void main(String[] args) {
24 Discount none = price -> price;
25 Discount tenPercent = price -> price * 0.9;
26 Discount[] plans = {none, tenPercent};
27 for (int i = 0; i < plans.length; i++) {
28 System.out.println("plan " + i + ": " + plans[i].apply(100.0));
29 }
30
31 EventBus bus = new EventBus();
32 List<String> log = new ArrayList<>();
33 bus.subscribe(log::add);
34 bus.subscribe(event -> System.out.println("got " + event));
35 bus.publish("order_placed");
36 System.out.println(log);
37 }
38}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
A lambda is a second implementation of the same interface.tenPercent: price * 0.9
—
The loop works for any Discount without knowing which one it has.tenPercent: price * 0.9
plan 0: 100.0
plan 1: 90.0
The first listener stores events in a list; the second prints them.listeners = 2
plan 0: 100.0
plan 1: 90.0
Both listeners receive the event in the order they subscribed.listeners = 2
plan 0: 100.0
plan 1: 90.0
got order_placed
The log listener recorded the event.listeners = 2
plan 0: 100.0
plan 1: 90.0
got order_placed
[order_placed]

Cost and common mistakes

publish is O(L) for L listeners; apply is O(1).

  • A listener that throws an exception stops the loop, so later listeners never run. Decide how errors should be handled.
  • Listeners hold references, so forgetting to unsubscribe can keep objects alive and leak memory.
  • Do not use double for money in real code; BigDecimal or integer cents avoid rounding errors.

Try it yourself

Add a Discount that takes a fixed 15 off.

Reveal answer and reasoning

Discount fifteenOff = price -> price - 15; with 100.0 it returns 85.0, and no existing code changes.

Further reading