Strategy pattern

S

syncman

I think there are 2 options for how to implement the Strategy pattern.
One is to use polymorphism; derived classes have the same interface
and can be plugged in. The other is to use templates: Instantiate the
class, passing in a functor (algorithm). Code samples below.

The problem with the first way is that the interface must be the same
(parameters passed in) for all strategies. The problem with the
second way is that there is no common base class, and that means that
I can't put these items on an STL queue (which requires that all its
types be homogeneous).

In my case, I want to make a general Task class, and put them on a
queue. Later, they will be taken off and executed by calling their
function do(). But in cases where you want to pass a few things in,
how do you do it? You could use Windows old trick of passing in 2
general parameters, one being a pointer to whatever you wish. But you
still need type information to interpret it.

Treat this as semi-pseudocode: You don't need this code to answer the
question.

// -------------------------
//First way: polymorphism
class base_strategy (string filename, int offset)
{
string filename, int offset;
base_strategy (string filename, int offset)
: filename(filename),offset(offset) {}
virtual void do() =0;
};

class strategy1 (string filename, int offset)
{
string filename, int offset;
strategy1 (string filename, int offset)
: filename(filename),offset(offset) {}
virtual void do() {//print contents of file starting at offset};
};

// But what if I want a strategy that also inputs another number?
// It's too late to change the interface.
// And isn't it illegal to have a container of pointers? Some STL
rule?

main()
{
queue<base_strategy *> q;
base_strategy *p = new strategy1 ("a.cpp", 234);
q.insert (p);
}

// ------------------------
//Second way: functors (function object)
class strategy1
{
int a,b;
strategy1 (int a, int b) : a(a), b(b) {};
void operator()(int i) {output a*b+2*i;}
}

for_each(q.begin(), q.end(), strategy1(3,4));

// That would work fine, but this doesn't help me with containers.

Sorry for the long message.
 
C

Chris Theis

syncman said:
I think there are 2 options for how to implement the Strategy pattern.
One is to use polymorphism; derived classes have the same interface
and can be plugged in. The other is to use templates: Instantiate the
class, passing in a functor (algorithm). Code samples below.
[SNIP]

Treat this as semi-pseudocode: You don't need this code to answer the
question.

I just wonder, if you take the time to type in this "pseudo" code, why not
do it correct with all the semicolons and so on?
// -------------------------
//First way: polymorphism
class base_strategy (string filename, int offset)
{
string filename, int offset;
base_strategy (string filename, int offset)
: filename(filename),offset(offset) {}
virtual void do() =0;
};

class strategy1 (string filename, int offset)
{
string filename, int offset;
strategy1 (string filename, int offset)
: filename(filename),offset(offset) {}
virtual void do() {//print contents of file starting at offset};
};

// But what if I want a strategy that also inputs another number?
// It's too late to change the interface.
// And isn't it illegal to have a container of pointers? Some STL
rule?

No it is not illegal to have a container of pointers. The requirements of
the standard containers can be found in the C++ standard, and there it says:
23.1

-3- The type of objects stored in these components must meet the
requirements of CopyConstructible types (lib.copyconstructible), and the
additional requirements of Assignable types.

Thus, having, e.g., a vector of pointers is perfectly okay.

[SNIP]

IMHO having a common interface of all strategies (at least to the outside
world) is not a drawback but a good thing. A common way to implement a
strategy pattern is to have an object that defines the interface and this
object obtains a pointer to the actual implementation.

For example: (Of course the actual integration objects like Simpson, Gauss
and so on should be singletons, but I will skip this here).

class CIntAlgorithm {
// some common base stuff
};

class CSimpsonInt : public CIntAlgorithm {
public:
double Integrate( double Min, double Max, double NrOfSteps ) { ....};
};

class CGaussInt : public CIntAlgorithm {
public:
double Integrate( double Min, double Max, double NrOfSteps, int
AdditionalParam ) { ....};
};

class CIntegrator {
public:
CIntegrator( CIntAlgorithm* pAlgo ) : m_pAlgo( pAlgo ) {};

double Integrate( double Min, double Max, double NrOfSteps ) {

// If you need to differentiate in the use of Simpson & Gauss you
could use dynamic_cast for example.
};

protected:
CIntAlgorithm* m_pAlgo;
};

HTH
Chris
 
Y

Yannick Le goc

syncman said:
I think there are 2 options for how to implement the Strategy pattern.
One is to use polymorphism; derived classes have the same interface
and can be plugged in. The other is to use templates: Instantiate the
class, passing in a functor (algorithm). Code samples below.

The problem with the first way is that the interface must be the same
(parameters passed in) for all strategies. The problem with the
second way is that there is no common base class, and that means that
I can't put these items on an STL queue (which requires that all its
types be homogeneous).

In my case, I want to make a general Task class, and put them on a
queue. Later, they will be taken off and executed by calling their
function do(). But in cases where you want to pass a few things in,
how do you do it? You could use Windows old trick of passing in 2
general parameters, one being a pointer to whatever you wish. But you
still need type information to interpret it.

What do you think of using templates and polymorphism like this:

// Interface => abstract class
class StrategyInterface
{
public:
StrategyInterface();

virtual void do() = 0;
};

template<class T>
class Strategy : StrategyInterface
{
public:
virtual void do() {...};
}

and you benefit from template specialization, and can use it:

main()
{
queue<StrategyInterface *> q;
StrategyInterface *p = new Strategy<float> ();
q.insert(p);
}

Yannick
 
M

Martijn Lievaart

I think there are 2 options for how to implement the Strategy pattern.
One is to use polymorphism; derived classes have the same interface
and can be plugged in. The other is to use templates: Instantiate the
class, passing in a functor (algorithm). Code samples below.

The problem with the first way is that the interface must be the same
(parameters passed in) for all strategies. The problem with the
second way is that there is no common base class, and that means that
I can't put these items on an STL queue (which requires that all its
types be homogeneous).

In my case, I want to make a general Task class, and put them on a
queue. Later, they will be taken off and executed by calling their
function do(). But in cases where you want to pass a few things in,
how do you do it? You could use Windows old trick of passing in 2
general parameters, one being a pointer to whatever you wish. But you
still need type information to interpret it.

Combine the two aproaches. Your functor approach is fine, just make them
inherit from a common baseclass. (Don't forget the virtual destructor,
your stategy_base class should have had one).

Now put pointers to these object in a container. Containers of pointers
are fine, and in fact very often used. It's containers of std::auto_ptr
that cannot be done. You may want to use boost::shared_ptr for this, or
just delete every pointer in the queue manually once you're done with them.

This does not allow one to use for_each and related algorithms easily (but
look up std::mem_fun), but that is a small price to pay. I'ld advice to do
it the way I told you above, but...

.... you could implement a functor that holds a pointer to some functor
implementation baseclass. You'll need to implement copy construction and
assignment of this functor in an inteligent way (use boost::shared_ptr to
make it trivial). Then make operator() of the functor call operator() on
this baseclass and you're done!

I would advice you to walk, no run, to your nearest bookstore and get
"Modern C++ Design" by Alexandrescu. It goes into great depth on this, and
other, subjects. A must have for every advanced C++ programmer. Also have
a look at boost::bind, it is very useful in situations like this.

HTH,
M4
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top