Applications often need to react when something changes.
When a user signs up, you might want to:
- Send a welcome email
- Create a user profile
- Log the activity
- Notify an analytics service
One approach is to put all of this logic inside the signup code. While it works initially, the code quickly becomes difficult to maintain as more responsibilities are added.
This is exactly the kind of problem the Observer Pattern solves.
The Observer Pattern is one of the most commonly used behavioral design patterns.
It defines a one-to-many relationship between objects so that when one object (the Subject) changes state, all of its dependent objects (the Observers) are notified automatically.
Instead of tightly coupling objects together, the subject simply announces that "something happened," and any interested observers can react.
How It Works
The pattern consists of two main participants.
Subject (Publisher)
The subject owns the state and keeps track of all registered observers.
Whenever its state changes, it notifies every observer.
Observer (Subscriber)
Observers register themselves with the subject.
When they receive a notification, they perform whatever action they're responsible for.
A classic real-world example is a UI button.
The button is the Subject, while event listeners are the Observers. When the button is clicked, every registered listener is notified.
Why Use the Observer Pattern?
Without the Observer Pattern, your code often grows into something like this:
class User
def signup
save
WelcomeMailer.send_email(self)
Analytics.track_signup(self)
ActivityLogger.log_signup(self)
UserProfile.create(self)
end
end
Every new action requires modifying the signup method.
This makes the class responsible for many unrelated tasks and violates the Single Responsibility Principle.
With the Observer Pattern, the signup process only needs to announce that a user has signed up. Each observer decides how to respond.
Loose Coupling
One of the biggest benefits of the Observer Pattern is loose coupling.
The subject only knows that its observers respond to a common interface (for example, an update method).
This means:
- New observers can be added without changing the subject.
- Observers can be removed at runtime.
- The subject doesn't know or care what each observer does.
- Subjects and observers can evolve independently.
- Each observer has a single, focused responsibility.
Ruby Example
Let's build a simple weather station.
Whenever the temperature changes, every registered display should update automatically.
class WeatherStation
def initialize
@observers = []
end
def add_observer(observer)
@observers << observer
end
def remove_observer(observer)
@observers.delete(observer)
end
def temperature=(value)
@temperature = value
notify_observers
end
private
def notify_observers
@observers.each do |observer|
observer.update(@temperature)
end
end
end
class PhoneDisplay
def update(temperature)
puts "Phone Display: #{temperature}°C"
end
end
class DashboardDisplay
def update(temperature)
puts "Dashboard Display: #{temperature}°C"
end
end
station = WeatherStation.new
station.add_observer(PhoneDisplay.new)
station.add_observer(DashboardDisplay.new)
station.temperature = 25
Output:
Phone Display: 25°C
Dashboard Display: 25°C
When the temperature changes, the weather station automatically notifies every registered display.
Notice that WeatherStation doesn't know anything about phones or dashboards. It only knows that each observer responds to update.
Observer Pattern in Rails
Even if you've never implemented the Observer Pattern yourself, you've probably used it in Rails.
Some common examples include:
- Active Record callbacks (
after_create,after_commit,before_save) ActiveSupport::Notifications- Action Cable broadcasting events to connected clients
For example:
class User < ApplicationRecord
after_create :send_welcome_email
private
def send_welcome_email
WelcomeMailer.welcome(self).deliver_later
end
end
When a User is created, Rails automatically invokes the callback.
Although callbacks aren't a textbook implementation of the Observer Pattern, they follow the same fundamental idea: responding automatically when an object's state changes.
When Should You Use the Observer Pattern?
The Observer Pattern is a good choice when:
- Multiple objects need to react to the same event.
- You want to avoid tightly coupling classes together.
- New behaviors should be be added without modifying existing code.
- Different parts of the application should respond independently to state changes.
- You want to follow the Open/Closed Principle and the Single Responsibility Principle.
Final Thoughts
The Observer Pattern is all about communication without tight coupling.
Instead of asking every object to perform additional work, the subject simply announces that something has changed.
Any interested observer can react in its own way.
In Ruby, where objects communicate through simple interfaces and duck typing, the Observer Pattern is straightforward to implement and can significantly improve the flexibility and maintainability of your applications.
As your applications grow, you'll find this pattern everywhere—from GUI frameworks to Rails callbacks to event-driven architectures.
for a full example in ruby check this https://github.com/ShroukAbozeid/design-pattern/tree/main/observer
Top comments (0)