By DevShark


2019-03-14 20:03:52 8 Comments

I have several behaviors that I want a class to have. I'd like to isolate these behaviors, so that I can reuse that code, mix and match at will.

For example, a way to do this would be:

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();
     }
 }

So here's the crux of the matter: ConcreteObject needs to call the 2 functions processInfo (same name, same arguments) of all the classes it inherits from. Imagine that all the behavior classes are coded by different developers. The function HAS to have the same name, because they all derive from BehaviorAbstract.

What's a reasonable design pattern to do this? I suspect multiple inheritance might be wrong here, and maybe a "multiple composition" would be better, but I need all the Behavior classes and the ConcreteObject to derive from BehaviorAbstract and they all need to operate on the same protected data member of BehaviorAbstract.

The solution I wrote above feels wrong and ugly. Is there a way to call automatically all the parent classes that implement processInfo, without explicitely having to rewrite their name?

Thanks a lot for the help.

1 comments

@Constantinos Glynos 2019-03-14 22:16:01

If I got this right, then this question is about refactoring the ConcreteObject class.

Approach #1:

If you can make performBehavior() part of the BehaviorAbstract base class, then you can simply use a vector of BehaviorAbstract* and let polymorphism do its thing. I think this can be seen as the strategy pattern.

#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();
}

Example: https://rextester.com/UXR42210

Approach #2:

Using a variadic template which creates a tuple. The iterate over that tuple and run the functions. Again, if performBehavior1() and performBehavior2() could share the same function name, then it would get easier. The extra complexity here is that you need to write a manual way of iterating over that tuple. For simplicity, I called the processInfo() directly from the iterate_tuple struct.

#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();
}

Example: https://rextester.com/SBRE16218

Both approaches avoid multiple inheritance which, from what I understood, is what you want to avoid. FYI, the simpler the better.

@DevShark 2019-03-15 23:13:47

Thanks, this is cool. That is indeed what I was after. Strategy Pattern is interesting. The way I read it, this is simply a composition pattern, with multiple objects being composed. Is there an authoritative list of Design Patterns? When you say strategy pattern, is it something a few books would say, or something recognized industry wide?

@Constantinos Glynos 2019-03-16 08:55:08

@DevShark: Absolutely! I would recommend the following books on Design Patterns: 1) Design Patterns - Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, 2) Game Programming Patterns by Robert Nystrom, 3) Head First Design Patterns by Eric Freeman, Elisabeth Freeman. The latter book (3) is written in Java and doesn't cover all the well-known design patterns, but it is a fun book to read and it would give you a smooth introduction to design patterns. The first book (1) is a must in your book shelf. It's like a dictionary on design patterns.

Related Questions

Sponsored Content

13 Answered Questions

[SOLVED] How does Python's super() work with multiple inheritance?

6 Answered Questions

24 Answered Questions

16 Answered Questions

[SOLVED] Multiple Inheritance in C#

2 Answered Questions

[SOLVED] Should I avoid multiple implementation inheritance?

13 Answered Questions

4 Answered Questions

2 Answered Questions

2 Answered Questions

[SOLVED] C++ multiple inheritance function call ambiguity

4 Answered Questions

[SOLVED] Multiple inheritance design implementation

Sponsored Content