Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
150: The Observer Pattern
I want to show you a piece of code that looks perfectly fine at first glance. Imagine you're building a stock market tracker. You have a StockTicker that fetches prices, and you want several different parts of your app—a dashboard, a logger, and an alert system—to react whenever a price changes.
public class StockTicker {
private double price;
private Dashboard dashboard;
private Logger logger;
private AlertSystem alertSystem;
public void setPrice(double newPrice) {
this.price = newPrice;
// Notify everyone manually
dashboard.updatePrice(newPrice);
logger.logPrice(newPrice);
alertSystem.checkThreshold(newPrice);
}
// Getters and setters for the components...
}
The Tight Coupling Trap
This code works. But it's a maintenance nightmare waiting to happen. Notice how the StockTicker has to know exactly who is listening to it. It has a hard dependency on Dashboard, Logger, and AlertSystem.
What happens when your boss tells you that we now need a MobilePushNotification service? You have to go back into the StockTicker class, add a new field, update the constructor, and add another line to the setPrice method. You're modifying the core logic of your data source every time you add a new UI element or a side-effect. This is a direct violation of the Open/Closed Principle: your classes should be open for extension, but closed for modification.
Decoupling with the Observer Interface
The fix is to stop the StockTicker from caring about who is listening. Instead, we define a contract—an interface—that any "observer" must follow. We then let the StockTicker maintain a list of these observers without knowing their specific types.
public interface StockObserver {
void onPriceChange(double price);
}
public class StockTicker {
private double price;
private final List<StockObserver> observers = new ArrayList<>();
public void attach(StockObserver observer) {
observers.add(observer);
}
public void detach(StockObserver observer) {
observers.remove(observer);
}
public void setPrice(double newPrice) {
this.price = newPrice;
notifyObservers();
}
private void notifyObservers() {
for (StockObserver observer : observers) {
observer.onPriceChange(price);
}
}
}
Now, the StockTicker is blissfully ignorant of the Dashboard or the Logger. It just knows it has a list of things that implement StockObserver. If you want to add a new notification service, you just create a new class that implements the interface and call ticker.attach(newService). You never have to touch the StockTicker code again.
Handling the "Ghost" Observer Problem
One thing I've seen trip up a lot of developers is forgetting to detach. If you're in a larger application where observers (like UI panels) are created and destroyed frequently, but the Subject (the StockTicker) lives for the entire duration of the app, you've just created a memory leak. The StockTicker still holds a reference to that destroyed panel in its observers list, preventing the Garbage Collector from cleaning it up.
Always make sure that if an object registers itself as an observer, it has a clear lifecycle path to unregister itself. In a real-world Java app, this usually happens in a dispose() or close() method.
📋 Practical Task
Build a Multi-Channel News Agency
You are tasked with building a News Agency system. The agency (the Subject) receives breaking news headlines and must push them to various subscribers (the Observers) without being tightly coupled to the delivery methods.
- Create a
NewsObserverinterface with a methodupdate(String news). - Create a
NewsAgencyclass that:- Maintains a list of
NewsObserverobjects. - Provides methods to
subscribe(NewsObserver o)andunsubscribe(NewsObserver o). - Has a method
publishNews(String news)that notifies all current subscribers.
- Maintains a list of
- Implement three distinct observer classes:
EmailSubscriber: Prints "Sending Email: [news]"SmsSubscriber: Prints "Sending SMS: [news]"NewsAppSubscriber: Prints "Push Notification: [news]"
- In your
mainmethod, simulate the following:- An agency is created.
- An email and SMS subscriber join.
- A piece of news is published (both should receive it).
- The SMS subscriber unsubscribes.
- A second piece of news is published (only the email subscriber should receive it).
There are no comments for now.