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.
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.