Lately I’ve been using some pattern quite a lot but I don’t know if it is really good or not.
It goes as follows:
I have a set of function, lets call them ActionFoo, ActionBar and ActionZapper. These might differ in implementation but generally are used for same things across these. They may or may not be used together in a sequence(i.e. some of them can be used as a standalone), but there are some cases when they are, indeed grouped.
If I DO want to use them in a sequence I generally have two options:
1) write them manually each time
2) create a class hierarchy:
Approach #1:
void SomeActionSequence1()
{
ActionFoo1(1);
ActionBar1("Moo");
ActionZapper1("Moo", 42);
}
void SomeActionSequence2()
{
ActionFoo4(1);
ActionBar2("Moo");
ActionZapper1("Moo", 42);
}
This has drawbacks:
1) I won’t have an ability to store state and will have to pass a lot of parameters to these Actions
2) I won’t really have a coherent interface and won’t be able to easily use autocompletion
Approach #2
class Base
{
public:
Base(){}
virtual ~Base(){}
virtual void ActionFoo(int) = 0;
virtual void ActionBar(string) = 0;
virtual void ActionZapper(string, int) = 0;
void ExecuteActionSequence();
};
void Base::ExecuteActionSequence()
{
ActionFoo(1);
ActionBar("Moo");
ActionZapper("Moo", 42);
}
Derived1 : public Base
{
void ActionFoo(int){/*some inplementation*/};
void ActionBar(string){/*some inplementation*/};
void ActionZapper(string, int){/*some inplementation*/};
}
Derived2 : public Base
{
void ActionFoo(int){/*some inplementation*/};
void ActionBar(string){/*some inplementation*/};
void ActionZapper(string, int){/*some inplementation*/};
}
and use it kinda like this:
Base* actionSequence = new Derived1();
actionSequence->ExecuteActionSequence();
Correct virtuals will be used and all seems ok except 2 small things:
1) Extensibility – I will have to write a class for each complex action
2) More importantly – either a lot of functions will be duplicated between these classes or
I will have a hierarchical tree too complex on my hands
I kinda “circumvent” problems of both approaches with “Interface Object” pattern (note, the name is mine, maybe it has a proper one)
What I do is this:
class InterfaceClass
{
public:
InterfaceClass(){};
~InterfaceClass(){};
void ActionFoo(int i)
{
if(fooPlaceholder != 0)
fooPlaceholder(i);
}
void ActionBar(string str)
{
if(barPlaceholder != 0)
barPlaceholder(str);
}
void ActionZapper(string str, int i)
{
if(zapperPlaceholder != 0)
zapperPlaceholder(str, i);
};
void ExecuteActionSequence();
std::function<void(int)> fooPlaceholder;
std::function<void(string)> barPlaceholder;
std::function<void(string, int)> zapperPlaceholder;
};
void InterfaceClass::ExecuteActionSequence()
{
ActionFoo(1);
ActionBar("Moo");
ActionZapper("Moo", 42);
}
in my application I do:
InterfaceClass complexAction;
complexAction.fooPlaceholder = ActionFoo;
complexAction.barPlaceholder = ActionBar;
complexAction.zapperPlaceholder = ActionZapper;
complexAction.ExecuteActionSequence();
Note that ActionFoo, ActionBar and ActionZapper are free functions, but at the same time I am using them in an interface. Also – I can easily switch between implementations of these functions, even at runtime(If I need this).
The advantage of this approach is – there is no need to create separate class structures for new actions and there is no code duplication of Action* functions.
Also – all functions can be brought to scope only where the complexAction is initialized.
The disadvantages are, I think, that it is not obvious just which Action* function is being used in the InterfaceClass object. Also – there is no ability to dynamic_cast such a class to determine just what it is.
I highly suspect that these are not only disadvantages of such approach so I would like comments about that.
It sounds like you want the Chain of Responsibility pattern
Advantages – Don’t need to change this base object with a fixed set of strategies when you add a new Action, you can map actions in all sorts of fun and exciting ways, each object has a single responsibility and it is a nice standard pattern so everyone knows what is going on.
The other option would be to separate the sequence from the Action. Have an Action interface with the three Actions inheriting it. Then have a
Sequenceclass with an execute method and a List ofActions