Dissecting Code

Wednesday, March 13, 2013

Visitor Pattern

Working on implementing a SceneGraph, I came across the problem of allowing for operations on each node, without polluting the Node class itself. For example, for debugging purposes, I might want to have each node print out its type. So, I would have a Node.getType function, which returns the type of the node.

But, I wouldn't want to have a Node.printType function that prints its type and also recursively traverses its children to print their types.

The visitor pattern does exactly this. Conceptually, it allows for a virtual function to be added to an entire class hierarchy, without having to put the function within the class hierarchy itself. Below is a example for a usage of the pattern. Note that double-dispatch won't be required in case the class hierarchy places a constraint on the visitors to treat the visited nodes transparently, which is perhaps too much of a restriction. 

#include <iostream>
#include <list>
#include <string>

using namespace std;

class Asteroid;
class MyAsteroid;

class IAsteroidVisitor
{
public:
    virtual void visit(Asteroid&) = 0;
    virtual void visit(MyAsteroid&) = 0;
};

class Asteroid
{
public:
    virtual string getType()
    {
        return "Asteroid";
    }

    virtual void accept(IAsteroidVisitor& visitor)
    {
        visitor.visit(*this);
    }
    
};

class MyAsteroid : public Asteroid
{
public:
    string getType() override
    {
        return "My Asteroid";
    }

    string doFunkyStuff()
    {
        return "I do something the normal asteroid can't!";
    }

    void accept(IAsteroidVisitor& visitor) override
    {
        visitor.visit(*this);
    }
};

class AsteroidList
{
private:
    list<Asteroid*> m_pAsteroidList;
public:
    AsteroidList()
    {
        m_pAsteroidList.push_back(new Asteroid());
        m_pAsteroidList.push_back(new MyAsteroid());
    }

    void accept(IAsteroidVisitor& visitor)
    {
        for (list<Asteroid*>::const_iterator i = m_pAsteroidList.cbegin(); i != m_pAsteroidList.cend(); i++)
        {
            (*i)->accept(visitor);
        }
    }
};

class AsteroidPrintVisitor : public IAsteroidVisitor
{
public:
    void visit(Asteroid& a) override
    {
        cout<<a.getType()<<endl;
    }

    void visit(MyAsteroid &ma) override
    {
        cout<<ma.getType()<<endl;
        cout<<ma.doFunkyStuff()<<endl;
    }
};

int main(int argc, char *argv[])
{
    AsteroidList al;
    AsteroidPrintVisitor p;
    al.accept(p);
    return 0;
}


Labels: ,

Monday, July 19, 2010

Factory Patterns

1. The Parameterized factory:

#include <iostream>

class Shape
{
public:
virtual void Draw() = 0;
};

class Circle: public Shape
{
public:
void Draw(){ std::cout<<"Circle::Draw"; }
};

class Square: public Shape
{
public:
void Draw(){ std::cout<<"Sqaure::Draw"; }
};

enum ShapeType{ Shape_Circle, Shape_Square };

class Factory
{
public:
static Shape& CreateShape( ShapeType shapeType );
};

Shape& Factory::CreateShape( ShapeType type )
{
switch ( type )
{
case Shape_Circle: return *new Circle();
break;
case Shape_Square: return *new Square();
break;
default: break;
}
}

int main()
{
Shape& s=Factory::CreateShape( Shape_Circle );
s.Draw();
return 0;
}



Disadvantages: Adding a new type of shape will need changes in the Factory method.



Getting around by the use of templates, we have:



#include <iostream>

class Shape
{
public:
virtual void Draw() = 0;
};

class Circle: public Shape
{
public:
void Draw(){ std::cout<<"Circle::Draw"; }
};

class Square: public Shape
{
public:
void Draw(){ std::cout<<"Sqaure::Draw"; }
};

template <class T>
class Factory
{
public:
static Shape& CreateShape();
};

template <class T>
/*static*/ Shape& Factory<T>::CreateShape()
{
return *new T();
}

int main()
{
Shape& s=Factory<Circle>::CreateShape();
s.Draw();
return 0;
}




There we have a much more concise version using the power of templates.

Labels: , ,