The Observer pattern is a one - to - many dependency relationship between objects. When one object (the subject) changes its state, all its dependents (observers) are notified and updated automatically. This pattern is useful in scenarios where a change in one object’s state needs to trigger updates in multiple other objects. For example, in a stock market application, when the price of a stock (the subject) changes, all the interested parties (observers) such as investors and brokers need to be notified.
The Mediator pattern defines an object that encapsulates how a set of objects interact. It centralizes the communication between objects, reducing the direct dependencies between them. Instead of objects communicating directly with each other, they communicate through the mediator. This pattern is beneficial when there are complex interactions between multiple objects in a system. For instance, in an air traffic control system, airplanes do not communicate directly with each other but through the air traffic controller (the mediator).
import java.util.ArrayList;
import java.util.List;
// Subject interface
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Observer interface
interface Observer {
void update();
}
// Concrete subject
class Stock implements Subject {
private double price;
private List<Observer> observers = new ArrayList<>();
public Stock(double price) {
this.price = price;
}
public void setPrice(double price) {
this.price = price;
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// Concrete observer
class Investor implements Observer {
private Subject stock;
public Investor(Subject stock) {
this.stock = stock;
stock.registerObserver(this);
}
@Override
public void update() {
System.out.println("Investor: Stock price has changed.");
}
}
public class ObserverExample {
public static void main(String[] args) {
Stock stock = new Stock(100.0);
Investor investor = new Investor(stock);
stock.setPrice(105.0);
}
}
// Mediator interface
interface Mediator {
void sendMessage(String message, Colleague colleague);
}
// Colleague interface
interface Colleague {
void receiveMessage(String message);
}
// Concrete mediator
class ChatMediator implements Mediator {
private List<Colleague> colleagues = new ArrayList<>();
public void addColleague(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void sendMessage(String message, Colleague colleague) {
for (Colleague c : colleagues) {
if (c != colleague) {
c.receiveMessage(message);
}
}
}
}
// Concrete colleague
class ChatUser implements Colleague {
private String name;
private Mediator mediator;
public ChatUser(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
public void send(String message) {
System.out.println(name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " receives: " + message);
}
}
public class MediatorExample {
public static void main(String[] args) {
ChatMediator mediator = new ChatMediator();
ChatUser user1 = new ChatUser("Alice", mediator);
ChatUser user2 = new ChatUser("Bob", mediator);
mediator.addColleague(user1);
mediator.addColleague(user2);
user1.send("Hello, Bob!");
}
}
Observable
class and Observer
interface (although they are deprecated in Java 9 onwards) or use a more modern approach like the above custom implementation.Both the Observer and Mediator patterns are valuable tools in a Java developer’s toolkit. The Observer pattern is ideal for scenarios where a single object’s state change needs to trigger updates in multiple objects, providing a simple and effective way to achieve loose coupling. On the other hand, the Mediator pattern is suitable for managing complex interactions between multiple objects, centralizing communication and reducing direct dependencies. By understanding the fundamental concepts, usage methods, common practices, and best practices of these two patterns, developers can make the right choice for their specific application requirements, leading to more maintainable and scalable Java code.