Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid downcasting while having interface and base classes?

I'm sure i'm missing something elemental here, but i cannot get my head around it.

Let's say we have several possible implementations of a Managerclass which handles objects of type Base. It should be possible to define which implementation to use at runtime.

Based on the implementation of the Manager, they will have to set and get specific properties from Base, therefore the derivations DerivedA and DerivedB which they use internally. Is there a way to circumvent the need for downcasting the parameter in the Handle methods in order to get to the implementation-specific properties?

class Base { /* Abstract class with common properties */ };
class DerivedA : public Base { /* DerivedA-specific properties */ };
class DerivedB : public Base { /* DerivedB-specific properties */ };

class IManager { /* These functions must be implemented by every Manager implementation */
public:
    virtual Base* Create() = 0;
    virtual void Handle(Base*) = 0;
};

class AManager : public IManager
{
public:
    Base* Create() override { return new DerivedA(); }
    void Handle(Base* pFoo) override 
    { 
        // Now if we want to access pFoo's specific properties, we will need to dynamic_cast it
    }
};

class BManager : public IManager
{
public:
    Base* Create() override { return new DerivedB(); }
    void Handle(Base* pBar) override { /* same here */ }
};

void Run(bool useAManager)
{
    IManager* pManager = nullptr;

    if (useAManager)
        pManager = new AManager();
    else
        pManager = new BManager();

    Base* pData = pManager->Create();
    /* use Base specific properties ... */
    pManager->Handle(pData);
}

Edit: Thank you all for the valuable input. I will accept @jpo38's post since it provides a possible solution to this problem. After some consideration however, I found that there is an underlying problem with the class design.

like image 852
Hirz Avatar asked Jul 27 '14 16:07

Hirz


1 Answers

You can use the visitor pattern. In your example, this would be:

class DerivedA;
class DerivedB;
class Visitor
{ 
public:
    virtual void visitA( DerivedA& a ) = 0;
    virtual void visitB( DerivedB& b ) = 0;
};


class Base 
{ 
public:
    virtual void Accept( Visitor& visitor ) = 0;
};

class DerivedA : public Base 
{ 
public:
    virtual void Accept( Visitor& visitor ) { visitor.visitA( *this ); }
};

class DerivedB : public Base 
{ 
public:
    virtual void Accept( Visitor& visitor ) { visitor.visitB( *this ); }
};

Then, from AManager or BManager:

void Handle(Base* pFoo) 
{ 
    class MyVisitor : public Visitor
    {
    public:
        virtual void visitA( DerivedA& a )
        {
             // do somethiong specific to a, you have access to DerivedA
        }
        virtual void visitB( DerivedB& b )
        {
             // do somethiong specific to b, you have access to DerivedB
        }
    };
    MyVisitor v;
    pFoo->Accept( v );
}

The disadvantage of visitor pattern is that you'll have to define a new visitor class every time you'll want to do something specific.

You can also consider doing this (but I definitely recommend visitors, very helful if you add DerivedC later or want to share some specific operation through shared visitor classes).

class Base 
{ 
public:
    virtual DerivedA* GetAsA() = 0;
    virtual DerivedB* GetAsB() = 0;
};

class DerivedA : public Base 
{ 
public:
    virtual DerivedA* GetAsA() { return this; }
    virtual DerivedB* GetAsB() { return NULL; }
};

class DerivedB : public Base 
{ 
public:
    virtual DerivedA* GetAsA() { return NULL; }
    virtual DerivedB* GetAsB() { return this; }
};

Then, from AManager or BManager:

void Handle(Base* pFoo) 
{ 
    if ( pFoo->GetAsA() )
    {
        // use GetAsA to access DerivedA object avoiding downcasting
    }
    if ( pFoo->GetAsB() )
    {
        // use GetAsB to access DerivedB object avoiding downcasting
    }
}
like image 159
jpo38 Avatar answered Oct 06 '22 21:10

jpo38