如何在拥有接口和基类的同时避免向下转换

How to avoid downcasting while having interface and base classes?

本文关键字:转换 拥有 接口 基类      更新时间:2023-10-16

我肯定我在这里错过了一些基本的东西,但是我无法理解它。

假设我们有几个可能的Manager类实现,它处理Base类型的对象。应该可以定义在运行时使用哪个实现。

基于Manager的实现,他们将不得不从Base中设置和获得特定的属性,因此他们内部使用的衍生DerivedADerivedB。是否有一种方法可以避免在Handle方法中向下转换参数以获得特定于实现的属性?

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);
}
编辑:谢谢大家的宝贵意见。我会接受@jpo38的帖子,因为它为这个问题提供了一个可能的解决方案。然而,经过一番考虑,我发现这个类的设计存在一个潜在的问题。

您可以使用访问者模式。在您的示例中,这将是:

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 ); }
};

然后,从manager或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 );
}

访问者模式的缺点是每次你想做一些特定的事情时,你都必须定义一个新的访问者类。

您也可以考虑这样做(但我绝对推荐访问者,如果您稍后添加DerivedC或希望通过共享访问者类共享某些特定操作,则非常有用)。

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; }
};

然后,从manager或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
    }
}

不完全是。如果您绝对需要以不同的方式处理特定的子类型,dynamic_cast是最干净的解决方案。

严格地说,这里真正的问题始于"属性"这个词。面向对象的基类没有属性,只有操作,当您接受Base参数时,您感兴趣的只是那些抽象操作。至少在完全干净的面向对象设计中,

你的类设计不是面向对象的,仅此而已。但这本身并不是问题。如果它适合您,并且代码易于阅读和维护,那么一切都很好。