Observer mode

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);
    }
}

uml structure diagram

Tags: Java Design Pattern

Posted by unmash on Sun, 26 Sep 2021 22:55:52 +0530