August 29, 2025

Observer Design Pattern

Posted on August 29, 2025  •  6 minutes  • 1129 words

The Observer Design Pattern is one of the most widely used behavioral patterns in software design. It defines a one-to-many dependency between objects, so that when one object (called the Subject) changes its state, all of its dependents (called Observers) are notified automatically.

YouTube video thumbnail

This pattern is extremely useful when you want to build loosely coupled systems where multiple parts of the application need to react to changes without being tightly bound together.

  • Think of Twitter: When you (Subject) tweet, all your followers (Observers) get notified.
  • A Weather Station (Subject) broadcasts updates to many displays (Observers).
  • A Stock Market Ticker updates dashboards and alerts.

Key Components

  1. Subject

    • Maintains a list of observers.
    • Provides methods to attach/detach observers.
    • Notifies observers when the state changes.
  2. Observer

    • Defines an interface with an update() method.
    • Concrete observers implement this method to react to notifications.
  3. Concrete Subject

    • The real object being observed (e.g., StockTicker).
  4. Concrete Observer

    • Reacts when the subject changes state (e.g., Console display, Alerts).

adapter

Basic C++ Implementation

Here’s a safe implementation using weak_ptr to avoid memory leaks and dangling pointers:

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

// Observer Interface
struct IObserver 
{
    virtual ~IObserver() = default;
    virtual void onPriceChanged(double newPrice) = 0;
};

// Subject Base Class
class Subject 
{
public:
    void attach(const std::shared_ptr<IObserver>& obs) 
    {
        _observers.push_back(obs);
    }

    void detach(const std::shared_ptr<IObserver>& obs) 
    {
        auto raw = obs.get();
        _observers.erase(std::remove_if(_observers.begin(), _observers.end(),
            [&](const std::weak_ptr<IObserver>& w)
            {
                auto s = w.lock();
                return !s || s.get() == raw;
            }), _observers.end());
    }

protected:
    void notifyPrice(double price) 
    {
        std::vector<std::shared_ptr<IObserver>> snapshot;

        _observers.erase(std::remove_if(_observers.begin(), _observers.end(),
            [&](const std::weak_ptr<IObserver>& w)
            {
                if (auto s = w.lock()) 
                { 
                    snapshot.push_back(s); return false; 
                }
                return true;
            }), _observers.end());

        for (auto& obs : snapshot) 
        {
            obs->onPriceChanged(price);
        }
    }

private:
    std::vector<std::weak_ptr<IObserver>> _observers;
};

// Concrete Subject
class StockTicker : public Subject 
{
public:
    void setPrice(double p) 
    {
        if (p == price_) 
            return;
        price_ = p;
        notifyPrice(price_);
    }
private:
    double price_{};
};

// Concrete Observers
class ConsoleDisplay : public IObserver 
{
public:
    void onPriceChanged(double p) override 
    {
        std::cout << "[ConsoleDisplay] Price: " << p << "\n";
    }
};

class AlertWhenAbove : public IObserver 
{
public:
    explicit AlertWhenAbove(double threshold) 
        : _threshold(threshold) {}
    void onPriceChanged(double p) override 
    {
        if (p > _threshold) 
        {
            std::cout << "[Alert] Crossed " << _threshold << ": " << p << "\n";
        }
    }
private:
    double _threshold;
};

int main() 
{
    auto display = std::make_shared<ConsoleDisplay>();
    auto alert   = std::make_shared<AlertWhenAbove>(105.0);

    StockTicker ticker;
    ticker.attach(display);
    ticker.attach(alert);

    ticker.setPrice(100.0);
    ticker.setPrice(106.5);

    ticker.detach(display);
    ticker.setPrice(107.2);
}

Output

[ConsoleDisplay] Price: 100
[ConsoleDisplay] Price: 106.5
[Alert] Crossed 105: 106.5
[Alert] Crossed 105: 107.2

RAII + std::function Signal Style

Another elegant way is using signals/slots style with RAII auto-disconnect:

#include <functional>
#include <vector>
#include <algorithm>
#include <mutex>
#include <iostream>

template <typename... Args>
class Signal 
{
public:
    using Slot = std::function<void(Args...)>;
    using SlotId = std::size_t;

    class Connection 
    {
    public:
        Connection() = default;
        Connection(Signal* sig, SlotId id) 
            : _sig(sig), _id(id) {}
        ~Connection() { disconnect(); }
        void disconnect() 
        { 
            if (_sig) 
            { 
                _sig->disconnect(_id); _sig=nullptr; 
            } 
        }
    private:
        Signal* _sig = nullptr;
        SlotId _id = 0;
    };

    Connection connect(Slot s) 
    {
        std::lock_guard<std::mutex> lock(_m);
        _slots.push_back({++_nextId, std::move(s)});
        return Connection(this, _nextId);
    }

    void emit(Args... args) 
    {
        std::vector<Slot> snapshot;
        {
            std::lock_guard<std::mutex> lock(_m);
            for (auto& it : _slots) 
            {
                snapshot.push_back(it.second);
            }
        }
        for (auto& s : snapshot) 
        {
            s(args...);
        }
    }

private:
    void disconnect(SlotId id) 
    {
        std::lock_guard<std::mutex> lock(_m);
        _slots.erase(std::remove_if(_slots.begin(), _slots.end(),
            [&](auto& p){ return p.first == id; }), _slots.end());
    }
    std::vector<std::pair<SlotId, Slot>> _slots;
    SlotId _nextId = 0;
    std::mutex _m;
};

// Example Usage
struct StockTicker
{
    void setPrice(double p) 
    { 
        if (p==price) 
            return; 
        price = p;
        onPrice.emit(price); 
    }
    double price{};
    Signal<double> onPrice;
};

int main()
{
    StockTicker t;
    auto c1 = t.onPrice.connect([](double p)
        { 
            std::cout << "Display: " << p << "\n"; 
        });
    {
        auto c2 = t.onPrice.connect([](double p)
        { 
            if (p>105) 
            {
                std::cout << "Alert: " << p << "\n";
            } 
        });
        t.setPrice(100);
        t.setPrice(106);
    }
    t.setPrice(107);
}

Notes:

✔ Use weak_ptr for storing observers → prevents memory leaks.
✔ Always create a snapshot before notifying to avoid iterator invalidation.
✔ Avoid calling observers while holding a mutex → deadlocks can occur.
✔ RAII Connection ensures auto-unsubscribe → no forgotten detach.
✔ For advanced needs, consider libraries like Boost.Signals2 or Qt signals/slots.

When to Use Observer Pattern

  • GUI frameworks (e.g., buttons notifying listeners).
  • Stock tickers, sensors, live data feeds.
  • Game development (player state, events).
  • Caching and invalidation.

The Observer Pattern in C++ is powerful for building reactive systems. By leveraging modern C++ features (weak_ptr, RAII, lambdas), we can avoid common pitfalls like memory leaks and dangling pointers.

It’s an essential tool in your design patterns toolkit for creating clean, modular, and decoupled applications.

Example in Youtube Video

Observer Interface

struct AbstractSubscriber
{
    virtual ~AbstractSubscriber() = default;
    virtual void onNewVideoPublished(const std::string& title) = 0;
    virtual const std::string& getName() const = 0;
};

Subject (YouTuber)

class YouTuber 
{
public:
    explicit YouTuber(const std::string& n) : name(n) {}

    void subscribe(const std::shared_ptr<AbstractSubscriber>& sub) 
    {
        subscribers.push_back(sub);
    }

    void unsubscribe(const std::shared_ptr<AbstractSubscriber>& sub) 
    {
        auto raw = sub.get();
        subscribers.erase(std::remove_if(subscribers.begin(), subscribers.end(),
            [&](const std::weak_ptr<AbstractSubscriber>& w) 
            {
                auto s = w.lock();
                return !s || s.get() == raw;
            }), subscribers.end());
    }

    void uploadAndPublishVideo(const std::string& title) 
    {
        std::cout << name << " uploaded a new video: " << title << std::endl;
        notify(title);
    }

private:
    void notify(const std::string& title) 
    {
        std::vector<std::shared_ptr<AbstractSubscriber>> tempList;
        subscribers.erase(std::remove_if(subscribers.begin(), subscribers.end(),
            [&](const std::weak_ptr<AbstractSubscriber>& w) 
            {
                if (auto s = w.lock()) 
                { 
                    tempList.push_back(s);
                    return false; 
                }
                return true;
            }), subscribers.end());

        for (auto& sub : tempList)
        {
            sub->onNewVideoPublished(title);
        }
    }
    
    std::string name;
    std::vector<std::weak_ptr<AbstractSubscriber>> subscribers;
};

Concrete Observers (Subscribers)

class MobileUser : public AbstractSubscriber 
{
public:
    explicit MobileUser(const std::string& name) : username(name) {}
    void onNewVideoPublished(const std::string& title) override 
    {
        std::cout << "Mobile : " << username << " got notification: New video - " << title << std::endl;
    }
    const std::string& getName() const { return username;}
private:
    std::string username;
};

class EmailUser : public AbstractSubscriber 
{
public:
    explicit EmailUser(std::string mail) : email(std::move(mail)) {}
    void onNewVideoPublished(const std::string& title) override 
    {
        std::cout << "Email sent to " << email << ": New video - " << title << std::endl;
    }
    const std::string& getName() const { return email;}
private:
    std::string email;
};

main function

int main() 
{
    auto user1 = std::make_shared<MobileUser>("Arun");
    auto user2 = std::make_shared<EmailUser>("xyz@gmail.com");

    YouTuber youtuber("#anooptube");
    youtuber.subscribe(user1);
    youtuber.subscribe(user2);

    youtuber.uploadAndPublishVideo("Adapter Pattern in C++");

    // Unsubscribe one user
    youtuber.unsubscribe(user1);
    std::cout << "Unsubscribed User : " << user1->getName() << std::endl;

    youtuber.uploadAndPublishVideo("Observer Pattern Explained in C++");
}
Follow me

I work on everything coding and share developer memes