From 065ae23a2f49ea758c9d4d86ef99c9fca6a727f3 Mon Sep 17 00:00:00 2001 From: Anthony Atkinson Date: Tue, 6 Sep 2016 11:45:37 -0400 Subject: [PATCH] Implement Observer pattern --- README.md | 2 +- behavioral/observer.md | 47 +++++++++++++++++++ behavioral/observer/main.go | 93 +++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 behavioral/observer.md create mode 100644 behavioral/observer/main.go diff --git a/README.md b/README.md index dfec8f7..7efea6a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ __Behavioral Patterns__: | TODO: [Command](behavioral/command.md) | Bundles a command and arguments to call later | | TODO: [Mediator](behavioral/mediator.md) | Connects objects and acts as a proxy | | TODO: [Memento](behavioral/memento.md) | Generate an opaque token that can be used to go back to a previous state | -| TODO: [Observer](behavioral/observer.md) | Provide a callback for notification of events/changes to data | +| [Observer](behavioral/observer.md) | Provide a callback for notification of events/changes to data | | TODO: [Registry](behavioral/registry.md) | Keep track of all subclasses of a given class | | TODO: [State](behavioral/state.md) | Encapsulates varying behavior for the same object based on its internal state | | [Strategy](behavioral/strategy.md) | Enables an algorithm's behavior to be selected at runtime | diff --git a/behavioral/observer.md b/behavioral/observer.md new file mode 100644 index 0000000..c48de84 --- /dev/null +++ b/behavioral/observer.md @@ -0,0 +1,47 @@ +# Observer Pattern + +The [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows a type instance to "publish" events to other type instances ("observers") who wish to be updated when a particular event occurs. + +## Implementation + +In long-running applications—such as webservers—instances can keep a collection of observers that will receive notification of triggered events. + +Implementations vary, but interfaces can be used to make standard observers and notifiers: + +```go +type ( + // Event defines an indication of a point-in-time occurrence. + Event struct { + // Data in this case is a simple int, but the actual + // implementation would depend on the application. + Data int64 + } + + // Observer defines a standard interface for instances that wish to list for + // the occurrence of a specific event. + Observer interface { + // OnNotify allows an event to be "published" to interface implementations. + // In the "real world", error handling would likely be implemented. + OnNotify(Event) + } + + // Notifier is the instance being observed. Publisher is perhaps another decent + // name, but naming things is hard. + Notifier interface { + // Register allows an instance to register itself to listen/observe + // events. + Register(Observer) + // Deregister allows an instance to remove itself from the collection + // of observers/listeners. + Deregister(Observer) + // Notify publishes new events to listeners. The method is not + // absolutely necessary, as each implementation could define this itself + // without losing functionality. + Notify(Event) + } +) +``` + +## Usage + +For usage, see [observer/main.go](observer/main.go) or [view in the Playground](https://play.golang.org/p/cr8jEmDmw0). diff --git a/behavioral/observer/main.go b/behavioral/observer/main.go new file mode 100644 index 0000000..d6fbc9f --- /dev/null +++ b/behavioral/observer/main.go @@ -0,0 +1,93 @@ +// Package main serves as an example application that makes use of the observer pattern. +// Playground: https://play.golang.org/p/cr8jEmDmw0 +package main + +import ( + "fmt" + "time" +) + +type ( + // Event defines an indication of a point-in-time occurrence. + Event struct { + // Data in this case is a simple int, but the actual + // implementation would depend on the application. + Data int64 + } + + // Observer defines a standard interface for instances that wish to list for + // the occurrence of a specific event. + Observer interface { + // OnNotify allows an event to be "published" to interface implementations. + // In the "real world", error handling would likely be implemented. + OnNotify(Event) + } + + // Notifier is the instance being observed. Publisher is perhaps another decent + // name, but naming things is hard. + Notifier interface { + // Register allows an instance to register itself to listen/observe + // events. + Register(Observer) + // Deregister allows an instance to remove itself from the collection + // of observers/listeners. + Deregister(Observer) + // Notify publishes new events to listeners. The method is not + // absolutely necessary, as each implementation could define this itself + // without losing functionality. + Notify(Event) + } +) + +type ( + eventObserver struct{ + id int + } + + eventNotifier struct{ + // Using a map with an empty struct allows us to keep the observers + // unique while still keeping memory usage relatively low. + observers map[Observer]struct{} + } +) + +func (o *eventObserver) OnNotify(e Event) { + fmt.Printf("*** Observer %d received: %d\n", o.id, e.Data) +} + +func (o *eventNotifier) Register(l Observer) { + o.observers[l] = struct{}{} +} + +func (o *eventNotifier) Deregister(l Observer) { + delete(o.observers, l) +} + +func (p *eventNotifier) Notify(e Event) { + for o := range p.observers { + o.OnNotify(e) + } +} + +func main() { + // Initialize a new Notifier. + n := eventNotifier{ + observers: map[Observer]struct{}{}, + } + + // Register a couple of observers. + n.Register(&eventObserver{id: 1}) + n.Register(&eventObserver{id: 2}) + + // A simple loop publishing the current Unix timestamp to observers. + stop := time.NewTimer(10 * time.Second).C + tick := time.NewTicker(time.Second).C + for { + select { + case <- stop: + return + case t := <-tick: + n.Notify(Event{Data: t.UnixNano()}) + } + } +} \ No newline at end of file