Strategy pattern

Discussion in 'C++' started by syncman, Jan 14, 2004.

  1. syncman

    syncman Guest

    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.
     
    syncman, Jan 14, 2004
    #1
    1. Advertisements

  2. syncman

    Chris Theis Guest

    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?
    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
     
    Chris Theis, Jan 15, 2004
    #2
    1. Advertisements

  3. 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
     
    Yannick Le goc, Jan 15, 2004
    #3
  4. 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
     
    Martijn Lievaart, Jan 15, 2004
    #4
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.