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
- Creating an Abstract observer
// Abstract the observertypeIObserver interface {Notify()} IObserver interface {Notify()}Copy the code
- Create abstract observed
// Abstracts the observed
type ISubject interface{ AddObservers(observers ... IObserver)// Add an observer
NotifyObservers() // Notify the observer
}
Copy the code
- Realization observer
type Observer struct{}func (o *Observer) Notify(a) {
fmt.Println("Has triggered the observer.")}Copy the code
- 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
- 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.
- 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.
-
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 ~