[design mode] observer mode: can a registration function also use design mode?

Posted by mariolopes on Sun, 07 Jun 2020 09:45:53 +0200

Life in the world is like living among thorns, the heart does not move, people do not move, do not hurt, if the heart is moving, people move, hurt their body, hurt their bones, so we can experience all kinds of pain in the world

catalog


Due to the impact of the epidemic, the streets on weekends are not as busy as they used to be. Although it's much better than in the past few months, I still like to stay at home. The first is because of safety, the second is because of poverty, and I accidentally say the truth again. Since it's the end of the week, losers like me must be watching anime, playing games and enjoying themselves. I'm happy Need to have fun, then why do I blog again? That's because my colleague Xiao Ming worked overtime today and asked me a simple but not simple question. I think you may encounter it at work, so I'll record it here, showtime.

My fighting dream

On Saturday morning, I got up to have a look at the time, grasps the grass, it's 10 o'clock, the Douluo continent has been updated, and Haotian is going to have a tianchui Pope, so I quickly turned on the computer, wow, it's really good-looking. Although the plot ahead is a bit delayed, at the end of the day, Haotian Chui and BGM (broken cocoon) were on fire, but at this time, Xiaoming, who was working overtime, sent me a lethal 13th company in wechat Elder brother, please help me. I have a problem. I don't know how to solve it. I asked elder brother for help. I was interrupted when I was watching the wonderful video. Although I was upset, the video was over. I asked what the problem was. It was basically like this: the registration function he was responsible for now needs to add new functions. What is it? Yes, it has been added many times before, so Xiao Ming will come to me. He said that the method of registration has been several hundred lines now, and he is very tired. If there are functions to be added later, this method can't be looked down on. Who calls me a good colleague who is willing to help others? Today, I will give Xiao Ming a lesson. The theme is: observer mode.

Sad Xiao Ming

Why is Xiaoming sad? A registration function has been changed more than 5 times by the product. It's either a new function or a function added before. I feel that the product is on the verge of death. Hahaha, OK, let's start today's theme. What's the relationship between observer mode and registration? Don't worry. Let's take a look at the problems Xiaoming has encountered. The first version of Xiaoming's product registration: there's nothing to say about this version. It's a common registration function, which allows users to log in. Xiaoming divides three times, five times and two times. It's done in a short time. But after a while, the product hopes to push a system use tutorial to users after the user's registration is successful. Xiaoming thinks This soeasy, and then the registration method added the push message function, but after a period of time, the product found Xiaoming again, saying: now is the key period of our app, as long as the new registered users, send a month's Limited VIP directly, Xiaoming is a little impatient, but there is no way, the product said, can you not add it? Xiao Ming added it.

After adding this function, the product didn't bother Xiaoming for a long time. Xiaoming thought that the registration function was finished, but what Xiaoming didn't expect was that the nightmare just started. Today, Xiaoming found the product.

Product: Xiaoming, are you busy? Discuss a need with you if you are not busy.

Xiaoming's face changed instantly when he heard this, but he still said: not busy.

Product: I want to add two new features to the registration function.

Xiaoming: what's the function?

Product: I want users to check their location through their mobile phone number when they register.

Xiaoming is broken. There is no end to it. Is it necessary to add so many functions to a registration? Product very serious answer: it's necessary. In the future, there may be an increase or a possible cancellation of the one month limit VIP. Hearing this, Xiaoming shed helpless tears. It's too difficult for me.

But there's no way. Complaints come from complaints. Functions still have to be done. Because of the added functions of the product, Xiaoming has changed a simple registration method into a very complex one. Now he's not happy with it. So he found me and asked me if there is any more elegant method, which can not only dynamically increase demand, but also simplify the registration method Shan Ming, today I told him how to use the observer model to solve the dynamic demand problem.

What is the observer model?

Observer pattern (sometimes called Model View pattern, source listener pattern or subordinate pattern) is a kind of software design pattern. In this mode, a target object manages all observer objects that are dependent on it and actively notifies when its own state changes. This is usually done by calling the methods provided by the observers. This mode is usually used to implement the event processing system (Baidu Encyclopedia).

This kind of explanation is abstract at first sight. Can you understand it after reading it? Anyway, I can't understand. What is the observer model? Have you heard about the publish and subscribe mode? It is a simple point of observer pattern: define a one to many dependency between objects. When an object's state changes, all dependent objects will be notified automatically. It's much easier to understand. You can combine the above registration function. Do you think of how to implement it?

Let's take a look at the simple use of observer mode, and then optimize it with the above registration function.

Upper code

/**
 * Observer interface
 */
public interface MyObserver {

    /**
     * Do something
     * @param msg
     */
    void dosomething(Msg msg);
}



public interface MySubject {

    /**
     * Registered observer
     */
    void reg(MyObserver myObserver);

    /**
     * Delete observer
     */
    void detele(MyObserver myObserver);

    /**
     * Notify all observers
     */
    void notify(Msg msg);
}




public class MyConcreteSubject implements MySubject {

    private List<MyObserver> myObservers = new ArrayList<MyObserver>();
    @Override
    public void reg(MyObserver myObserver) {
        myObservers.add(myObserver);
    }

    @Override
    public void detele(MyObserver myObserver) {
        myObservers.remove(myObserver);
    }

    @Override
    public void notify(Msg msg) {
        if(myObservers.size() > 0){
            for (MyObserver observer: myObservers) {
                observer.dosomething(msg);
            }
        }
    }
}





public class MyObserverA implements MyObserver {
    @Override
    public void dosomething(Msg msg) {
        System.out.println("I am: MyObserverA,I was executed; execution parameters:"+msg);
    }
}




public class MyObserverB implements MyObserver {
    @Override
    public void dosomething(Msg msg) {
        System.out.println("I am: MyObserverB,I was executed; execution parameters:"+msg);
    }
}




public class MyObserverC implements MyObserver {
    @Override
    public void dosomething(Msg msg) {
        System.out.println("I am: MyObserverC,I was executed; execution parameters:"+msg);
    }
}





public class ObserverTest {

    public static void main(String[] args) {
        MySubject mySubject = new MyConcreteSubject();
        mySubject.reg(new MyObserverA());
        mySubject.reg(new MyObserverB());
        mySubject.reg(new MyObserverC());
        Msg msg = new Msg();
        msg.setId(1);
        msg.setName("Xiaoming");
        mySubject.notify(msg);
    }
}



public class Msg {

    private Integer id;

    private String name;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Msg{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Above is a simple observer pattern template. Is it very simple? Yes, it's so simple. Let's see what the printing result is

My name is: MyObserverA, I was executed; execution parameters: Msg{id=1, name = Xiaoming '}
My name is: MyObserverB, I was executed; execution parameters: Msg{id=1, name = Xiaoming '}
My name is: MyObserverC, I was executed; execution parameters: Msg{id=1, name = Xiaoming '}

Process finished with exit code 0

Is there any understanding of the observer model now? What does that have to do with the registration above? You can think about whether the location of the message push, VIP gift and mobile phone number saved during registration corresponds to MyObserverA, MyObserverB and MyObserverC. Yes, we can separate these three functions from the registration function, so that the registration function will become very simple. We can tell the subscribers through the way of publishing and subscribing Well, you need to do it.

Registration function transformation

After seeing the above case, you must know how to modify the registration code. Let's take a look at the registration function.

Primitive writing

public class UserReg {

    /**
     * Register, save user information
     * @param user
     */
    private void saveUser(User user){
        System.out.println("Save user information to database, registration succeeded!");
    }


    /**
     * Time limited VIp
     * @param user
     */
    private void giveVip(User user){
        System.out.println("Gift time limit VIp success!");
    }

    /**
     * Send a message using the guide
     * @param user
     */
    private void sendMsg(User user){
        System.out.println("Push a system message to the user. The content of the message is the user guide!");
    }

    /**
     * Query the home location of the user's mobile phone number and save it to the database
     * @param user
     */
    private void savePhoneRegion(User user){
        System.out.println("Query the home location of the user's mobile phone number and save it to the database,It is convenient for colleagues in data Department to do data analysis!");
    }

    public void reg(User user){
        //register
        saveUser(user);
        //Time limited VIp
        giveVip(user);
        //Send a message using the guide
        sendMsg(user);
        //Query the home location of the user's mobile phone number and save it to the database
        savePhoneRegion(user);
    }
}





public class User {

    /**
     * cell-phone number
     */
    private String phone;

    /**
     * password
     */
    private String pwd;

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "phone='" + phone + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}



public class UserTest {

    public static void main(String[] args) {
        UserReg userReg = new UserReg();
        User user = new User();
        user.setPhone("18888888888");
        user.setPwd("123456");
        userReg.reg(user);
    }
}

Run the main function

Save user information to database, registration succeeded!
Push a system message to the user. The content of the message is the user guide!
Query the user's mobile phone number, save it to the database, so that colleagues in the data department can do data analysis!

Process finished with exit code 0

Has Xiaoming realized the registration function? Yes, there is nothing wrong with it, but I don't recommend it. Do you know why? Now there are only three. What about the new functions in the back? Are we still adding in the reg() method? This obviously violates the single principle in the design principle. A registration method does too many things unrelated to registration, so we need to separate them. The design principle is often more important than the design pattern, so we use the observation pattern to transform the registration function.

public interface RegSubject {

    /**
     * Registered observer
     */
    void reg(MyRegObserver myRegObserver);

    /**
     * Delete observer
     */
    void detele(MyRegObserver myRegObserver);

    /**
     * Notify all observers
     */
    void notify(User user);
}



public class MyRegSubject implements RegSubject {

    private List<MyRegObserver> myObservers = new ArrayList<MyRegObserver>();
    @Override
    public void reg(MyRegObserver myRegObserver) {
        myObservers.add(myRegObserver);
    }

    @Override
    public void detele(MyRegObserver myRegObserver) {
        myObservers.remove(myRegObserver);
    }

    @Override
    public void notify(User user) {
        if(myObservers.size() > 0){
            for (MyRegObserver observer: myObservers) {
                observer.regHandler(user);
            }
        }
    }
}


/**
 * Observer interface
 */
public interface MyRegObserver {

    /**
     * Do something
     * @param user
     */
    void regHandler(User user);
}


public class MsgNotificationObserver implements MyRegObserver {
    @Override
    public void regHandler(User user) {
        System.out.println("Push a system message to the user. The content of the message is the user guide! ; execution parameters:"+user);
    }
}


/**
 * Time limited vip
 */
public class LimitVipObserver implements MyRegObserver {
    @Override
    public void regHandler(User user) {
        System.out.println("Gift time limit VIp success! Execution parameters:"+user);
    }
}


/**
 * Save the user's mobile phone number home
 */
public class PhoneRegionObserver implements MyRegObserver {
    @Override
    public void regHandler(User user) {
        System.out.println("Query the home location of the user's mobile phone number and save it to the database,It is convenient for colleagues in data Department to do data analysis! Execution parameters:"+user);
    }
}


public class User {

    /**
     * cell-phone number
     */
    private String phone;

    /**
     * password
     */
    private String pwd;

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "phone='" + phone + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

public class UserReg {

    /**
     * Register, save user information
     * @param user
     */
    private void saveUser(User user){
        System.out.println("Save user information to database, registration succeeded!");
    }

    public void reg(User user){
        //register
        saveUser(user);
        RegSubject regSubject = new MyRegSubject();
        regSubject.reg(new MsgNotificationObserver());
        regSubject.reg(new LimitVipObserver());
        regSubject.reg(new PhoneRegionObserver());
        regSubject.notify(user);
    }
}


public class UserTest {

    public static void main(String[] args) {
        UserReg userReg = new UserReg();
        User user = new User();
        user.setPhone("18888888888");
        user.setPwd("123456");
        userReg.reg(user);
    }
}



Execute the main() function

Save user information to database, registration succeeded!
Push a system message to the user. The content of the message is the user guide! ; execution parameters: User{phone='18888888888', pwd='123456'}
Time limited VIp gift succeeded! Execution parameters: User{phone='18888888888', pwd='123456'}
Query the user's mobile phone number, save it to the database, so that colleagues in the data department can do data analysis! Execution parameters: User{phone='18888888888', pwd='123456'}

Process finished with exit code 0

Although the functions unrelated to registration have been extracted, there is still a problem that needs to be solved, that is, new is needed in the registration method Observer class, this will certainly make many developers very unhappy. If using observer mode requires such new, it is not as good as the previous writing method. Yes, in the reg() method, new observer is not a good way, which can definitely be solved. Do you know how to solve this problem?

We just need to move the method in MyRegSubject to UserReg class, and modify it as follows:

public class UserReg {

    private List<MyRegObserver> myObservers = new ArrayList<MyRegObserver>();

    /**
     * Add all observers at once
     * @param myObservers
     */
    public void regAll(List<MyRegObserver> myObservers) {
        myObservers.addAll(myObservers);
    }

    /**
     * Register, save user information
     * @param user
     */
    private void saveUser(User user){
        System.out.println("Save user information to database, registration succeeded!");
    }

    public void reg(User user){
        //register
        saveUser(user);
        for(MyRegObserver myRegObserver : myObservers){
            myRegObserver.regHandler(user);
        }
    }
}

This is to hand over the management of the observer to UserReg. Then you may have doubts. Does it need to be external new? Yes, although I need new, I can inject these observers into UserReg when the program starts. We can go to the ioc container to get the bean directly. The subsequent changes only need to modify the corresponding observers, and do not need to modify the registration method. Although it still violates the opening and closing principle, this writing method can be large It is recommended to reduce the bug s of the program greatly.

When Xiaoming and I talked about this, he seemed to understand, nodded, and then said, "brother, can you set these observers to be asynchronous?"? I found that user registration does not need the results of these three observers. If it can be asynchronous, it will greatly improve the efficiency of registration. Grasps the grass, excessively, but looks at Xiaoming to the knowledge desire, I complete him, is giving him a lesson, what is called: asynchronous non blocking observer pattern.

Asynchronous nonblocking observer mode

Yes, you didn't hear me wrong. It's asynchronous non blocking observer mode. What do you mean? As the name implies, it is to make the execution of the observer asynchronous, and the execution of the main thread is not affected by their execution.

If it's just a simple observer mode, create a new thread directly in the observer method, and their execution will be asynchronous. The implementation is as follows:

/**
 * Time limited vip
 */
public class LimitVipObserver implements MyRegObserver {
    @Override
    public void regHandler(User user) {
        new Thread(() -> 
                System.out.println("Gift time limit VIp success! Execution parameters:"+user)
        ).start();

    }
}

I will not write for the other two observers. The same writing method enables asynchronous non blocking observer mode. You may say that this is too simple, but there is a problem here. Do you know what it is? Yes, that is, threads are frequently created and destroyed. It takes time for threads to be created and destroyed, so we should be careful when using this method. Can we optimize it? Of course, you only need to give threads to the thread pool management. Here I will not give the use code. It is relatively simple. The implementation of a non blocking observer mode I want to introduce is not these two ways, so you can understand these two ways.

EventBus

EventBus is a very famous "event bus" framework of Google Guava, which supports synchronous blocking and asynchronous non blocking modes respectively.

It allows us to omit the MyRegObserver interface and directly give it to EventBus for management. Let's use EventBus together to transform synchronous registration into asynchronous registration.

public class UserReg {

    private EventBus eventBus;

    public UserReg() {
         eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10)); // Asynchronous non blocking mode
    }

    /**
     * Add all observers at once
     * @param myObservers
     */
    public void regAll(List<Object> myObservers) {
        for(Object myObserver : myObservers){
            eventBus.register(myObserver);
        }
    }

    /**
     * Register, save user information
     * @param user
     */
    private void saveUser(User user){
        System.out.println("Save user information to database, registration succeeded!");
    }

    public void reg(User user){
        //register
        saveUser(user);
        eventBus.post(user);
    }
}


public class MsgNotificationObserver  {

    @Subscribe
    public void regHandler(User user) {
        System.out.println("Push a system message to the user. The content of the message is the user guide! ; execution parameters:"+user);
    }
}

public class LimitVipObserver  {

    @Subscribe
    public void regHandler(User user) {
        System.out.println("Gift time limit VIp success! Execution parameters:"+user);
    }
}


public class PhoneRegionObserver {

    @Subscribe
    public void regHandler(User user) {
        System.out.println("Query the home location of the user's mobile phone number and save it to the database,It is convenient for colleagues in data Department to do data analysis! Execution parameters:"+user);
    }
}

We created an asynchronous non blocking EventBus (New asynceventbus( Executors.newFixedThreadPool (10)))
eventBus.register(myObserver): register our observers with the EventBus.
eventBus.post(user): send the message through eventbus.
@Subscribe: the method modified by him can receive eventBus.post() sent message.

We found that as long as the party decorated by @ Subscribe receives the event sent by eventBus, will the message be consumed by the observer who does not belong to the registered event? It depends on how you define parameters. eventBus determines its consumers by parameters. Here I will focus on the parameters.
We have three bean s: UserA, UserB, and UserC. UserA is the parent class of UserB. What happens when we use them as parameters?

UserA userA = new UserA ();
post(userA);
 
UserB userB = new UserB();
post(userB); 

UserC userC = new UserC();
post(userC );

post(userA): only the observation class with parameter UserA will receive it.
post(userB): as long as the receiving parameters of the defined observation class are: UserA, UserB; it will receive
post(userC): only the observation class with parameter UserC will receive it.

Therefore, you must pay attention to this point when using. Otherwise, there will be a post(), multiple different observation consumption situations.

It wasn't EventBus before. Does it support synchronization? How did he achieve it? Simply, look at the code:

public UserReg() {
         //eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10)); / / asynchronous non blocking mode
        eventBus = new EventBus();//Synchronous blocking mode
    }

Application scenario

Well, the implementation of observer mode is almost the same. In fact, we often encounter observer mode in our development, such as mail subscription, but we need to pay attention to different demand scenarios, with different implementation methods, such as synchronous blocking mode / different step non blocking mode, etc., which can also be implemented across processes.

Therefore, the observer pattern is widely used, and it is also a very common design pattern among the 23 design patterns.

summary

Although the observer mode can decouple the code, do not overuse it. For example, if the above registration function is successful, only the user guide message is pushed, and there is no possibility of adding new functions in the future. At this time, if the observer mode is forcibly used, it is a bit of a butcher's knife. Compared with the design mode, the design principle is more important, even if it is When using the design pattern, we should also try to meet the design principles of the code, such as the single principle of the class, the opening and closing principle, the Richter replacement principle, the interface isolation principle, etc. if the design pattern is separated from the design principle, then the pattern is meaningless.

Topics: Database Mobile Google