Почему наследование обычно используется вместо композиции при реализации шаблона наблюдателя? - PullRequest
0 голосов
/ 26 апреля 2020
#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);
}

Вывод:

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

Я узнал шаблон observer из многих источников, но ни один из них не упомянул о его реализации с помощью композиции. Я думаю, что использование композиции является более мощным, чем наследование, по нескольким причинам:

1 - класс может иметь много наблюдателей, и каждый из них наблюдает за чем-то другим. Если бы я хотел добиться того же поведения (имея разных наблюдателей, наблюдающих за n разными вещами), мне пришлось бы изменить базовый класс так, чтобы он поддерживал n типов наблюдателей и позже, если бы я хотел, чтобы он поддерживал n + 1 наблюдатели, мне придется изменить это снова или унаследовать от другого класса. В то время как при использовании композиции я могу просто добавить еще один Subject и все.

2 - С наследованием, если я захочу уведомить наблюдателей, я уведомлю всех подписавшихся, даже если каждый из них ищет что-то другое. Принимая во внимание, что при использовании композиции я могу просто уведомить желаемые.

3 - Некоторые языки, такие как java, не допускают множественное наследование, если я наследую от абстрактного класса, который реализует некоторые логики c из Subject, я не смогу наследовать от другого учебный класс. В то время как при использовании композиции у некоторых классов есть свободное место для наследования.

4 - Композиция в любом случае предпочтительнее наследования. Если какое-либо поведение достижимо с использованием как наследования, так и композиции, почему подход наследования более популярен?

В приведенном выше коде у меня есть Observer и Subject. Существует 2 типа Subject, один из которых использует наследование с именем UsingInheritance, а другой использует композицию с именем UsingComposition. Каждое из них имеет 2 значения, value1 и value2. Есть 2 наблюдателя, ObservingForValue1, которые заинтересованы только в value1 и ObservingForValue2, которые заинтересованы только в value2. Обратите внимание, что при использовании UsingInheritance все подписанные наблюдатели получают уведомление, даже если значение, в котором они заинтересованы, не изменяется, тогда как при использовании UsingComposition обновляются только нужные.

...