Java Description Design Patterns (11): Observer Patterns

Posted by baze on Sun, 15 Sep 2019 17:49:02 +0200

This article source code: GitHub. Click here || GitEE. Click here

I. Observer Model

1. Concept Description

The observer pattern is the object's behavior pattern, also known as the Publish/Subscribe pattern. The observer pattern defines a one-to-many dependency that allows multiple observer objects to simultaneously listen to a subject object, which notifies all observer objects when the state changes. Redis and common message middleware publishing and subscribing modes are based on this principle.

2. Core role

  • Abstract subject roles

The abstract subject role manages all observer objects in a unified way, and each topic can have one or more observers. An abstract topic provides an interface for adding and deleting observer objects. The abstract topic role is also called the Observable role.

  • Specific Thematic Roles

Store the relevant state in the specific observer object; notify all registered observers when the internal state of the specific subject changes. Specific subject roles are also called specific observer roles.

  • The role of abstract observer

Define an interface for all specific observers to update themselves when they are notified of the subject. This interface is called an update interface.

  • Specific Observer Role

Specific observer roles implement update interfaces required by abstract observer roles in order to synchronize their own state with the state of the subject. If necessary, the specific observer role can maintain a reference to a specific subject object.

3. Source Code Implementation

  • Message Push Mode

The subject object pushes the message of the subject to the observer, regardless of whether the observer needs it or not.

/**
 * Observer Design Patterns
 */
public class C01_Observer {
    public static void main(String[] args) {
        // Create subject objects
        ConcreteSubject subject = new ConcreteSubject();
        // Creating Observer Objects
        Observer observer1 = new ConcreteObserver("Observer A");
        Observer observer2 = new ConcreteObserver("Observer B");
        // Registered observer
        subject.attach(observer1);
        subject.attach(observer2);
        // Modify the subject state
        subject.change("New State !");
        /**
         * Topic Status: New State!
         *[Observer A: New State!
         *[Observer B) State: New State!
         */
    }
}
// Abstract subject roles
abstract class Subject {
    // Save registered observer objects
    private List<Observer> list = new ArrayList<>();
    /**
     * Registered Observer Object
     */
    public void attach (Observer observer){
        list.add(observer);
        System.out.println("Register an observer:"+observer.getClass().getName());
    }
    /**
     * Delete observer objects
     */
    public void delete (Observer observer){
        list.remove(observer);
        System.out.println("Delete an observer:"+observer);
    }
    /**
     * Notify all registered observers
     */
    public void notifyObserver (String newState){
        for (Observer observer : list) {
            observer.update(newState);
        }
    }
}
// Specific Thematic Roles
class ConcreteSubject extends Subject{
    private String state ;
    public String getState (){
        return state ;
    }
    public void change (String newState){
        state = newState;
        System.out.println("Theme state:"+state);
        //Notify each observer of a change in status
        this.notifyObserver(state);
    }
}
// The role of abstract observer
interface Observer {
    /**
     * Update interface
     */
    void update (String state);
}
// Specific Observer Role
class ConcreteObserver implements Observer{
    private String name ;
    // Observer state
    private String observerState ;
    public ConcreteObserver (String name){
        this.name = name ;
    }
    /**
     * Update the state of the observer to align it with the state of the target
     */
    @Override
    public void update(String state) {
        observerState = state ;
        System.out.println("["+this.name+"]state:"+observerState);
    }
}
  • Message pull mode

Subject objects convey a small amount of information when notifying the observer. If the observer needs the content of the message, it is equivalent to the observer pulling data from the subject object to get it from the observer.

The case is modified based on the above case. The observer obtains the topic of the subject, and only the topic of interest to himself, can he get the content further.

public class C02_Observer_Pull {
    public static void main(String[] args) {
        // Create subject objects
        ConcreteSubject1 subject = new ConcreteSubject1();
        // Creating Observer Objects
        Observer1 observer1 = new ConcreteObserver1("Observer A","JAVA");
        Observer1 observer2 = new ConcreteObserver1("Observer B","MySQL");
        // Registered observer
        subject.attach(observer1);
        subject.attach(observer2);
        /*
         * Modify the subject state
         * Topic Status: JAVA State!
         * [Observer A Status: JAVA State!
         * Topic Status: MySQL State!
         * [Observer B) Status: MySQL State!
         */
        subject.change("JAVA State !","JAVA");
        subject.change("MySQL State !","MySQL");
    }
}
abstract class Subject1 {
    // Save registered observer objects
    private List<Observer1> list = new ArrayList<>();
    /**
     * Registered Observer Object
     */
    public void attach (Observer1 observer){
        list.add(observer);
    }
    /**
     * Delete observer objects
     */
    public void delete (Observer1 observer){
        list.remove(observer);
        System.out.println("Delete an observer:"+observer);
    }
    /**
     * Notify all registered observers about the topic of the incoming message
     */
    public void notifyObservers (String msgTopic){
        for (Observer1 observer : list){
            observer.update(this);
        }
    }
}
class ConcreteSubject1 extends Subject1 {
    private String state ;
    private String msgTopic ;
    public String getState (){
        return state ;
    }
    public String getMsgTopic (){
        return msgTopic ;
    }
    public void change (String newState,String newMsgTopic){
        this.state = newState ;
        this.msgTopic = newMsgTopic ;
        System.out.println("Topic status:"+state);
        this.notifyObservers(msgTopic);
    }
}

interface Observer1 {
    /**
     * Update interface
     * @param subject Pass in the subject object, and the aspect obtains the status of the corresponding subject object.
     */
    void update(Subject1 subject);
}
class ConcreteObserver1 implements Observer1{
    private String name ;
    // Topic selection
    private String msgTopic ;
    // Observer state
    private String observerState ;
    public ConcreteObserver1 (String name,String msgTopic){
        this.name = name ;
        this.msgTopic = msgTopic ;
    }
    @Override
    public void update(Subject1 subject) {
        ConcreteSubject1 concreteSubject1 = (ConcreteSubject1)subject ;
        // Interest will be cancelled only if the topic is specified.
        if (concreteSubject1.getMsgTopic().equals(msgTopic)){
            observerState = concreteSubject1.getState();
            System.out.println("["+this.name+"]state:"+observerState);
        }
    }
}

4. Comparison of Two Models

Push mode assumes that the subject object knows the data that the observer needs and pushes directly, which makes it difficult for the observer object to reuse. Pull mode is that the subject object does not know what data the observer needs, and will pass itself to the observer and take the value as needed.

II. Application of JDK

In the java.utill class library of JAVA language, an Observable class and an Observer interface are provided to support the observer pattern in JAVA language.

1. Observer interface

This interface defines only one method, the update() method, which is called by the notifyObservers() method of the observed object when its state changes.

package java.util;
/**
 * A class can implement the <code>Observer</code> interface when it
 * wants to be informed of changes in observable objects.
*/
public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     */
    void update(Observable o, Object arg);
}

2. Observable class

Observed classes are subclasses of the java.util.Observable class. Java.util.Observable provides methods to support observer objects.

  • setChanged method: The state of the observer object has changed.
  • notifyObservers: Call the update() method of all registered observer objects.
package java.util;
public class Observable {
    private boolean changed = false;
    private Vector obs;
    /** Construct an Observable with zero Observers. */
    public Observable() {
    obs = new Vector();
    }
    /**Add an observer to the observer cluster*/
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }
    /** Delete an observer from the observer cluster */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
    notifyObservers(null);
    }
    /**
     * If the object changes (then the hasChanged method returns true)
     * Call this method to notify all registered observers, calling their update() method
     * Input this and arg as parameters
     */ 
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
    synchronized (this) {      
        if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    /** Gather the observers and empty them */
    public synchronized void deleteObservers() {
    obs.removeAllElements();
    }
    /** Set "Changed" to true */
    protected synchronized void setChanged() {
    changed = true;
    }
    /** Reset "changed" to false */
    protected synchronized void clearChanged() {
    changed = false;
    }
    public synchronized boolean hasChanged() {
    return changed;
    }
    public synchronized int countObservers() {
    return obs.size();
    }
}

3. Application cases

public class C03_Observer_JDK {
    public static void main(String[] args) {
        //Create the Observed Object
        MsgSource msgSource = new MsgSource();
        //Create an observer object and register the observed object
        MsgConsumer watcher = new MsgConsumer(msgSource);
        msgSource.setData("Hello,Java");
        msgSource.setData("Bye Java");
    }
}
class MsgSource extends Observable {
    private String data = "";
    public String getData() {
        return data;
    }
    public void setData(String data) {
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }
        notifyObservers();
    }
}
class MsgConsumer implements java.util.Observer {
    // Adding observers
    public MsgConsumer(Observable msgSource){
        msgSource.addObserver(this);
    }
    // State acquisition
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Message content:" + ((MsgSource)o).getData());
    }
}

Summary of Advantages and Disadvantages

The main function of the observer model is to decouple the object and isolate the observer from the observee.

Programs include multiple observers and multiple observers. Development and debugging are complex, and notifications of messages in Java are executed sequentially by default. Blocking the execution of an observer can affect the overall execution efficiency.

4. Source code address

GitHub·address
https://github.com/cicadasmile/model-arithmetic-parent
GitEE·address
https://gitee.com/cicadasmile/model-arithmetic-parent

Topics: Java MySQL github Redis