The Observer - is a behavioral design pattern that allows an object, called the subject, to notify a list of observers or subscribers about any changes to its state. In Go, this pattern can be used to maintain loosely coupled relationships between objects.
In a nut shell
The Observer Design Pattern is a great way to design a system where an object can notify a set of observers when its state changes. It's especially useful when we want to decouple objects that depend on each other. In Go, this pattern can be used to maintain loosely coupled relationships between objects. This pattern is part of the GoF patterns, which are a set of design patterns described by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides in their book "Design Patterns: Elements of Reusable Object-Oriented Software".
In the Observer Design Pattern, the subject maintains a list of observers and notifies them when its state changes. The observers register with the subject to receive notifications, and the subject notifies the observers by calling their update method.
Why do we need it
The Observer Design Pattern is useful in several scenarios, including:
- When there is a one-to-many relationship between objects, and a change to one object requires changes to other objects.
- When an object should be able to notify other objects without making assumptions about who these objects are.
- When an object should be able to notify other objects without knowing anything about them.
How can it help us
Observer Design Pattern solves several problems, including:
- It reduces the coupling between objects.
- It allows for better separation of concerns.
- It promotes reusable code.
What to use
Several packages can simplify the implementation of the Observer Design Pattern in Go, including:
RxGo
github.com/reactivex/rxgo/v2 package provides a set of operators to work with observable streams in a similar way to ReactiveX libraries, allowing to easily implement the Observer Design Pattern in Go.
RxGo is a Go implementation of ReactiveX, which is a library for composing asynchronous and event-based programs. It provides a set of operators to work with observable streams, which are sequences of events that can be observed by multiple observers. RxGo allows developers to write code that is declarative, composable, and reusable.
Here is an example of how to use RxGo to implement the Observer Design Pattern in Go:
package main
import (
"context"
"fmt"
"github.com/reactivex/rxgo/v2"
)
type Observer struct{}
func (o Observer) OnNext(item interface{}) {
fmt.Printf("Received: %v\\n", item)
}
func (o Observer) OnError(err error) {
fmt.Printf("Error: %v\\n", err)
}
func (o Observer) OnCompleted() {
fmt.Println("Done!")
}
func main() {
observable := rxgo.Just("Hello, dev.to!")
_ = observable.Subscribe(context.Background(), Observer{})
}
In this example, we define an observer Observer
that implements the rxgo.Observer
interface. We then create an observable observable
using the rxgo.Just
method and subscribe to it using the Subscribe
method. When the observable emits a value, the observer's OnNext
method is called with that value. If the observable encounters an error, the observer's OnError
method is called with that error. Finally, when the observable completes, the observer's OnCompleted
method is called.
Go Cloud Pub/Sub
github.com/google/go-cloud/pubsub package provides a set of interfaces and tools to work with publish/subscribe messaging. This package allows easy communication between independent components by decoupling the sender and the receiver.
Go Cloud Pub/Sub is a set of libraries and tools for building cloud-native applications. It provides a set of interfaces and tools to work with publish/subscribe messaging, which allows for easy communication between independent components by decoupling the sender and the receiver.
Here is an example of how to use Go Cloud Pub/Sub to implement the Observer Design Pattern in Go:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/google/go-cloud/pubsub"
)
func main() {
ctx := context.Background()
// Create a Pub/Sub topic.
topic, err := pubsub.OpenTopic(ctx, "dev-to-topic")
if err != nil {
log.Fatal(err)
}
// Create a Pub/Sub subscription.
subscription, err := pubsub.OpenSubscription(ctx, "some-subscription")
if err != nil {
log.Fatal(err)
}
// Publish a message to the topic.
msg := &pubsub.Message{
Body: []byte("Hello, dev.to!"),
Metadata: map[string]string{
"key": "value",
},
}
if err := topic.Send(ctx, msg); err != nil {
log.Fatal(err)
}
// Receive the message from the subscription.
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
receivedMsg, err := subscription.Receive(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received: %s\\n", receivedMsg.Body)
// Acknowledge the message.
receivedMsg.Ack()
// Close the subscription and topic.
if err := subscription.Shutdown(ctx); err != nil {
log.Fatal(err)
}
if err := topic.Shutdown(ctx); err != nil {
log.Fatal(err)
}
}
In this example, we create a Pub/Sub topic and subscription using the pubsub.OpenTopic
and pubsub.OpenSubscription
methods. We then publish a message to the topic using the topic.Send
method. Finally, we receive the message from the subscription using the subscription.Receive
method and acknowledge it using the msg2.Ack
method.
Watermill
github.com/ThreeDotsLabs/watermill package provides a framework for building event-driven applications. It allows easy communication between independent components by decoupling the sender and the receiver.
Watermill is a Go library for building event-driven applications. It provides a framework for building event-driven applications and allows for easy communication between independent components by decoupling the sender and the receiver.
Here is an example of how to use Watermill to implement the Observer Design Pattern in Go:
package main
import (
"context"
"fmt"
"log"
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
)
func main() {
logger := watermill.NewStdLogger(false, true)
// Create a new router.
router, err := message.NewRouter(message.RouterConfig{}, logger)
if err != nil {
log.Fatal(err)
}
// Create a new publisher.
publisher, err := message.NewPublisher(message.PublisherConfig{}, logger)
if err != nil {
log.Fatal(err)
}
// Subscribe to a topic.
topic := "my-topic"
subscriber, err := router.AddNoPublisherHandler(
"my-subscriber",
topic,
func(msg *message.Message) error {
fmt.Printf("Received: %s\\n", msg.Payload)
return nil
},
)
if err != nil {
log.Fatal(err)
}
// Publish a message to the topic.
msg := message.NewMessage(watermill.NewUUID(), []byte("Hello, dev.to!"))
if err := publisher.Publish(topic, msg); err != nil {
log.Fatal(err)
}
// Wait for the message to be processed.
if err := subscriber.AwaitClose(); err != nil {
log.Fatal(err)
}
}
In this example, we create a new router and publisher using the message.NewRouter
and message.NewPublisher
methods. We then subscribe to a topic using the router.AddNoPublisherHandler
method and publish a message to the topic using the publisher.Publish
method. Finally, we wait for the message to be processed using the subscriber.AwaitClose
method.
Conclusion
The Observer Design Pattern is a powerful tool that can be used to maintain loosely coupled relationships between objects in Go. By using packages like github.com/reactivex/rxgo/v2
, github.com/google/go-cloud/pubsub
, and github.com/ThreeDotsLabs/watermill
, we can simplify the implementation of this pattern and create reusable, modular code. Happy coding!
Top comments (0)