How to refactor the garbage code we wrote before — observer pattern

Time:2020-10-24

How to refactor the garbage code we wrote before — observer pattern

First of all, let’s look at the GOF’s definition of observer pattern

There is a one to many relationship between multiple objects. When one object changes, the other objects will be informed of the change, which will affect the behavior of other objects

That is to say, when an object is about to change, it is necessary to inform other objects of the corresponding change behavior at the same time.

From the definition of this sentence, the focus is on two “objects” – the observer (multiple objects of the latter), and the observed (one object of the former). This is what we often call subscribers and publishers.

First of all, we use the most intuitive code to implement the above sentence.

Taking the eve of our senior three college entrance examination as the time background, in order to slow down the pressure of our study, the students in our class always collude with the students on duty on that day. In the last self-study class, the students on duty were on guard outside the classroom. We watched the movie “Shawshank’s redemption” in the classroom

//On duty student
public class StudentOnDuty {
    private List observers = new List();
    public void Notify() {
        foreach (var observer in observers) {
            observer.Update();
        }
    }

    public void Attach(StudentObserver observer) {
        observers.Add(observer);
    }

    Public string state = > "the head teacher is coming!!! ";
}

//Students in trouble
public class StudentObserver {
    private readonly StudentOnDuty _studentOnDuty;
    public StudentObserver(string studentTypeName, StudentOnDuty studentOnDuty) {
        _studentOnDuty = studentOnDuty;
        StudentTypeName = studentTypeName;
    }
    public void Update() {
        Console.WriteLine (studenttypename + "received a notification from the student on duty: +_ studentOnDuty.State +"Turn off the computer on the platform and pretend to open books, read books, do homework, etc.);
    }

    public string StudentTypeName { get; }
}
// Program
StudentOnDuty studentOnDuty = new StudentOnDuty();
studentOnDuty.Attach(new StudentObserver("marson shine", studentOnDuty));
studentOnDuty.Attach(new StudentObserver("summer zhu", studentOnDuty));
studentOnDuty.Notify();

This code is very simple, and it best reflects the definition of the observer concept.

So what’s wrong with the code above? In fact, it is obvious that the two classes are directly coupled (the student on duty should inform the students of attach one by one, and the students should also remember the state passed by the student on duty). That means that if one of the classes is changed, the whole logic will be affected. For example, some of our children’s shoes don’t like watching the movie we’re playing, so they will do other things, such as playing with mobile phones and reading novels. So forStudentObserver.Update()It’s about to change.

How to reconstruct?

When it comes to refactoring, we have an idea in mind to encapsulate the common parts and extract the abstract parts to deal with the changing parts.

The opening and closing principle tells us to close the modification and open to the newly added.

The dependency inversion principle tells us to rely on abstraction rather than concrete implementation.

So the first thing we need to do is abstract, extract the common parts. Obviously, as the informer, the duty student’s notification behavior is stable. So abstract it as an interface

public interface ISubject {
    object State { get;}
    void Notify();
}

Then the duty student has to inherit it to be called a notifier. Some people may say that the scene here does not need to be abstract because it is stable and only responsible for comrades and us. In fact, otherwise, the students on duty can’t stand outside the classroom all the time to help us watch, otherwise it’s too obvious. ~

Sometimes the students on duty will watch the climax of the movie with us. Ha ha. So just at this time, our head teacher came into the classroom. In addition to the classroom that suddenly became dead and the air was frozen, we took the informer’s identity card and changed it from the student on duty to the head teacher! So the observer must also be abstract. The observer has only one abstract responsibility – to receive the notice from the informer to make corresponding updates.

public interface IObserver {
    void Update();
}

So how to add subscribers and notifications to the on duty students. Because the added objects will change and are unstable, we should take corresponding measures to deal with this situation.

From the original direct addition of observation objects to “interface oriented” programming – first of all, the behavior of representing the informed group and making corresponding changes is abstracted into an interface. It has only one responsibility, that is, making changes, such as turning off the computer, opening a book and pretending to study by oneself.

public class StudentOnDuty: ISubject {
    private readonly List _observers = new List();
  
    public void AddObserver(IObserver observer)
    {
      	A kind of observers.Add (observer); // for simplicity, do not check the weight
    }
    public void Notify()
    {
	....
    }
    Public object state = > "turn off the computer, the teacher is coming!!! ";
}

public class ClassTeacher: ISubject {
    ...
    Public object state = > "I'm afraid of sudden silence!!! ";
    ...
}

So our observers also receive the message from the notifier, which can not be specific, but also need to be oriented to abstract programming. Because we don’t know what we are going to face is a notice from the duty student or the thunder storm class teacher.

//Students watching movies
public class MovieStudent: IObserver {
    private readonly ISubject _subject;
    public MovieStudent(ISubject subject) {
      	_subject = subject;
    }
    public void Update()
    {
      	Console.WriteLine ($"the students who watched the movie received the signal from the subject:{_ subject.State }Immediately open the book and pretend to study by oneself. ""
    }
}
//Students in charge of turning off the computer
public class ClosePlayerStudent: IObserver {
    //...
}

In this way, our client code is the same as the previous call. When we want to add the notifier of students who do other things, our client code does not need to be changed too much, just add specific implementation classes.

Optimization from another perspective

Do not be satisfied with the degree of refactoring at this time. In fact, if we continue to look at the interface oriented programming, we must implement it according to the contract of the interface. Especially for those who are obsessed with code cleanliness, the term “update” of the implementation class method of these observers is really inappropriate. The concept is too big and too general. Especially in this era of microservices, the responsibilities of each method representative are very clear, and we can see from the name that its behavior is the best. For example, if the student in charge of turning off the computer does what he or she does is turn off the computer and open the book to read, then the name of the method should be changed toClosePlayerThenOpenTheBook()Students who watch movies just need to open their books to readOpenTheBook()

So at this time, how can we inform them and act accordingly?

At this time, our delegation is about to appear. We can expose a delegate (or event) to the notifier, and let this delegate perform the update behavior of the specific observer.

public class ClassTeacher
{
    ...
    public Action UpdateEvent { get; set;}
    public void Update()
    {
      	UpdateEvent?.Invoke();
    }
  	...
}

Then the specific behavior operation will be transferred to the client

classTeacher.UpdateEvent += closePlayerStudent.ClosedPLayerThenOpenTheBook;
classTeacher.UpdateEvent += moiveStudent.OpenTheBook;

Why is this from another point of view, because this implementation has nothing to do with the observer pattern, because we can do it without these interfaces. In this way, we can separate the behavior of the observer and the informer.

Subscription publish mode

At first, I thought that the subscription publish mode was the observer mode, but it was different.

But in fact, they are still the same thing… Um ~ ~ Yes, that’s right.

I also looked at some materials on the Internet, and found that some people actually distinguish the subscription publish mode from the observer mode. The subscription publish model we usually talk about is different from the observer mode. The former can achieve complete isolation and zero coupling. Such as message middleware.

In fact, their ideas are the same, but the subscription publishing mode adds a message scheduling layer to deliver information, so that subscribers and publishers do not know each other’s existence.However, by changing one end, other dependent objects will be updated accordingly.