2012-07-02 48 views
1

我正在写一个XML解析器,我需要将对象添加到一般类,切换对象的实际类型。问题是,我想保留一个简单的addElement接口(BaseClass *),然后正确放置对象。什么是切换对象的实际类型的正确方法?

void E_TableType::addElement(Element *e) 
{ 
    QString label = e->getName(); 
    if (label == "state") { 
     state = qobject_cast<E_TableEvent*>(e); 
    } 
    else if (label == "showPaytable") { 
     showPaytable = qobject_cast<E_VisibleType*>(e); 
    } 
    else if (label == "sessionTip") { 
     sessionTip = qobject_cast<E_SessionTip*>(e); 
    } 
    else if (label == "logoffmedia") { 
     logoffMedia = qobject_cast<E_UrlType*>(e); 
    } 
    else { 
     this->errorMessage(e); 
    } 
} 

这是调用类,对象工厂。 myElement是E_TableType的一个实例。

F_TableTypeFactory::F_TableTypeFactory() 
{ 
    this->myElement = myTable = 0; 
} 

void F_TableTypeFactory::start(QString qname) 
{ 
    this->myElement = myTable = new E_TableType(qname); 
} 

void F_TableTypeFactory::fill(const QString& string) 
{ 
    // don't fill complex types. 
} 

void F_TableTypeFactory::addChild(Element* child) 
{ 
    myTable->addElement(child); 
} 

Element* F_TableTypeFactory::finish() 
{ 
    return myElement; 
} 

void F_TableTypeFactory::addAttributes(const QXmlAttributes &attribs) { 
    QString tName = attribs.value(QString("id")); 
    myTable->setTableName(tName); 
} 
+3

正确的方法是最有可能的*不*来。找到更好的解决方案,自动让元素类型做正确的事情。 – Xeo

+0

元素类型都有不同的数据,所以他们只共享相同的创建界面。 – IslandCow

回答

1

Double-dispatch可能会感兴趣的。表(在你的情况下)会调用基本元素的虚拟方法,然后再调用回表中。第二次调用是使用对象的动态类型进行的,因此在Table类中找到适当的重载方法。

#include <iostream> 

class Table; //forward declare 
class BaseElement 
{ 
public: 
    virtual void addTo(Table* t); 
}; 
class DerivedElement1 : public BaseElement 
{ 
    virtual void addTo(Table* t); 
}; 
class DerivedElement2 : public BaseElement 
{ 
    virtual void addTo(Table* t); 
}; 
class Table 
{ 
public: 
    void addElement(BaseElement* e){ e->addTo(this); } 
    void addSpecific(DerivedElement1* e){ std::cout<<"D1"; } 
    void addSpecific(DerivedElement2* e){ std::cout<<"D2"; } 
    void addSpecific(BaseElement* e){ std::cout<<"B"; } 
}; 
void BaseElement::addTo(Table* t){ t->addSpecific(this); } 
void DerivedElement1::addTo(Table* t){ t->addSpecific(this); } 
void DerivedElement2::addTo(Table* t){ t->addSpecific(this); } 

int main() 
{ 
Table t; 
DerivedElement1 d1; 
DerivedElement2 d2; 
BaseElement b; 

t.addElement(&d1); 
t.addElement(&d2); 
t.addElement(&b); 
} 

output: D1D2B

+0

你摇滚!我总是忘记如何做到这一点。 – IslandCow

+0

只是出于懒惰,有没有办法使用父类的addTo,并避免重新实现或打破了输入系统? – IslandCow

+0

您可以在此处使用示例代码尝试:未能在派生类中实现该方法会打破此模式。 – tmpearce

2

你在这里考虑过使用多态吗?如果一个通用的接口可以由每个具体的类实现,那么所有这些代码都会消失,事情会变得简单并且容易在未来发生变化。例如:

class Camera { 
public: 
    virtual void Init() = 0; 
    virtual void TakeSnapshot() = 0; 
} 

class KodakCamera : Camera { 
public: 
    void Init() { /* initialize a Kodak camera */ }; 
    void TakeSnapshot() { std::cout << "Kodak snapshot"; } 
} 

class SonyCamera : Camera { 
public: 
    void Init() { /* initialize a Sony camera */ }; 
    void TakeSnapshot() { std::cout << "Sony snapshot"; } 
} 

那么,假设我们有一个包含硬件设备,在这种情况下,摄像头的系统。每个设备都需要不同的逻辑来拍摄图片,但代码必须支持具有任何支持的相机的系统,所以我们不希望在我们的代码中散布开关语句。所以,我们创建了一个抽象类Camera

每个具体类(即,SonyCamera,KodakCamera)的实现将包括不同的头部,链接到不同的库等,但它们都共享一个公共接口;我们只需要决定在前面创建哪一个。所以......

std::unique_ptr<Camera> InitCamera(CameraType type) { 
    std::unique_ptr<Camera> ret; 
    Camera *cam; 
    switch(type) { 
    case Kodak: 
     cam = new KodakCamera(); 
     break; 
    case Sony: 
     cam = new SonyCamera(); 
     break; 
    default: 
     // throw an error, whatever 
     return; 
    } 

    ret.reset(cam); 
    ret->Init(); 
    return ret; 
} 

int main(...) { 
    // get system camera type 
    std::unique_ptr<Camera> cam = InitCamera(cameraType); 
    // now we can call cam->TakeSnapshot 
    // and know that the correct version will be called. 
} 

所以现在我们有一个实现Camera一个具体的实例。我们可以拨打TakeSnapshot而不检查代码中任何地方的正确类型,因为它无关紧要;我们知道正确的硬件版本会被调用。希望这有助于。

每下方的评论:

我一直在试图使用多态,但我觉得元素相差太多。例如,E_SessionTip有一个数量和状态元素,E_Url只有一个url。我可以在财产体系下将其统一起来,但是我完全失去了所有漂亮的打字。如果你知道这可以工作,但我愿意接受建议。

我会建议将XML数据写入共享通用接口的类型的责任。例如,而不是像这样:

void WriteXml(Entity *entity) { 
    switch(/* type of entity */) { 
     // get data from entity depending 
     // on its type and format 
    } 

    // write data to XML 
} 

做这样的事情:

class SomeEntity : EntityBase { 
public: 
    void WriteToXml(XmlStream &stream) { 
     // write xml to the data stream. 
     // the entity knows how to do this, 
     // you don't have to worry about what data 
     // there is to be written from the outside 
    } 
private: 
    // your internal data 
} 

void WriteXml(Entity *entity) { 
    XmlStream str = GetStream(); 
    entity->WriteToXml(stream); 
} 

这是否对你的工作?我之前完成了这件事,它对我很有帮助。让我知道。

+0

我一直在尝试使用多态,但我认为元素差别太大。例如,E_SessionTip有一个数量和状态元素,E_Url只有一个url。我可以在财产体系下将其统一起来,但是我完全失去了所有漂亮的打字。如果你知道这可以工作,但我愿意接受建议。 – IslandCow

+1

@IslandCow:类型本身可以负责格式化数据吗?如果他们都有一个'WriteData(xml_data_stream * stream)'函数,并且你传递了一个XML数据流(或者你可能拥有的任何形式)并且他们负责写入流?因此,不要询问实例的数据,而是要求您提供一个可写入的流,并且它完成了它的工作。我之前完成了这个工作,它运行得很好。 –

+0

它实际上是一个解析器,而不是作家。如果所有元素的out函数都是相同的,那么这将起作用。但是,我真的只是用自定义类型生成一个DOM树。 – IslandCow

1

看看访问者模式,它可以帮助你

+0

我在想访客,但不太清楚如何使它工作。无论如何,我会给你一个upvote。 – IslandCow

+0

访客模式使用双派遣实施,所以它看起来非常相似 – duselbaer

相关问题