2012-07-02 79 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

相關問題