I. Observer — Behavioral Pattern
Observer defines a one-to-many dependency between objects: when one object (the Subject) changes state, all of its dependents (the Observers) are notified automatically. The Subject doesn't know anything about the concrete type of its Observers — it only knows they implement a shared interface.
This solves a very common coupling problem. Without Observer, the code that places an order would need to explicitly call the barista, then the cashier, then anyone else who cares — and every time a new interested party shows up, that calling code has to change. Observer inverts the dependency: interested parties subscribe themselves, and the Subject simply broadcasts to whoever is currently subscribed.
It's the same idea behind pub/sub systems, event emitters, and UI data-binding — just at the scale of a single in-process object graph.
II. Real-world Example
A coffee shop where placing an order should trigger multiple independent reactions:
- CoffeeShop (Subject) — keeps a list of subscribed observers and exposes
SubscribeandPlaceOrder. It has no idea what a Barista or a Cashier actually does.
- Observer (interface) — declares a single
Update(orderID, drinkName string)method that every subscriber must implement.
- Barista (Concrete Observer) — reacts to a new order by starting to brew the drink.
- Cashier (Concrete Observer) — reacts to the same order by logging the receipt.
When PlaceOrder is called once, both Barista and Cashier react independently — CoffeeShop never calls them by name.

III. Implementation
observer.go
package observer
type Observer interface {
Update(orderID string, drinkName string)
}coffee_shop.go
package observer
type CoffeeShop struct {
observers []Observer
}
func NewCoffeeShop() *CoffeeShop {
return &CoffeeShop{}
}
func (s *CoffeeShop) Subscribe(observer Observer) {
s.observers = append(s.observers, observer)
}
func (s *CoffeeShop) PlaceOrder(orderID string, drinkName string) {
for _, observer := range s.observers {
observer.Update(orderID, drinkName)
}
}barista.go
package observer
import "fmt"
type Barista struct {
Name string
}
func (b *Barista) Update(orderID string, drinkName string) {
fmt.Printf("Barista %s: start brewing %s for order %s\n", b.Name, drinkName, orderID)
}cashier.go
package observer
import "fmt"
type Cashier struct {
Name string
}
func (c *Cashier) Update(orderID string, drinkName string) {
fmt.Printf("Cashier %s: received order %s (%s)\n", c.Name, orderID, drinkName)
}main.go (usage)
shop := observer.NewCoffeeShop()
shop.Subscribe(&observer.Barista{Name: "Anna"})
shop.Subscribe(&observer.Cashier{Name: "Ben"})
shop.PlaceOrder("ORD-001", "Latte")IV. Explain the example above
NewCoffeeShop()creates a Subject with an empty list of observers.
shop.Subscribe(&observer.Barista{Name: "Anna"})andshop.Subscribe(&observer.Cashier{Name: "Ben"})register two concrete observers. CoffeeShop stores them only as theObserverinterface — it has no idea these are a Barista and a Cashier.
shop.PlaceOrder("ORD-001", "Latte")loops through every subscribed observer and callsUpdateon each one, in subscription order.
Running it produces:
*** Example Observer ***
Barista Anna: start brewing Latte for order ORD-001
Cashier Ben: received order ORD-001 (Latte)
*** End of Observer ***One call to PlaceOrder fanned out into two independent reactions. Add a third observer — say, a LoyaltyProgram that awards points — and CoffeeShop's code doesn't change at all; only one new Subscribe call is needed.
V. Conclusion
Observer is a great fit whenever several independent parts of a system need to react to the same event, and you don't want the event source hard-wired to each of them. It keeps the Subject small and stable while the set of Observers can grow or shrink freely.
The trade-off is indirection: once there are many observers, tracing "what actually happens when this event fires" means jumping across several files instead of reading one linear function. It also doesn't guarantee ordering or error handling between observers by default — if Barista's Update panics, Cashier might never run, unless you add that handling yourself. For a fixed, small number of reactions that always happen together, a plain function call is often clearer than the extra interface and subscription machinery.
No pattern is universally superior.
VI. References
- Go Design Patterns (Mario Castro Contreras)




