Goal
Implement an observer in golang
Description
This recipe shows you my own version of an implementation of the observer pattern for Golang. The observer pattern is a very known one, in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
How to
In this example, we will be using an event to be shared from the notifier (subject) to let all its dependents (observers) be aware of something going on and be able to interact with that event.
The implementation consists on 4 packages:
obspatterncontaining the event struct (what is shared for the communication between the subject and the observers, as well as the relevant contracts to be implemented)notifiercontaining the implementation of the subject/notifierobservercontaining examples of observers that will enrich the event with additional datamainto show an example of setting up this pattern
Lets start with the package obspattern :
// event.go
package obspattern
import "domain"
// Event represents a shareable struct for the communication between the subject and its observers. It can be used
// to store data calculated by each observer and shared with the common subject.
type Event struct {
Data []string
}
// AddElement adds the information contained in the element to the current event as its data and returns it. As such,
// it is possible to add multiple elements to the same event in the same line.
func (e *Event) AddElement(element string) *Event {
e.Data = append(e.Data, element)
return e
}
// notifier.go
package obspattern
// Notifier defines the interface that the subject must implement in order to maintain a list of its dependents (or
// observers) as well as the method notify them of particular changes.
type Notifier interface {
Register(Observer)
Notify(*Event)
}
// observer.go
package obspattern
// Observer defines the interface that each observer must implement in order to be notified from the subject.
type Observer interface {
OnNotify(*Event)
}
Next, lets implement the notifier/subject:
package notifier
import "obspattern"
// Get returns the notifier as a singleton instance. The notifier (or subject) maintains a list of its dependents,
// also called observers to be notifier
func Get() obspattern.Notifier {
return instance
}
var instance *notifier
type notifier struct {
observers map[obspattern.Observer]struct{}
}
func init() {
instance = ¬ifier{observers: map[obspattern.Observer]struct{}{}}
}
func (n *notifier) Register(o obspattern.Observer) {
n.observers[o] = struct{}{}
}
func (n *notifier) Notify(e *obspattern.Event) {
for o := range n.observers {
o.OnNotify(e)
}
}
Now, lets also implement the observers (for the sake of simplicity, they simply add a new string to the event’s data). As you can see, in the implementation, I chose to have a dependency on the notifier package so that we can have the init() method of the package registering the observer to the notifier. In the main package, I’ll be showing some different configuration examples of using that way versus using the exported Init() method when ordering of multiple observers is required:
package observer1
import (
"notifier"
"obspattern"
)
// Init allows the package to be instrumented from the outside where, for example, order between observers must be
// respected.
func Init() {
notifier.Get().Register(&obs{})
}
func init() {
Init()
}
type obs struct{}
func (o *obs) OnNotify(e *obspattern.Event) {
e.Data = append(e.Data, doStuff())
}
func doStuff() string {
return "OBSERVER1: additional data"
}
and
package observer2
import (
"notifier"
"obspattern"
)
// Init allows the package to be instrumented from the outside where, for example, order between observers must be
// respected.
func Init() {
notifier.Get().Register(&obs{})
}
func init() {
Init()
}
type obs struct{}
func (o *obs) OnNotify(e *obspattern.Event) {
e.Data = append(e.Data, doStuff())
}
func doStuff() string {
return "OBSERVER2: additional data"
}
Finally, here’s the main.go file with the setup of the example code:
package main
import (
"fmt"
"notifier"
_ "observer1" // register the observer through its init method
_ "observer2" // register the observer through its init method
"obspattern"
)
func main() {
n := notifier.Get()
evt := obspattern.Event{
Data: []string{"Initial Data"},
}
n.Notify(&evt)
for _, d := range evt.Data {
fmt.Println(d)
}
}
The previous is a simple way to configure the observers to be listening for the events sent by the subject/notifier. If, however, you need more control over the order of the observers (one that the order of the imports does not guarantee, for example), you could do the following instead:
package main
import (
"fmt"
"notifier"
"observer1"
"observer2"
"obspattern"
)
func init() {
observer2.Init()
observer1.Init()
}
func main() {
n := notifier.Get()
evt := obspattern.Event{
Data: []string{"Initial Data"},
}
n.Notify(&evt)
for _, d := range evt.Data {
fmt.Println(d)
}
}
Explanation
No further explanation seems necessary.

One Reply to “”