Design Mode 20 - Observer

Keywords: Design Pattern

Design Mode 20 - Observer

Summary

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Define a one-to-many dependency between objects. When an object's state changes, all dependent objects are notified and updated automatically. Also known as publish-subscribe mode, it is a good way to decouple one object change and automatically change another.

Simple implementation

JDK implementation

  • Define Observed

com.demo.design.pattern.behavioral.observer.Subject

public interface Subject {
    /**
     * Add an observer (if necessary, add a remove method to remove the observer interface and implement it in an abstract class)
     *
     * @param observer Observer
     */
    void add(Observer observer);

    /**
     * Changes in the observee's execution
     */
    void operation();
}

com.demo.design.pattern.behavioral.observer.AbstractSubject

public abstract class AbstractSubject implements Subject {
    /**
     * Observer objects, which can have many different observers performing different behaviors
     */
    private final List<Observer> observers = new ArrayList<>();

    @Override
    public void add(Observer observer) {
        observers.add(observer);
    }

    void notifyObservers() {
        // Wake up all observers and synchronize updates
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

com.demo.design.pattern.behavioral.observer.SubjectImpl

public class SubjectImpl extends AbstractSubject {
    @Override
    public void operation() {
        System.out.println("update self!");
        this.notifyObservers();
    }
}
  • Define Observed

com.demo.design.pattern.behavioral.observer.Observer

interface Observer {
    /**
     * update operation
     */
    void update();
}

com.demo.design.pattern.behavioral.observer.ObserverImpl

public class ObserverImpl implements Observer {
    @Override
    public void update() {
        System.out.println("observer has received and update");
    }
}
  • Use examples
@Test
public void observerPattern(){
    Subject sub = new SubjectImpl();
    sub.add(new ObserverImpl());
    sub.operation();
}

Source Parsing

JDK

java.util.Observer

/**
 * Accept notifications of changes to observed objects by implementing the Observer interface
 */
public interface Observer {
    /**
     * This method is called when the observed object changes. When a change occurs, the wake-up method of the observed object is called to notify all observers.
     *
     * @param   o     Observed object.
     * @param   arg   Wake Up Method Parameters for Observed Objects
     */
    void update(Observable o, Object arg);
}

java.util.Observable

/**
 * This class represents an observable object, or "data"
 * in the model-view paradigm. It can be subclassed to represent an
 * object that the application wants to have observed.
 * <p>
 * An observable object can have one or more observers. An observer
 * may be any object that implements interface <tt>Observer</tt>. After an
 * observable instance changes, an application calling the
 * <code>Observable</code>'s <code>notifyObservers</code> method
 * causes all of its observers to be notified of the change by a call
 * to their <code>update</code> method.
 * <p>
 * The order in which notifications will be delivered is unspecified.
 * The default implementation provided in the Observable class will
 * notify Observers in the order in which they registered interest, but
 * subclasses may change this order, use no guaranteed order, deliver
 * notifications on separate threads, or may guarantee that their
 * subclass follows this order, as they choose.
 * <p>
 * Note that this notification mechanism has nothing to do with threads
 * and is completely separate from the <tt>wait</tt> and <tt>notify</tt>
 * mechanism of class <tt>Object</tt>.
 * <p>
 * When an observable object is newly created, its set of observers is
 * empty. Two observers are considered the same if and only if the
 * <tt>equals</tt> method returns true for them.
 *
 * @author  Chris Warth
 * @see     java.util.Observable#notifyObservers()
 * @see     java.util.Observable#notifyObservers(java.lang.Object)
 * @see     java.util.Observer
 * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
 * @since   JDK1.0
 */
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();
    }
}

EventBus

Event bus, which provides the skeleton code to implement the observer mode. Google Guava EventBus is a well-known EventBus framework that supports not only asynchronous non-blocking but also synchronous blocking.

com.google.common.eventbus.EventBus

@Beta
public class EventBus {
    private static final Logger logger = Logger.getLogger(EventBus.class.getName());
    private final String identifier;
    private final Executor executor;
    private final SubscriberExceptionHandler exceptionHandler; 
    private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
    private final Dispatcher dispatcher;
    // ...
}

The observer mode implemented by the EventBus framework does not require an Observer interface to be defined. Any type of object can be registered in EventBus, and the @Subscribe annotation indicates which function in the class can receive messages sent by the observer.

Advantage

  • Abstract coupling between observer and observee

Whether increasing observers or being observed is very easy to expand, and you will be comfortable with system extensions.

  • Set up a trigger mechanism

The change of the observer causes the observer to change automatically. However, it is important to note that message notifications from one observer, multiple observers, Java are executed sequentially by default, and that if one observer gets stuck, the entire process gets stuck, which is synchronization blocking.

So asynchronous is not considered sequentially in the actual development. Asynchronous non-blocking can not only decouple modern codes, but also make full use of hardware resources to improve code execution efficiency.

There are also observer modes between different processes, usually based on message queues, for the interaction between observers and observers between different processes.

Use scenarios

  • Associate behavior scenarios. If a user sends a text message reminder after successful registration or login, he or she can send a text message by observing whether the registration or login is successful.

  • Event multilevel trigger scenario.

  • Cross-system message exchange scenarios, such as the processing mechanism of message queues.

Reference material

Posted by zbee on Wed, 01 Dec 2021 11:02:34 -0800