Observer model

Writing in the front

define

Wiki: The Observer pattern is a type of software design pattern. In this mode, a target object manages all observer objects that depend on it and actively notifies itself when its state changes. This is usually done by calling the methods provided by each observer. This mode is commonly used in real-time event processing systems.

In simple terms, you can imagine multiple objects observing an object at the same time. When the observed object changes, they are notified and can do something about it.

Say a few words

Laravel is an event-driven framework. All operations are decoupled by events to implement a simple observer pattern. A typical use of this pattern is the database model. Will trigger the event (created/updated/does…).

For starters, just create an observer object in the Observers directory, and add observer associations. This happens automatically when they modify the model.

The observer pattern is often used in practical development. It mainly exists in the underlying framework and is decoupled from the business logic, which only needs to implement various observer observed.

The class diagram

(Figure source network)

role

  • Abstract observer

  • Concrete observer

  • Abstract observed

  • Specific observed

Take a chestnut

  1. Creating an Abstract observer
// Abstract the observertypeIObserver interface {Notify()} IObserver interface {Notify()}Copy the code
  1. Create abstract observed
// Abstracts the observed
type ISubject interface{ AddObservers(observers ... IObserver)// Add an observer
    NotifyObservers()                    // Notify the observer
}
Copy the code
  1. Realization observer
 
 type Observer struct{}func (o *Observer) Notify(a) {
     fmt.Println("Has triggered the observer.")}Copy the code
  1. Realizing the observed
 
 type Subject struct {
     observers []IObserver
 }
 
 func (s *Subject) AddObservers(observers ... IObserver) {
     s.observers = append(s.observers, observers...)
 }
 
 func (s *Subject) NotifyObservers(a) {
     for k := range s.observers {
         s.observers[k].Notify() // Trigger the observer}}Copy the code
  1. Using the instance
     // Create the observed
      s := new(Subject)
      // Create an observer
      o := new(Observer)
      // Add an observer to the theme
      s.AddObservers(o)
  
      // Here the observed makes various changes...
  
      // When the change is complete, trigger the observer
      s.NotifyObservers()  // output: the observer has been triggered
Copy the code

Here’s an example of a practical application

For those of you who are familiar with PHP, take a look at this, event system and observer mode in Laravel

Here is a chestnut from my own project: github.com/silsuer/bin…

This is an event system implemented in Golang, and I am trying to implement it in my framework. It implements two observer modes, one is the observer mode which implements the observer interface, and the other is the observer mode which uses reflection for type mapping.

  1. The observer interface is implemented in the following way:
   // Create a structure that implements the event interface
   type listen struct {
      bingo_events.Event
      Name string
   }
   
   func func main(a) {
   	// Event object
   	app := bingo_events.NewApp() 
     l := new(listen)  // Create the observed
     l.Name = "silsuer"
     l.Attach(func (event interface{}, next func(event interface{})) { 
     	 // As the object monitored by the listener does not necessarily implement the IEvent interface, type assertion is needed here to convert the object back to its original type
          a := event.(*listen)
          fmt.Println(a.Name) // output: silsuer
          a.Name = "god"      // Change the properties of the structure
          next(a)             // Call the next function to proceed to the next listener. If it is not called here, the program will terminate there and not proceed
       })
     
     // Trigger the event
     app.Dispatch(l)
   }
Copy the code

Here, we use the structure combination to realize the implementation of the Event interface. As long as the bingo-events.Event is put into the structure to be monitored, the IEvent interface is realized. Attach() method can be used to add observers.

The observer here is a func(Event Interface {}, next func(Event Interface {})) method,

The first argument is the triggered object. After all, observers sometimes need properties of the observed, such as Laravel’s model mentioned above…

The second argument is a method of type func(Event Interface {}). A pipeline is implemented to intercept the observer. The event will only be passed to the next observer if the next() method is called at the end of the observer.

I have written about the principle of pipeline in reference to Laravel’s production of golang-based routing package, which is used for middleware interception operations.

  1. Use reflection to do observer mapping

      // Create an object without implementing an event interface
      type listen struct {
           	Name string      
      }
    
      func main(a) {
           	// Event object
           	app := bingo_events.NewApp()
           	// Add an observer
           	app.Listen("*main.listen", ListenStruct)  // Use the Listen method directly to add a callback to the listening structure
             l := new(listen)              
           	l.Name = "silsuer"  // Assign a value to an object attribute
       
           	// ListenStruct and L2 are added as observers
             // The ListenStruct listener has been added at the beginning, so the ListenStruct listener will not be added again at the second time
             // In this case, arguments are passed to each listener in order for subsequent operations
             app.Dispatch(l)
           }      
     func ListenStruct(event interface{}, next func(event interface{})) {
           	// As the object monitored by the listener does not necessarily implement the IEvent interface, type assertion is needed here to convert the object back to its original type
     	    a := event.(*listen)
           	fmt.Println(a.Name) // output: silsuer
           	a.Name = "god"   // Change the properties of the structure
           	next(a)   // Call the next function to proceed to the next listener. If it is not called here, the program will terminate there and not proceed
           }
    Copy the code

    Instead of using the Attach method that implements the event object to add an observer, we use a string to represent the observed, thus achieving the observer mode without implementing the observer interface. The complete code can be directed to the Git repository

    Here we need to focus on two methods: Listen() and Dispatch()

    Bingo_events. App is a service container that holds all events and the mapping between them

     // Service container
     type App struct {
     	sync.RWMutex / / read/write locks
     	events map[string][]Listener // Event mapping
     }
    Copy the code

    Look at the source code below:

    Listen():

      // Listen to [event][listener], bind a separate listener
      func (app *App) Listen(str string, listener Listener) {
      	app.Lock()
      	app.events[str] = append(app.events[str], listener) 
      	app.Unlock()
      }
    Copy the code

    App.events holds objects that are listened to via strings, which are retrieved via reflect.typeof (v).string (). For example, the listen object above is *main.listen, which is the key and the corresponding value is the bound listener method

    Dispatch()

     // Distribute events, pass in various events, if yes
     func (app *App) Dispatch(events ...interface{}) {
     	// The container distributes data
     	var event string
     	for k := range events {
     		if _, ok := events[k].(string); ok { // If the input is a string
     			event = events[k].(string)}else {
     			// If it is not a string, get its type
     			event = reflect.TypeOf(events[k]).String()
     		}
     
     		// If an event interface IEvent is implemented, then the observer mode of the event is called to get all the IEvent
     		var observers []Listener
     		if _, ok := events[k].(IEvent); ok {
     			obs := events[k].(IEvent).Observers()
     			observers = append(observers, obs...) // Place the self-added observer in the event after all observers
     		}
     
            // If there is a map map, it is also put into the observer array
     		if obs, exist := app.events[event]; exist {
     			observers = append(observers, obs...)
     		}
     
     		if len(observers) > 0 {
     			// Get all the observers, this is done by pipeline, next controls when to call the observer
     			new(Pipeline).Send(events[k]).Through(observers).Then(func(context interface{}){})}}}Copy the code

    The Dispatch() method passes in an object (or string of object type) with or without an event interface, iterates over all objects (if a string is passed in, it does nothing, if an object, it extracts the string name of the object by reflection), and extracts the corresponding observer from the map in the current App. If the object also implements the event interface, it takes all the observers mounted on the event, installs them in the same slice, and builds a pipeline that executes the observers sequentially.

The code above is stored in a repository called Golang-Design-Patterns

Go web framework bingo, for star, for PR ~