Loslegen
Javaneer
Zurück zur Stufe
Stufe 1·Design Patterns angewandt

Observer & wann Patterns schaden

Observer entkoppelt einen Audit-/Event-Publisher von seinen Abonnenten (ein Vorgeschmack auf Domain Events) - und eine ehrliche Lektion über Over-Engineering: wann ein Muster Kosten ohne Nutzen bringt.

14 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

One last behavioral pattern, then the module's most important lesson. Observer decouples a source of events from the things that react to them - the foundation of event-driven design and a preview of domain events. Then we close with the counterweight to a whole module about patterns: when patterns hurt. Because the mark of seniority isn't knowing twenty-three patterns - it's the restraint to use three.

Observer: publish, don't call

ledger-legacy posts a transaction and then, inline, must update the audit log, refresh a cache, and email a receipt. That hard-wires the transaction code to three unrelated concerns - and every new reaction means editing it (an Open/Closed violation). Observer inverts this: the source publishes an event; interested parties subscribe; the source never knows who's listening.

interface TransactionListener { void onPosted(Transaction t); }   // observer interface

class TransactionService {                                        // the SUBJECT
    private final List<TransactionListener> listeners;            // subscribers
    void post(Transaction t) {
        ledger.record(t);
        listeners.forEach(l -> l.onPosted(t));   // notify all - who they are is irrelevant here
    }
}

class AuditListener  implements TransactionListener { public void onPosted(Transaction t){ audit.log(t); } }
class ReceiptListener implements TransactionListener { public void onPosted(Transaction t){ email.receipt(t); } }

Now adding a reaction is registering a new listener - TransactionService never changes. The publisher and subscribers are decoupled: each can change independently. This is the seed of domain events (next module) and of Spring's ApplicationEventPublisher, which is Observer built into the framework. It underlies message queues, UI event handlers, and reactive streams.

Observer's hidden costs

Decoupling has a price you should name. Event flows are harder to trace - 'who reacts to this?' has no compile-time answer, so debugging means finding every listener. Ordering and error handling get subtle (if the audit listener throws, does the receipt still send?). And synchronous listeners can silently slow the publisher. Observer is powerful, but it trades an obvious call stack for a decoupled-but-invisible one. Use it when reactions genuinely vary and multiply, not to decouple two things that only ever talk to each other.

When patterns hurt

Here's the counterweight to this whole module. Every pattern buys flexibility with complexity - more classes, more indirection, more names to learn. That trade is worth it only against real, present need. Applied reflexively, patterns produce code that's harder to change than the mess they replaced. Watch for these smells:

  • A pattern with one implementation, forever. A Strategy interface with a single strategy, an Abstract Factory making one family - the flexibility is never used, so it's pure overhead.
  • Indirection you can't follow. If tracing one request means hopping through five patterns, you've optimized for imagined change at the cost of actual readability.
  • Patterns naming trivia. A NullObjectSingletonProxyFactory for a config flag. The name-to-value ratio is the tell.
  • Speculative generality. "We might need to swap databases / support plugins / go multi-tenant" - built now, needed never. This is YAGNI's exact target.

The senior heuristic: write the simplest thing that works, and let patterns emerge when simplicity actually strains. A duplicated if isn't yet a Strategy; a second use might make it one. Refactor toward a pattern when a real second case arrives - not in anticipation of one. Removing a needless pattern is as valuable a refactor as adding a needed one.

A carpenter's cluttered vs. curated bench

A novice carpenter, thrilled by every new tool, mounts all forty on the bench and reaches for the biscuit joiner to hang a picture - the tools now crowd the work. A master keeps a curated few within reach and fetches the specialist jig only when a job truly needs it; their bench looks almost bare. Patterns are those tools. Knowing all twenty-three is good; covering every surface of your code with them is the novice's cluttered bench. The craft is choosing the fewest tools that do the job cleanly - and knowing that most days, a chisel and a plane are all you need.

Argue both sides

A service currently has one payment provider, integrated with a direct call. A teammate wants to introduce a PaymentStrategy interface, an AbstractPaymentProviderFactory, and an Observer-based event system 'so we're ready for more providers and easy to extend.' There are no concrete plans for a second provider. Make the case for keeping it simple now, and state the specific condition under which you'd add each pattern later.

What is the senior heuristic for applying design patterns?

Key takeaways

  • Observer decouples an event source (subject) from reactors (observers): the subject publishes, subscribers register, and the subject never knows who listens.
  • It's the foundation of domain events, Spring's ApplicationEventPublisher, message queues, and reactive streams - adding a reaction needs no change to the publisher.
  • Observer's costs: harder-to-trace flows, subtle ordering/error handling, and synchronous listeners that can slow the publisher.
  • Every pattern trades complexity for flexibility; applied without real need, patterns make code harder to change than the mess they replace.
  • The senior heuristic: write the simplest thing that works and refactor toward a pattern when simplicity strains against real, present change - removing a needless pattern is a valid refactor too.
War diese Lektion hilfreich?
Diese Seite auf GitHub bearbeiten