2
\$\begingroup\$

I am writing an observer design pattern that is not too complex but I still feel there's a lot for me to learn about C++ be it little details (especially coming from C).

A couple of observations:

  • I wanted to understand was Subject here isn't an abstract class unlike Observer however I would ideally have caller instantiate Subject and rather a concrete Subject class (i.e child class of Subject) for which I specified its constructor to be protected. For instance, Weather class being a concrete Subject updates all its observers upon getting a weather data hence the instantiation of Weather in the caller
  • raw pointers (just like here) are fine as long as they're not owning right? If so, I wouldn't need smart pointers perhaps (?) specially if I don't want the object in the caller to be 'owned' by Subject?
  • I used std::set over std::vector to avoid storing duplicates
template<typename T>
class Observer
{
    public:
    virtual void update(T val) = 0;
    virtual ~Observer() = default;
};

template<typename T>
class Subject
{
    std::set<Observer<T>*> _observers;

    protected:
    Subject() = default;

    public:
    void attach(Observer<T>* observer)
    {
        _observers.insert(observer);
    }
    void detach(Observer<T>* observer)
    {
        _observers.erase(observer);
    }
    void notify(const T& val)
    {
        for (auto& observer : _observers)
        {
            observer->update(val);
        }
    }
    virtual ~Subject() = default;
};

Main feedback is requested for the above snippet but adding the following to see how the whole pattern could be used:

template<typename T>
class ConcreteSubject : public Subject<T>
{
    T _value;

    public:
    void set(const T& value)
    {
        _value = value;
        this->notify(value);
    }
};

template<typename T>
class ConcreteObserver : public Observer<T>
{
    public:
    void update(T value)
    {
        std::cout << "Notified: " << value << "\n";
    }
};

int main() 
{
    ConcreteObserver<int> obs;
    ConcreteSubject<int> sub;
    sub.attach(&obs);
    sub.set(5);
    return 0;
}
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Requiring all our observers to inherit from Observer<T> makes the type awkward to use (particularly if some other base class has a conflicting update(T)).

However, std::function is the ideal vehicle for passing around both free functions and bound member functions.

So I would write

using Observer<T> = std::function<void(const T&)>;

Note also that we choose to pass the value as a reference to a const T, rather than by value. This allows the template to function correctly for move-only types, as well as being more efficient when T is expensive to copy.

When we do this, we'll want Subject::attach() to return the value it has actually stored, to make it easier to pass this back to Subject::detach() later.


Subject as a base class is a more reasonable choice. That said, we might not need to inherit from it if we make it contain a T. Or we could provide a concrete subclass that wraps a T object and defines its assignment operators to call notify() after changing the value.

I'm not convinced that storing the observers in a set is a good choice. If we add an observer twice and remove it once, then we might expect to still be observing the subject. I would be inclined to accept duplicates (and notify them as many times as registered), and leave it to the caller to ensure that it only registers once if that's what's required.

I wouldn't use a std::vector for storage, though. That's quite inefficient for adding and removing values in the middle. Perhaps a std::unordered_multiset might be appropriate?


We need to think carefully about lifetime and ownership. At present, it's the caller's responsibility to ensure that every observer is no longer observing when it is deleted, otherwise notify() will see a dangling pointer, resulting in the dreaded Undefined Behaviour. This makes this interface very brittle to use.

A more robust interface arranges to deregister itself from every object it's observing before it's deleted. We can do this by changing the observer to remember which events it has subscribed to.

\$\endgroup\$
3
  • \$\begingroup\$ I've emphasised terms which you might want to look up to learn more if you're not already familiar with them. \$\endgroup\$ Sep 21, 2022 at 6:55
  • \$\begingroup\$ On your last point: you’re prolly implying the destructor of Observer should deregister itself from all the Subjects it’s subscribed to however for that it would need to know about Subject which it doesn’t. So to make it happen, should Subject inform Observer in attach() that it’s registered to this Subject and Observer goes onto add the said Subject to its list that contains Subjects it’s subscribed to? \$\endgroup\$
    – xyf
    Sep 21, 2022 at 14:53
  • \$\begingroup\$ It's something to consider - if you have ever used Qt's signals and slots, you'll know that either end of the connection can be deleted, and the infrastructure removes the connection automatically, preventing a whole class of errors. I'm trying to find time to implement something like that by way of demonstration. \$\endgroup\$ Sep 21, 2022 at 17:40
1
\$\begingroup\$

Question 1) The goal of the Observer Pattern is to allow an object (a subtype of Subject) to inform some other object (an Observer) about an event. In order to do this the Subject needs to have some "knowledge" about the other object (the Observer). This knowledge introduces a dependency from the Subject to the Observer. Dependencies play a very important role in software development. Most importantly the cause the software to be hard to change. That means the Subject should not know more about the other object as needed to inform it about an event. This knowledge is put into the pure abstract class Observer. It defines the communication contract between the Subject and the object to be informed. This contract is often called interface or API. The object to be informed is inherits from the Observer interface (pure abstract class) to ensure that it sticks to the communication contract. The important point here is that the interface does not provide an implementation, but only ensures that the subtype provides an implementation for a given method. In other words - Inheritance in this case is used to enable Polymorphism.

The Subject class on the other hand is a base class that provides an implementation to handle Observers (register, notify, unregister). This is logic that any subtype needs to implement in order to handle Observers. The base class ensures, that you don't have redundant implementations of this logic in specific subjects. Hence, inheritance in this case is used to avoid redundancy which is also a design smell.

Question 2) I would agree that raw pointers would be fine here. In case of smart pointers you would make reuse of the classes harder.

Question 3) It's a matter of taste and a question of what behaviour you would prefer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.