1. Overview
Observer mode is a software design mode in which a target object manages all observer objects that depend on it and actively notifies them when their own state changes. This is usually achieved by calling the methods provided by each observer. This mode is often used in real-time event processing systems.
2. Definition
Also known as the Publish/Subscribe mode, it defines a one-to-many dependency that allows multiple observer objects to listen to a theme object at the same time. When the state changes, this theme object notifies all observer objects so that they can update themselves automatically.
3. Structure
The following roles exist in the observer mode:
- Subject: An abstract theme (abstract observer), the abstract theme role stores all observer objects in a collection, each theme can have any number of observers, and the abstract theme provides an interface to add and delete observer objects.
- ConcreteSubject: A specific subject (a specific observee) that stores the relevant state in a specific observer object and sends notifications to all registered observers when the internal state of the specific subject changes.
- Observer: An Abstract observer is an abstract class of observers that defines an update interface that updates itself when notified of subject changes.
- ConcrereObserver: Specific observer that implements an update interface defined by an abstract observer to update its own state when notified of a subject change.
4. Implementation
4.1 Abstract Theme
public interface Subject { /** * Add an observer * @param observer */ void addObserver(Observer observer); /** * Remove Observer * @param observer */ void removeObserver(Observer observer); /** * Notify observers */ void notifyObserver(); }
4.2 Specific topics
package com.zengqingfa.designpattern.observer.standard; import java.util.ArrayList; import java.util.List; /** * * @fileName: ConcreteSubject * @author: zengqf3 * @date: 2021-4-23 15:06 * @description: Specific Subject (Observed) */ public class ConcreteSubject implements Subject { /** * Observer List */ private List<Observer> observerList; public ConcreteSubject() { observerList = new ArrayList<>(); } @Override public void addObserver(Observer observer) { observerList.add(observer); } @Override public void removeObserver(Observer observer) { observerList.remove(observer); } @Override public void notifyObserver() { for (Observer observer : observerList) { observer.update("standard Standard observer"); } } /** * Trigger Update */ public void triggle() { System.out.println("Observed event changes"); this.notifyObserver(); } }
4.3 Abstract Observer
public interface Observer { /** * To update */ void update(String notice); }
4.4 Specific observers
public class ConcreteObserver1 implements Observer { @Override public void update(String notice) { System.out.println("Observer ConcreteObserver1 Received Message:"+notice); } } public class ConcreteObserver2 implements Observer { @Override public void update(String notice) { System.out.println("Observer ConcreteObserver2 Received Message:"+notice); } }
4.5 Test
public class Client { public static void main(String[] args) { /** * Observed event changes * Observer ConcreteObserver1 receives message: standard standard observer * Observer ConcreteObserver2 receives the message: standard Standard Observer */ ConcreteSubject subject = new ConcreteSubject(); subject.addObserver(new ConcreteObserver1()); subject.addObserver(new ConcreteObserver2()); subject.triggle(); } }
4.6 uml structure
5. Advantages and disadvantages
5.1 Advantages
1) Reduced coupling between the target and the observer, which is abstract
2) Notifications are sent by the observer, and all registered observers receive information [broadcasting mechanism is implemented]
5.2 Disadvantages
1) If there are many observers, it will take time for all observers to receive notifications from the observee
2) If the observee has a circular dependency, notification sent by the observer causes the observer to make circular calls, resulting in a system crash
6. Scenarios applicable
1) An abstract model has two aspects, one of which depends on the other. Encapsulate these aspects in separate objects so that they can be changed and reused independently of each other.
2) Changes in one object will result in changes in one or more other objects without knowing how many objects will change, which can reduce the coupling between objects.
3) An object must inform other objects without knowing who they are.
4) A trigger chain needs to be created in the system. The behavior of object A will affect object B, and the behavior of object B will affect object C.... You can use the observer mode to create a chain trigger mechanism.
7. Design Mode in Source Code
7.1 Observer interface provided by JDK
In JDK's java.util package, Observable classes and Observer interfaces are provided, which form JDK's support for observer mode.
Observer Observer
package java.util; /** * A class can implement the <code>Observer</code> interface when it * wants to be informed of changes in observable objects. * * @author Chris Warth * @see java.util.Observable * @since JDK1.0 */ public interface Observer { /** * This method is called whenever the observed object is changed. An * application calls an <tt>Observable</tt> object's * <code>notifyObservers</code> method to have all the object's * observers notified of the change. * * @param o the observable object. * @param arg an argument passed to the <code>notifyObservers</code> * method. */ void update(Observable o, Object arg); }
Observable class is the target class
public class Observable { private boolean changed = false; private Vector<Observer> obs; /** Construct an Observable with zero Observers. */ public Observable() { obs = new Vector<>(); } /** * Adds an observer to the set of observers for this object, provided * that it is not the same as some observer already in the set. * The order in which notifications will be delivered to multiple * observers is not specified. See the class comment. * * @param o an observer to be added. * @throws NullPointerException if the parameter o is null. */ public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } /** * Deletes an observer from the set of observers of this object. * Passing <CODE>null</CODE> to this method will have no effect. * @param o the observer to be deleted. */ public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } /** * If this object has changed, as indicated by the * <code>hasChanged</code> method, then notify all of its observers * and then call the <code>clearChanged</code> method to * indicate that this object has no longer changed. * <p> * Each observer has its <code>update</code> method called with two * arguments: this observable object and <code>null</code>. In other * words, this method is equivalent to: * <blockquote><tt> * notifyObservers(null)</tt></blockquote> * * @see java.util.Observable#clearChanged() * @see java.util.Observable#hasChanged() * @see java.util.Observer#update(java.util.Observable, java.lang.Object) */ public void notifyObservers() { notifyObservers(null); } /** * If this object has changed, as indicated by the * <code>hasChanged</code> method, then notify all of its observers * and then call the <code>clearChanged</code> method to indicate * that this object has no longer changed. * <p> * Each observer has its <code>update</code> method called with two * arguments: this observable object and the <code>arg</code> argument. * * @param arg any object. * @see java.util.Observable#clearChanged() * @see java.util.Observable#hasChanged() * @see java.util.Observer#update(java.util.Observable, java.lang.Object) */ public void notifyObservers(Object arg) { /* * a temporary array buffer, used as a snapshot of the state of * current Observers. */ Object[] arrLocal; synchronized (this) { /* We don't want the Observer doing callbacks into * arbitrary code while holding its own Monitor. * The code where we extract each Observable from * the Vector and store the state of the Observer * needs synchronization, but notifying observers * does not (should not). The worst result of any * potential race-condition here is that: * 1) a newly-added Observer will miss a * notification in progress * 2) a recently unregistered Observer will be * wrongly notified when it doesn't care */ if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } /** * Clears the observer list so that this object no longer has any observers. */ public synchronized void deleteObservers() { obs.removeAllElements(); } /** * Marks this <tt>Observable</tt> object as having been changed; the * <tt>hasChanged</tt> method will now return <tt>true</tt>. */ protected synchronized void setChanged() { changed = true; } /** * Indicates that this object has no longer changed, or that it has * already notified all of its observers of its most recent change, * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>. * This method is called automatically by the * <code>notifyObservers</code> methods. * * @see java.util.Observable#notifyObservers() * @see java.util.Observable#notifyObservers(java.lang.Object) */ protected synchronized void clearChanged() { changed = false; } /** * Tests if this object has changed. * * @return <code>true</code> if and only if the <code>setChanged</code> * method has been called more recently than the * <code>clearChanged</code> method on this object; * <code>false</code> otherwise. * @see java.util.Observable#clearChanged() * @see java.util.Observable#setChanged() */ public synchronized boolean hasChanged() { return changed; } /** * Returns the number of observers of this <tt>Observable</tt> object. * * @return the number of observers of this object. */ public synchronized int countObservers() { return obs.size(); } }
Specific topics
public class ConcreteSubject extends Observable { /** * Trigger Update */ public void triggle(String notice) { System.out.println("Observed event changes"); setChanged(); notifyObservers(notice); } }
Specific observer
public class ConcreteObserver1 implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("Observer ConcreteObserver1 Received Message:" + arg); } } public class ConcreteObserver2 implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("Observer ConcreteObserver2 Received Message:" + arg); } }
test
public class Client { public static void main(String[] args) { /** * Observed event changes * Observer ConcreteObserver2 receives the message: jdk Observer Mode * Observer ConcreteObserver1 receives the message: jdk Observer Mode */ ConcreteSubject subject = new ConcreteSubject(); subject.addObserver(new ConcreteObserver1()); subject.addObserver(new ConcreteObserver2()); subject.triggle("jdk Observer mode"); } }
uml diagram structure
7.2 Observer mode in Guava EventBus
EventBus in Guava encapsulates a friendly "production/consumption model" that enables the registration of listeners and event distribution in the observer mode in a very simple way.
After using Guava EventBus, if you need to subscribe to a message, you don't need to implement any interfaces, just add the @Subscribe annotation to the listening method, EventBus provides register and unregister methods to register and unregister events, which are distributed to registered objects when EventBus calls the post method
Introducing dependencies
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency>
Abstract observer
public interface Observer { /** * To update */ void update(String notice); }
Specific observer
public class ConcreteObserver1 implements Observer { @Subscribe @Override public void update(String notice) { System.out.println("Observer ConcreteObserver1 Received Message:" + notice); } } public class ConcreteObserver2 implements Observer { @Override @Subscribe public void update(String notice) { System.out.println("Observer ConcreteObserver2 Received Message:" + notice); } }
Abstract Theme
public interface Subject { /** * Register Observers * @param observer */ void register(Observer observer); /** * Remove Observer * @param observer */ void unregister(Observer observer); }
Specific topics
package com.zengqingfa.designpattern.observer.guava; import com.google.common.eventbus.EventBus; /** * * @fileName: ConcreteSubject * @author: zengqf3 * @date: 2021-4-23 15:06 * @description: Specific Subject (Observed) */ public class ConcreteSubject implements Subject{ private EventBus eventBus; public ConcreteSubject() { eventBus = new EventBus(); } /** * Trigger Update */ public void triggle() { System.out.println("Observed event changes"); this.eventBus.post("guava Observer mode"); } @Override public void register(Observer observer) { this.eventBus.register(observer); } @Override public void unregister(Observer observer) { this.eventBus.unregister(observer); } }
test
public class Client { public static void main(String[] args) { /** * Observed event changes * Observer ConcreteObserver1 receives a message: guava Observer Mode * Observer ConcreteObserver2 receives a message: guava Observer Mode */ ConcreteSubject subject=new ConcreteSubject(); subject.register(new ConcreteObserver1()); subject.register(new ConcreteObserver2()); subject.triggle(); } }
uml structure
7.3 Observer mode in Spring ApplicationContext event mechanism
spring's event mechanism extends from java's event mechanism, and event handling in ApplicationContext is provided by the ApplicationEvent class and the ApplicationListener interface. If a Bean implements the ApplicationListener interface and has been published to a container, one ApplicationEvent is published per ApplicationContextEvent, this Bean will be notified
- ApplicationContext: Event source, where the publishEvent() method is used to trigger container events
- ApplicationEvent: Event itself, custom events need to inherit this class to pass data
- ApplicationListener: Event listener interface, event business logic encapsulated in the listener
Introducing dependencies
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.0.RELEASE</version> </dependency>
spring Profile
@Configuration @ComponentScan(basePackages = "com.zengqingfa.designpattern.observer.spring") public class AppConfig { }
Event
package com.zengqingfa.designpattern.observer.spring; import org.springframework.context.ApplicationEvent; import org.springframework.stereotype.Component; /** * * @fileName: Notice * @author: zengqf3 * @date: 2021-4-23 16:04 * @description: * * ApplicationContext: Event source, where the publishEvent() method is used to trigger container events * * ApplicationEvent: Events themselves, custom events need to inherit this class, which can be used to pass data * * */ public class Notice extends ApplicationEvent { /** * Create a new {@code ApplicationEvent}. * @param source the object on which the event initially occurred or with * which the event is associated (never {@code null}) */ public Notice(Object source) { super(source); } }
Observer
package com.zengqingfa.designpattern.observer.spring; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * * @fileName: ConcreteObserver1 * @author: zengqf3 * @date: 2021-4-23 15:10 * @description: * ApplicationListener: Event listener interface, business logic of events encapsulated in the listener */ @Component public class ConcreteObserver1 implements ApplicationListener<Notice> { @Override public void onApplicationEvent(Notice event) { System.out.println("Observer ConcreteObserver1 Received Message:" + event); } }
package com.zengqingfa.designpattern.observer.spring; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * * @fileName: ConcreteObserver2 * @author: zengqf3 * @date: 2021-4-23 15:10 * @description: * ApplicationListener: Event listener interface, business logic of events encapsulated in the listener */ @Component public class ConcreteObserver2 implements ApplicationListener<Notice> { @Override public void onApplicationEvent(Notice event) { System.out.println("Observer ConcreteObserver2 Received Message:" + event); } }
Active Trigger Event
package com.zengqingfa.designpattern.observer.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * * @fileName: Client * @author: zengqf3 * @date: 2021-4-23 15:17 * @description: */ public class Client { public static void main(String[] args) { /** * Observer ConcreteObserver1 receives a message: com.zengqingfa.design pattern.observer.spring.Notice[source=spring observer mode] * Observer ConcreteObserver2 receives a message: com.zengqingfa.design pattern.observer.spring.Notice[source=spring observer mode] */ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); //ApplicationContext: Event source, where the publishEvent() method is used to trigger container events //Active Trigger context.publishEvent(new Notice("spring Observer mode")); } }
Container Start Trigger
package com.zengqingfa.designpattern.observer.spring; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * * @fileName: ApplicationContextHolder * @author: zengqf3 * @date: 2021-5-13 13:55 * @description: */ @Component public class ApplicationContextHolder implements ApplicationContextAware { @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //Trigger when container starts applicationContext.publishEvent(new Notice("spring Observer mode")); } }
package com.zengqingfa.designpattern.observer.spring; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * * @fileName: Client * @author: zengqf3 * @date: 2021-4-23 15:17 * @description: */ public class Client { public static void main(String[] args) { /** * Observer ConcreteObserver1 receives a message: com.zengqingfa.design pattern.observer.spring.Notice[source=spring observer mode] * Observer ConcreteObserver2 receives a message: com.zengqingfa.design pattern.observer.spring.Notice[source=spring observer mode] */ AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); } }