The Observer pattern is a software design pattern in which an object (subject / Observable), maintains a list of Observers, and notifies them automatically of any state changes [wiki]. The Observer pattern falls under behavioral pattern category.
When it is used
Observer Pattern (alias as Alias: Subject Observer, Publish Subscribe, Callback) is used when there is a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. The change of a state in one object must be reflected in another object without keeping the objects tight coupled. It is easy to support new observers with minimal code changes.
Here is one scenario: a configuration file (server_setting.xml) is monitored by an Observable object. When the configuration file is modified by someone, the Observable will detect the file modification, and notify multiple Observers about the file change. Once the Observers receive the notification, one observer will reload the file without restarting the application (/server), another observer may send alert message to the system administrator about the change.
Observer Pattern is part of other important patterns (e.g. Model View Controller), and also the basis for publish-subscribe messaging architectures.
Implementation
The participants in the diagram are:
- Observer – interface defining the operations to be executed after receiving notification
- ObserverA – Observer implementation
- ObserverB – Observer implementation
- Observable (Subject) – interface defining the operations for registering and unregistering observers
- ObservableA – Observable implementation. It maintains the state of the object, and notifies the attached Observers once the state changes.
By the way, Java SDK provides Observable API. We will implement our own interface to help understand the Observer pattern.
Code Examples
1 2 3 4 5 6 7 8 9 10 |
package net.tecbar.designpattern.observer; // subject to observe (monitor) public interface Observable { void registerObserver(Observer obs); void deregisterObservers(); void notifyObserver(Object obj); } |
1 2 3 4 5 6 7 8 |
package net.tecbar.designpattern.observer; public interface Observer { void update(); String getFileName(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
package net.tecbar.designpattern.observer; import org.apache.log4j.Logger; // monitor the single file public class ObserverA implements Observer { private static final Logger LOG = Logger.getLogger(ObserverA.class); private String fileName; public ObserverA (String filename) { this.fileName = filename; } @Override public void update() { LOG.info("reload the setting file " + fileName); // do what you want } @Override public String getFileName() { return fileName; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package net.tecbar.designpattern.observer; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.apache.log4j.helpers.FileWatchdog; public class ObservableA implements Observable { private static final Logger LOG = Logger.getLogger(ObservableA.class); private static final ObservableA INSTANCE = new ObservableA(); private Map<String, Observer> filemap = new HashMap<String, Observer>(); private ObservableA() { filemap = new HashMap<String, Observer>(); } public static ObservableA getInstance() { return INSTANCE; } @Override public void registerObserver(Observer obs) { filemap.put(obs.getFileName(), obs); FileMonitor monitor = new FileMonitor(obs.getFileName()); monitor.start(); } @Override public void deregisterObservers() { } @Override public void notifyObserver(Object obj) { filemap.get(obj.toString()).update(); } // check if the file was modified using FileWatchDog // In FileWatchDog , the default delay between every file modification check is 60 seconds. // The thread (monitor file change) is defined as a Daemon thread. I doubt that copy to redeployment may lead to memory leakage if no JVM restart. // Beside using FileWatchDog, other approaches include: // 1. use Java WatchService (more flexible) // or // 2. Implement your own thread and TimeTask to detect file change private class FileMonitor extends FileWatchdog { public FileMonitor(String filename) { super(filename); } @Override protected void doOnChange() { LOG.warn(super.filename + " was modified!"); notifyObserver(super.filename); } } } |
Client class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package net.tecbar.designpattern.observer; public class ObserverPatternTest { public static void main(String[] args) throws InterruptedException { Observer observer1 = new ObserverA("/tmp/server_setting.xml"); ObservableA.getInstance().registerObserver(observer1);; while(true) { // test only Thread.sleep(10000); } } } |
logging:
1 2 3 4 5 6 7 8 |
2015-03-09 17:46:19,015 WARN [ObservableA]:51 - /tmp/server_setting.xml was modified! 2015-03-09 17:46:19,017 INFO [ObserverA]:18 - reload the setting file /tmp/server_setting.xml 2015-03-09 17:50:57,292 WARN [ObservableA]:51 - /tmp/server_setting.xml was modified! 2015-03-09 17:50:57,294 INFO [ObserverA]:18 - reload the setting file /tmp/server_setting.xml 2015-03-09 17:51:57,296 WARN [ObservableA]:51 - /tmp/server_setting.xml was modified! 2015-03-09 17:51:57,296 INFO [ObserverA]:18 - reload the setting file /tmp/server_setting.xml |
Pingback: Software Design Patterns