The of Java design patterns -- observer pattern

1. What is observer mode?

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

Observer Design Pattern: define a one to many dependency between objects. When the state of an object changes, all dependent objects will be notified and updated automatically.

Human words: also known as publish subscribe mode, it can well decouple the change of one object and automatically change another object.

2. Observer mode definition

① Subject, observed

Define the responsibilities that must be realized by the observed. It must be able to dynamically add and cancel observers. It is generally an abstract class or an implementation class, which only performs the duties that the observer must implement: managing the observer and notifying the observer.

② Observer observer

After receiving the message, the observer performs an update operation to process the received information.

③ Concrete subject, concrete subject

Define the observer's own business logic and what events to notify.

④ Concrete observer

The processing response of each observer after receiving the message is different, and each observer has its own processing logic.

3. Observer mode generic code

/**
 * Observer
 */
public interface Observer {
    // Update method
    void update();
}
/**
 * Specific observer
 */
public class ConcreteObserver implements Observer{
    @Override
    public void update() {
        System.out.println("Receive the information and process it");
    }
}
/**
 * Observed
 */
public abstract class Subject {
    // Define an array of observers
    private List<Observer> obsList = new ArrayList<>();

    // Add an observer
    public void addObserver(Observer observer){
        obsList.add(observer);
    }

    // Delete an observer
    public void delObserver(Observer observer){
        obsList.remove(observer);
    }

    // Notify all observers
    public void notifyObservers(){
        for (Observer observer : obsList){
            observer.update();
        }
    }
}
/**
 * Specific observer
 */
public class ConcreteSubject extends Subject{
    // Specific business
    public void doSomething(){
        super.notifyObservers();
    }
}
public class ObserverClient {

    public static void main(String[] args) {
        // Create an observer
        ConcreteSubject subject = new ConcreteSubject();
        // Define an observer
        Observer observer = new ConcreteObserver();
        // The observer observes the observed
        subject.addObserver(observer);
        subject.doSomething();
    }
}

4. JDK implementation

Under the java.util package of JDK, we have provided an abstract implementation of observer mode. You can see that the internal logic is actually similar to that described above.

Observer java.util.Observer

Observed java.util.Observable

5. Instance

Users register. After registration, a welcome email will be sent.

5.1 general implementation

public class UserController {

    public void register(String userName, String passWord){
        // 1. The password is saved in the database according to the user name
        Long userId = saveUser(userName, passWord);
        // 2. Send a welcome email if there is a result of the previous step
        if(userId != null){
            Mail.sendEmail(userId);
        }
    }


    public Long saveUser(String userName, String passWord){
        return 1L;
    }
}

The above registration interface implements two things: registering and sending e-mail, which obviously violates the principle of single responsibility. However, assuming that the registration requirements change frequently, there is no problem in writing this. However, if the requirements change, such as not only sending e-mail but also sending SMS, the register interface will become very complex.

How should it be simplified? Yes, it's the observer mode.

5.2 observer mode implementation

We directly apply the implementation of JDK.

import java.util.Observable;

/**
 * User login - observed
 */
public class UserControllerObservable extends Observable {

    public void register(String userName, String passWord){
        // 1. The password is saved in the database according to the user name
        Long userId = saveUser(userName, passWord);
        // 2. Notify all observers if there are results from the previous step
        if(userId != null){
            super.setChanged();
            super.notifyObservers(userName);
        }
    }

    public Long saveUser(String userName, String passWord){
        return 1L;
    }

}
import java.util.Observable;
import java.util.Observer;

/**
 * Send mail - observer
 */
public class MailObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Send mail:" + arg + "Welcome");
    }
}
/**
 * Send SMS - observer
 */
public class SMSObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Send SMS:" + arg + "Welcome");
    }
}

Test:

public class UserClient {
    public static void main(String[] args) {
        UserControllerObservable observable = new UserControllerObservable();
        observable.addObserver(new MailObserver());
        observable.addObserver(new SMSObserver());
        observable.register("Zhang San","123");
    }
}

After rewriting the observer mode, users can register later. Even if other operations are added, we only need to add an observer, and the registration interface register will not be changed.

5.3 asynchronous mode optimization

Back to the previous picture:

There are two steps after registration: sending e-mail and sending SMS. After we rewrite the above through the observer mode, although the process is very clear, we find that the two steps are executed in sequence, but in fact, there is no sequence. Therefore, we can change to the asynchronous mode to increase the execution efficiency.

/**
 * Send mail - observer
 */
public class MailObserver implements Observer {
    
    private Executor executor = Executors.newFixedThreadPool(2);

    @Override
    public void update(Observable o, Object arg) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("Send mail:" + arg + "Welcome");
            }
        });

    }
}

5,EventBus

Translated as "event bus", it provides skeleton code to implement observer mode. Based on this framework, we can easily implement the observer pattern in our own business scenarios without developing from scratch. Among them, Google Guava EventBus is a well-known EventBus framework. It supports not only asynchronous non blocking mode, but also synchronous blocking mode.

PS: Google Guava is a particularly easy-to-use toolkit, and the code in it is elegant. You can study the source code if you are interested.

https://github.com/google/guava

Let's use the above example to illustrate how to use EventBus:

① , such as Guava bag

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1.1-jre</version>
</dependency>

② . specific codes are as follows:

import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;

import java.util.List;
import java.util.concurrent.Executors;

public class UserController {
    private EventBus eventBus;

    public UserController(){
        eventBus = new AsyncEventBus(Executors.newFixedThreadPool(2));
    }

    /**
     * Note: the generic parameter is Object, not the interface Observer
     * @param observerList
     */
    public void setObserverList(List<Object> observerList){
        for(Object observer : observerList){
            eventBus.register(observer);
        }
    }

    public void register(String userName, String passWord){
        // 1. The password is saved in the database according to the user name
        Long userId = saveUser(userName, passWord);
        // 2. Notify all observers if there are results from the previous step
        if(userId != null){
            eventBus.post(userName);
        }
    }


    public Long saveUser(String userName, String passWord){
        return 1L;
    }
}
import com.google.common.eventbus.Subscribe;

/**
 * Send mail - observer
 */
public class MailObserver{

    @Subscribe
    public void sendMail(String userName) {
        System.out.println("Send mail:" + userName + "Welcome");
    }
}
import com.google.common.eventbus.Subscribe;

/**
 * Send SMS - observer
 */
public class SMSObserver{

    @Subscribe
    public void sendSMS(String userName) {
        System.out.println("Send SMS:" + userName + "Welcome");
    }
}

Test:

public class EventBusClient {
    public static void main(String[] args) {
        UserController userController = new UserController();
        List<Object> observerList = new ArrayList<>();
        observerList.add(new MailObserver());
        observerList.add(new SMSObserver());
        userController.setObserverList(observerList);
        userController.register("Zhang San","123");
    }
}

Compared with the Observer mode written from scratch, the Observer mode implemented by the EventBus framework has roughly the same implementation idea in terms of large process. Observers need to be defined, registered through the register() function, and sent messages to observers by calling a function (such as the post() function in EventBus) (in EventBus, messages are called event events). However, there are some differences in implementation details. Based on EventBus, we do not need to define the Observer interface. Objects of any type can be registered in EventBus. The @ Subscribe annotation is used to indicate which function in the class can receive messages sent by the Observer.

6. Advantages of observer mode

① There is abstract coupling between, observer and observed

Whether it is to add observers or observed, it is very easy to expand, and it will be handy in system expansion.

② Establish a set of trigger mechanism

The change of the observed causes the automatic change of the observer. However, it should be noted that the message notification of Java is executed sequentially by default for one observed and multiple observers. If one observer gets stuck, the whole process will get stuck, which is synchronous blocking.

Therefore, there is no sequential consideration in the actual development. Asynchronous non blocking can not only realize code decoupling, but also make full use of hardware resources and improve the execution efficiency of code.

In addition, there is the inter process observer mode, which is usually implemented based on message queue, which is used to realize the interaction between observers and observers between different processes.

7. Observer mode application scenario

① Associated behavior scenarios.

② Events and multi-level triggering scenarios.

③ Cross system message exchange scenarios, such as the processing mechanism of message queue.

Posted on Fri, 03 Dec 2021 21:13:07 -0500 by shinstar