Java/Android Design Patterns Series (2) -- Observer Patterns

In this article, we will talk about the observer model, which is used frequently in real projects. The most commonly used parts of the observer model are GUI system, subscription-publishing system and so on. Because an important role of this model is decoupling, so that they rely less on each other, or even do not rely on each other. For GUI system, the UI of application is changeable, especially with the change of business or product requirements in the early stage, the application interface also changes frequently, but the business logic changes little. At this time, the GUI system needs a set of mechanisms to deal with this situation, so that the UI layer is decoupled from the specific business logic, and the observer mode comes into use at this time.

General Catalogue of Design Patterns

  Java/Android Design Patterns Series - Directories

Characteristic

The observer pattern defines one-to-many dependencies between objects, so that when an object's state changes, all its dependants are notified and updated automatically. It implements loose coupling between Subject and Observer. Subject only knows that the observer implements the Observer interface. Subject does not need to know who the specific class is, what it does or any other details. We can add new observers at any time. Because the only thing the subject relies on is a list of objects that implement the Observer interface, we can add observers at any time. Similarly, we can delete observers at any time, not to mention the type of observers. As long as we implement the Observer interface, Subject will only notify observers who implement the Observer interface. After loose coupling between Subject and Observer, neither code modification will affect the other side, provided that both sides comply with the specification of the interface.( Interface Isolation Principle).
Scenarios used in the observer pattern:

  • In the context of association behavior scenarios, it should be noted that association behavior is separable rather than a "combination" relationship.
  • Event multi-level trigger scenario;
  • Cross-system message exchange scenarios, such as message queues, event bus processing mechanisms.

The EventBus framework is a typical example of using the observer pattern.

UML class diagram

  
The uml class diagram of the observer pattern, as shown above, generally consists of four roles

  • Subject: An Abstract topic, that is, the role to be observed, stores references to all observer objects in a set, and each topic can have any number of observers. An abstract topic provides an interface to add and delete observer objects.
  • ConcreteSubject: A specific topic, in which the role stores the relevant state in a specific observer object and notifies all registered observers when the internal state of the specific topic changes. The specific subject role object is also called the specific observer role.
  • Observer: An Abstract observer, whose role is an abstract class of observers, defines an update interface that updates itself when notified of changes to a topic.
  • ConcreteObserver: A concrete observer that implements an update interface defined by the abstract observer role to update its own state when the state of the subject changes.

    There are several points to be noted.

    1. Subject and Observer are one-to-many relationships, that is to say, as long as the observer implements the Observer interface and registers himself in Subject, he can receive message events.
    2. Java API has built-in observer pattern classes: java.util.Observable class and java.util.Observer interface, which correspond to the roles of Subject and Observer respectively.
    3. Using the observer pattern class of Java API, it should be noted that the observer must call the setChanged() function before calling the notifyObservers() function to notify the observer, otherwise the observer cannot be notified.
    4. The disadvantage of using Java API is also obvious. Because Observable is a class, the disadvantage of java only allows single inheritance leads you to choose either the adapter pattern or the internal class when you want to acquire the attributes of another parent class at the same time, and because setChanged() function is the protected attribute, you can't make it unless you inherit Observable class. Using the attributes of this class also violates the principle of design patterns: use more combinations and use less inheritance.

Examples and source code

There are many ways to write the observer pattern, so long as we follow the idea of decoupling the observer and the observer, we can list three common methods in my development. First moduel is used as the parent module of secondmodule 1 and secondmodule 2. Finally, application takes secondmoduel 1 and secondmodule 2 as the parent module to form a diamond-shaped relationship.

Java API

Use Java API is actually written in terms of usage Java The. util.Observable class and the java.util.Observer interface construct the observer according to the uml class diagram. We simply implement the function of notifying secondmodule 1 of the change of data source in secondmodule 2 and letting secondmodule 1 print the corresponding log. Because secondmodule 1 and secondmodule 2 are peer-to-peer relationships, they cannot be invoked each other and can only communicate through the first module as a topic using the observer mode.
Subject is in use java API is actually the Observable class, so we inherit this class in the first module to implement a ConcreteSubject to save all observers, then implement the Observer interface in the second module 1 to generate an observer, and finally notify the first module data source change in the second module 2, and finally notify the second module 1 through the first module, the code is as follows :
Utilization in firstmoduel/DataObservable.class Singleton mode Management and notification of observers:

public class DataObservable extends Observable{

    private static volatile DataObservable instance;
    private DataObservable() {}

    public static DataObservable getInstance() {
        if (instance == null){
            synchronized (DataObservable.class) {
                if (instance == null){
                    instance = new DataObservable();
                }
            }
        }
        return instance;
    }

    public void notifyDataChanged(DataBean data) {
        setChanged();
        notifyObservers(data);
    }
}

First moduel/DataBean.class data class:

public class DataBean {
    public int temperature;
}

The second module 1/DataObserver. class determines that if it is the correct Observer rable, print the log:

public class DataObserver implements Observer {
    private static final String TAG = "DataObserver";

    @Override
    public void update(Observable observable, Object data) {
        if (observable instanceof DataObservable){
            if (data instanceof DataBean){
                Log.e(TAG, ((DataBean)data).temperature+"");
            }
        }
    }
}

secondmodule2/DataNotify.class

public class DataNotify {
    public static void notifyDataChanged(){
        DataBean bean = new DataBean();
        bean.temperature = (int) (Math.random() * 40);
        DataObservable.getInstance().notifyDataChanged(bean);
    }
}

Finally, call the corresponding method of DataNotify class in MainActivity to notify DataObserver of temperature data change.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    DataObservable.getInstance().addObserver(new DataObserver());
}

@Override
protected void onDestroy() {
    super.onDestroy();
    DataObservable.getInstance().deleteObserver(new DataObserver());
}
...
public void onClick(View v) {
    if (v.getId() == R.id.btn_data_change_1){
        DataNotify.notifyDataChanged();
    }
    ...
}

Finally, the correct log can be printed successfully, that is to say, the implementation. Android Communication between two module s of the same level.
Of course, if you feel the trouble of not using these two classes of Java API, you can implement them by yourself, and sometimes the maneuverability will be better than the Java API.

multi Observer

Sometimes we don't all need to implement the same Observer interface. According to the actual situation, we can register multiple Observers into Subject under the condition of complete decoupling, and only notify one Observer we want to notify according to the situation. It sounds confusing. In fact, it is clear from a uml diagram:
  
In fact, the most important part of this approach is the role of Subject, which takes on most of the workload. First, we implement several Observer roles:
firstmodule/IDataListenerOne.class

public interface IDataListenerOne {
    void OnDataChanged(DataBean data);
}

firstmodule/IDataListenerTwo.class

public interface IDataListenerTwo {
    void OnDataChanged(DataBean data);
}

Several very simple interfaces are used to communicate between secondmodule 1 and secondmodule 2. Next comes the most important Subject and ConcreteSubject roles:
firstmodule/IMultiDataObservable.class

public interface IMultiDataObservable {
    /**
     * Increase the number of observers
     */
    void addObserver(Object observer);

    /**
     * Delete the observer
     */
    void deleteObserver(Object observer) throws IllegalArgumentException;

    /**
     * Find the Observer
     */
    <T>ArrayList<T> findObserver(Class<T> clazz);
}

firstmodule/MultiDataObservable.class

public class MultiDataObservable implements IMultiDataObservable {

    private static volatile MultiDataObservable instance;
    private ArrayList<Object> observers;

    private MultiDataObservable() {
        observers = new ArrayList<>();
    }

    public static MultiDataObservable getInstance() {
        if (instance == null) {
            synchronized (MultiDataObservable.class) {
                if (instance == null) {
                    instance = new MultiDataObservable();
                }
            }
        }
        return instance;
    }

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

    @Override
    public void deleteObserver(Object observer) throws IllegalArgumentException {
        if (observer == null) {
            throw new IllegalArgumentException("observer must not be null");
        }
        if (!observers.remove(observer)) {
            throw new IllegalArgumentException("observer not registered");
        }
    }

    @Override
    public <T> ArrayList<T> findObserver(Class<T> clazz) {
        ArrayList<T> lists = new ArrayList<>();
        for (Object observer : observers) {
            if (clazz.isInstance(observer)) {
                lists.add(clazz.cast(observer));
            }
        }
        return lists;
    }
}

This class has three main methods, add and delete search, especially findObserver method, which finds Observer from the list of observers through the incoming Class object and returns it. It should be noted that the method to be used is Class.isInstance(Object object) method, rather than directly judging object. getClass () === clazz, because the latter is such a method. Judging that if an anonymous internal class approach is used, its class name will be in the form of DataCommunicate , so this approach is not feasible. Finally, in secondmodule 1 and secondmodule 2, only the corresponding method can be invoked according to the use. Take secondmodule 2 as an example:
secondmodule2/DataCommunicate.class

public class DataCommunicate {
    private static final String TAG = "DataCommunicate";

    private static IDataListenerTwo listenerTwo = null;

    public static void registerDataListenerTwo() {
        IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
        listenerTwo = new IDataListenerTwo() {
            @Override
            public void OnDataChanged(DataBean data) {
                Log.e(TAG, data.temperature + "");
            }
        };
        dataObservable.addObserver(listenerTwo);
    }

    public static void notifyDataListenerOne() {
        IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
        ArrayList<IDataListenerOne> lists = dataObservable.findObserver(IDataListenerOne.class);
        DataBean bean = new DataBean();
        bean.temperature = (int) (Math.random() * 40);
        for (IDataListenerOne listener : lists) {
            listener.OnDataChanged(bean);
        }
    }

    public static void unRegisterDataListenerTwo() {
        IMultiDataObservable dataObservable = MultiDataObservable.getInstance();
        dataObservable.deleteObserver(listenerTwo);
    }
}

Finally, we can communicate successfully. The advantage of this method is that we can customize Observer interface completely. The method in the interface can be defined arbitrarily and has great freedom. It is convenient to manage all the observers in a unified way.

EventBus

Event Bus is a typical observer mode use case. List three common frameworks I use:

They are not complicated in principle. There are a lot of information on the Internet such as comparison, for example. Otto frameworks are mostly less efficient than EventBus For example, I use the most widely used green robot/EventBus to realize the communication between secondmodule 1 and secondmodule 2.
secondmodule1/EventNotifier.class

public class EventNotifier {
    private static volatile EventNotifier instance;

    private EventNotifier() {
    }

    public static EventNotifier getInstance() {
        if (instance == null) {
            synchronized (EventNotifier.class) {
                if (instance == null) {
                    instance = new EventNotifier();
                }
            }
        }
        return instance;
    }

    public void sendEvent() {
        DataBean bean = new DataBean();
        bean.temperature = (int) (Math.random() * 40);
        EventBus.getDefault().post(bean);
    }
}

secondmodule2/EventObserver.class

public class EventObserver {
    private static final String TAG = "EventObserver";
    private static volatile EventObserver instance;

    private EventObserver() {

    }

    public static EventObserver getInstance() {
        if (instance == null) {
            synchronized (EventObserver.class) {
                if (instance == null) {
                    instance = new EventObserver();
                }
            }
        }
        return instance;
    }

    public void registerObserver() {
        EventBus.getDefault().register(this);
    }

    public void unRegisterObserver() {
        EventBus.getDefault().unregister(this);
    }

    @Subscribe
    public void onEventMainThread(DataBean bean) {
        Log.e(TAG, bean.temperature + "");
    }
}

In particular, the onEventMainThread function needs to be annotated with @Subscribe, otherwise it will not work. Finally, in MainActivity, you can call the related functions:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    EventObserver.getInstance().registerObserver();
}

@Override
protected void onDestroy() {
    ...
    EventObserver.getInstance().unRegisterObserver();
}

@Override
public void onClick(View v) {
    ...
    else if (v.getId() == R.id.btn_data_change_3) {
        EventNotifier.getInstance().sendEvent();
    }
}

Finally, of course, it can succeed!

summary

There are many scenarios used in the actual development process of the observer pattern, such as communication between two fragment s in Activity, communication between two modules of the same level described above, communication between upper module and lower module, real-time synchronization of data between two activities and so on. After using the observer pattern, the idea is simple. Single and clear, good maintainability, and most importantly, low coupling is conducive to expansion. android Listview and Adapter in official source code are also typical examples of using the observer pattern.
The advantages and disadvantages of the observer model are summarized.

  • The Abstract coupling between the observer and the observee is strong in responding to business changes and scalability.
  • The observer mode supports broadcast communication. The observee notifies all registered observers.

At the same time, the observer model has some shortcomings:

  • Observer mode sometimes has problems of development efficiency and operation efficiency. Debugging and development become relatively complex when the program includes one observer and more than one observer.
  • If the number of observers is large, it will take some time for all observers to notify.
  • When the observer notifies the observer in the observer mode, the default is sequential execution, so when an observer performs slowly or Carton will affect the overall execution efficiency, if this is the case, asynchronous execution is the best way to deal with it.
  • If there is a cyclic dependency between observers, the observer will trigger a cyclic call between them, leading to system crash. This should be paid special attention to when using the observer mode.

Source download

https://github.com/Jakey-jp/Design-Patterns/tree/master/ObserverPattern

Quote

http://www.cnblogs.com/zemliu/p/3313782.html
http://blog.csdn.net/luoxianfeng89/article/details/50395901
http://blog.csdn.net/jason0539/article/details/45055233
http://ju.outofmemory.cn/entry/226121
http://www.cnblogs.com/yydcdut/p/4651208.html
http://blog.csdn.net/harvic880925/article/details/40787203
http://bianchengzhe.com/DesignPattern/neirong/221.html

Keywords: Java less Android Attribute

Added by mikeduke on Thu, 30 May 2019 22:36:00 +0300