Множественное наследование: вызов всех переопределенных функций - PullRequest
0 голосов
/ 14 марта 2019

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

Например, способ сделать это будет:

class BehaviorAbstract {
  protected:
     virtual void processInfo(Info i) = 0;
}

class Behavior1: public BehaviorAbstract {
   protected:
     virtual void processInfo(Info i) { ... }
     void performBehavior1()  { ... }
}

class Behavior2: public BehaviorAbstract {
   protected:
     virtual void processInfo(Info i) { ... }
     void performBehavior2()  { ... }
}

class ConcreteObject: public Behavior1, Behavior2 {
   protected:
     void processInfo(Info i) {
       // needs to call processInfo of Behavior1 and Behavior2
       Behavior1::processInfo(i);
       Behavior2::processInfo(i);
     }
     void perform() {
       this->performBehavior1(); this->performBehavior2();
     }
 }

Таквот суть вопроса: ConcreteObject необходимо вызвать 2 функции processInfo (то же имя, те же аргументы) всех классов, от которых он наследует.Представьте, что все классы поведения написаны разными разработчиками.Функция ДОЛЖНА иметь одно и то же имя, поскольку все они являются производными от BehaviorAbstract.

Какой разумный шаблон проектирования используется для этого?Я подозреваю, что множественное наследование может быть неправильным, и, возможно, «множественная композиция» была бы лучше, но мне нужно, чтобы все классы Behavior и ConcreteObject были производными от BehaviorAbstract, и все они должны работать на одном и том же защищенномчлен данных BehaviorAbstract.

Решение, которое я написал выше, кажется неправильным и безобразным.Есть ли способ автоматически вызывать все родительские классы, которые реализуют processInfo, без явной необходимости переписывать их имя?

Большое спасибо за помощь.

1 Ответ

1 голос
/ 15 марта 2019

Если я правильно понял, то этот вопрос касается рефакторинга класса ConcreteObject.

Подход № 1:

Если вы можете сделать performBehavior()часть базового класса BehaviorAbstract, тогда вы можете просто использовать вектор BehaviorAbstract* и позволить полиморфизму делать свое дело.Я думаю, что это можно рассматривать как шаблон стратегии.

#include <iostream>
#include <vector>

typedef int Info;

struct BehaviorAbstract
{
    virtual void processInfo(Info i) = 0;
    virtual void performBehavior() = 0;
};

struct Behavior1 : BehaviorAbstract 
{
     void processInfo(Info i) override
     { std::cout<< "Behavior1::processInfo()" <<std::endl; }

     void performBehavior() override
     { std::cout<< "Behavior1::performBehavior()" <<std::endl; }
};

struct Behavior2 : BehaviorAbstract
{
     void processInfo(Info i) override
     { std::cout<< "Behavior2::processInfo()" <<std::endl; }

     void performBehavior() override
     { std::cout<< "Behavior2::performBehavior()" <<std::endl; }
};

//------------------------------------------------//

struct ConcreteObject
{
    typedef std::vector<BehaviorAbstract*> vec_behavior;

    vec_behavior vba;

    ConcreteObject(vec_behavior &&v) : vba(v)
    {;}

    void processInfo(Info i)
    {
        for (auto &&itr : vba)
            itr->processInfo(i);
    }

    void perform()
    {
        for (auto &&itr : vba)
            itr->performBehavior();
    }
};

int main()
{
    ConcreteObject foo = {{new Behavior1(), new Behavior2()}};
    foo.processInfo(23);
    foo.perform();
}

Пример: https://rextester.com/UXR42210

Подход № 2:

Использование вариадического алгоритмаШаблон, который создает кортеж.Выполните итерацию по этому кортежу и запустите функции.Опять же, если performBehavior1() и performBehavior2() могут использовать одно и то же имя функции, тогда это станет проще.Дополнительная сложность заключается в том, что вам нужно написать ручной способ итерации по этому кортежу.Для простоты я вызвал processInfo() непосредственно из структуры iterate_tuple.

#include <iostream>
#include <tuple>

typedef int Info;

struct BehaviorAbstract
{
     virtual void processInfo(Info i) = 0;
};

struct Behavior1 : BehaviorAbstract 
{
     void processInfo(Info i) override
     { std::cout<< "Behavior1::processInfo()" <<std::endl; }

     void performBehavior1()
     { std::cout<< "Behavior1::performBehavior1()" <<std::endl; }
};

struct Behavior2 : BehaviorAbstract
{
     void processInfo(Info i) override
     { std::cout<< "Behavior2::processInfo()" <<std::endl; }

     void performBehavior2()
     { std::cout<< "Behavior2::performBehavior2()" <<std::endl; }
};


//------------------------------------------------//

template<typename T, std::size_t N>
struct iterate_tuple 
{
    static void run(T &t, Info i) 
    {
        std::get<N>(t).processInfo(i); 
        iterate_tuple<T, N-1>::run(t,i); 
    }
}; 

template<typename T>
struct iterate_tuple<T, 0> 
{
    static void run(T &t, Info i) 
    {
        std::get<0>(t).processInfo(i); 
    }
};

//------------------------------------------------//

template<typename ...T>
struct ConcreteObject
{
    std::tuple<T ...> tmp;
    static constexpr std::size_t tuple_size = std::tuple_size<decltype(tmp)>::value;

    ConcreteObject() : tmp{std::forward<T>(T()) ...}
    {;}

    void processInfo(Info i)
    {
        iterate_tuple<decltype(tmp), tuple_size-1>::run(tmp, i);
    }

    void perform()
    {
        std::get<0>(tmp).performBehavior1();
        std::get<1>(tmp).performBehavior2();
    }
};


int main()
{
    ConcreteObject<Behavior1,Behavior2> foo;
    foo.processInfo(23);
    foo.perform();
}

Пример: https://rextester.com/SBRE16218

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

...