dimanche 26 avril 2020

Why inheritance is usually used instead of composition when implementing the observer pattern?

#include <iostream>
#include <set>

class Observer
{
public:

    virtual void update() = 0;
};

class Subject
{
    std::set<Observer*> observers;

public:

    void addObserver(Observer* observer)
    {
        observers.insert(observer);
    }

    void deleteObserver(Observer* observer)
    {
        observers.erase(observer);
    }

    void notifyObservers()
    {
        for (auto observer : observers)
            observer->update();
    }
};

class HaveValue1 { public: virtual int getValue1() const = 0; };
class HaveValue2 { public: virtual int getValue2() const = 0; };

class ObservingForValue1 : public Observer
{
    HaveValue1* subject;

public:

    ObservingForValue1(HaveValue1* subject) : subject(subject) {}

    virtual void update()
    {
        std::cout << "New value of Value1: " << subject->getValue1() << '\n';
    }
};

class ObservingForValue2 : public Observer
{
    HaveValue2* subject;

public:

    ObservingForValue2(HaveValue2* subject) : subject(subject) {}

    virtual void update()
    {
        std::cout << "New value of Value2: " << subject->getValue2() << '\n';
    }
};

class UsingInheritance : public HaveValue1, public HaveValue2, public Subject
{
    int value1;
    int value2;

public:

    UsingInheritance(int value1, int value2) : value1(value1), value2(value2) {}

    void setValue1(int value) { value1 = value; notifyObservers(); }
    void setValue2(int value) { value2 = value; notifyObservers(); }

    virtual int getValue1() const override { return value1; }
    virtual int getValue2() const override { return value2; }
};

class UsingComposition : public HaveValue1, public HaveValue2
{
    int value1;
    int value2;

    Subject observersForValue1;
    Subject observersForValue2;

public:

    UsingComposition(int value1, int value2) : value1(value1), value2(value2) {}

    void observeValue1(Observer* observer) { observersForValue1.addObserver(observer); }
    void observeValue2(Observer* observer) { observersForValue2.addObserver(observer); }

    void unobserveValue1(Observer* observer) { observersForValue1.deleteObserver(observer); }
    void unobserveValue2(Observer* observer) { observersForValue2.deleteObserver(observer); }

    void setValue1(int value)
    { value1 = value; observersForValue1.notifyObservers(); }

    void setValue2(int value)
    { value2 = value; observersForValue2.notifyObservers(); }

    virtual int getValue1() const override { return value1; }
    virtual int getValue2() const override { return value2; }
};

int main()
{
    std::cout << "With Inheritance:\n";

    UsingInheritance inheritance{ 1, 2 };

    ObservingForValue1 io1{ &inheritance };
    ObservingForValue2 io2{ &inheritance };

    inheritance.addObserver(&io1);
    inheritance.addObserver(&io2);

    inheritance.setValue1(11);
    inheritance.setValue2(22);

    std::cout << "\nWith Composition:\n";

    UsingComposition composition{ 1, 2 };

    ObservingForValue1 co1{ &composition };
    ObservingForValue2 co2{ &composition };

    composition.observeValue1(&co1);
    composition.observeValue2(&co2);

    composition.setValue1(11);
    composition.setValue2(22);
}

Output:

With Inheritance:
New value of Value2: 2
New value of Value1: 11
New value of Value2: 22
New value of Value1: 11

With Composition:
New value of Value1: 11
New value of Value2: 22

I've learned the observer pattern from many sources, yet none of them mentioned implementing it with composition. I think using composition is more powerful than inheritance for couple of reasons:

1 - A class can have lots of observers and each one observing for a different thing. If I wanted to achieve the same behavior (having different observers observing n different things), I'll have to change the base class so that it supports n types of observers and later on, if I wanted it to support n + 1 observers, I'll have to change it again or inherit from another class. Whereas when using composition, I can just add another Subject and that's it.

2 - With inheritance if I wanted to notify the observers, I'll notify all the subscribed ones, even if each one of them is looking for a different thing. Whereas when using composition, I can just notify the desired ones.

3 - Composition is prefered over inheritance anyways. If some behavior is achievable using both inheritance and composition, Why the inheritance approach is more popular?

In the code above, I have an Observer and a Subject. There are 2 types of Subject, one using inheritance called UsingInheritance, and the other uses composition, called UsingComposition. Each one of them has 2 values, value1 and value2. There are 2 observers, ObservingForValue1 which is only interested in value1 and ObservingForValue2 which is only interested in value2. Notice that when using UsingInheritance, all the subscribed observers get notified even if the value they're interested in didn't change, whereas when using UsingComposition, only the desired ones get updated.

Aucun commentaire:

Enregistrer un commentaire