2010-08-02 66 views
6

我正在使用抽象工廠創建用戶界面組件,如對話框。所使用的抽象工廠從當前選擇的通用「INode」返回,INode是幾種不同類型節點的基類。因此,舉例來說,如果我想添加相同類型選擇節點的新節點,該方案是這樣的:使用抽象工廠的問題

(請注意,這是半僞代碼)

用戶點擊節點,節點被存儲供以後使用:

void onTreeNodeSelected(INode *node) 
{ 
    selectedNode = node; 
} 

用戶點擊用戶界面上的「添加」:

void onAddClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createAddDialog(parentWidget); 
    dialog->show(); 
} 

這一切似乎罰款。問題出現在我想編輯選定節點時:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget); 
    dialog->show(); 
} 

哦,親愛的..我傳入一個INode對象。在某些時候,我將不得不將它們轉換爲正確的節點類型,以便對話框可以正確使用它。

我研究過「PostgreSQL Admin 3」的源代碼,他們做了類似的事情。他們通過做這樣的事情來繞過它:

FooObjectFactoryClass::createDialog(IObject *object) 
{ 
    FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object); 
} 

Yeck .. cast!

我能想到繞過它,仍然可以使用我的工廠唯一的辦法是到節點自身注入到工廠返回之前:

FooNode : INode 
{ 
    FooNodeFactory* FooNode::getFactory() 
    { 
     fooNodeFactory->setFooNode(this); 
     return fooNodeFactory; 
    } 
} 

所以後來我的編輯事件可以這樣做:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(parentWidget); 
    dialog->show(); 
} 

它將使用注入的節點作爲上下文。

我想如果沒有注入代碼,createEditDialog可能會聲明錯誤或其他東西。

有什麼想法?

謝謝!

回答

3

一個常見的解決方案是「double-dispatch」,您可以在一個對象上調用虛擬函數,然後在另一個對象上調用虛擬函數,並傳遞this,該函數現在具有正確的靜態類型。所以,在你的情況下,工廠可以包含各種類型的對話「創造」功能:

class IFactory 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(ThisNode*, IWidget*); 
    virtual Dialog* createEditDialog(ThatNode*, IWidget*); 
    virtual Dialog* createEditDialog(TheOtherNode*, IWidget*); 
    .... 
}; 

那麼每個類型的節點有一個虛擬createEditDialog一個分派到正確的工廠函數:

class INode 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(IWidget* parent) = 0; 
    .... 
}; 

class ThisNode : public INode 
{ 
public: 
    .... 
    virtual Dialog* ThisNode::createEditDialog(IWidget* parent) 
    { 
     return getFactory()->createEditDialog(this, parent); 
    } 
    .... 
}; 

然後你就可以創建正確的對話

void onEditClicked() 
{ 
    Dialog *dialog = selectedNode->createEditDialog(parentWidget); 
    dialog->show(); 
} 
1

在我看來,只要您的代碼得到正確評論,使用C風格演員(儘管C++風格將是首選)是完全可以接受的。

我不是DI(dependency injection)的粉絲,因爲它使一些代碼難以遵循,在你的情況下,我寧願看看dynamic_cast<>()或其他東西,而不是試圖在多個源文件中注入代碼。

0

我會消化兩件事。

第一:鑄造沒有錯。如果你想要安全,你可以在INode類中使用RTTI(type_id stuff)或一些虛函數,它可以返回一些信息,讓你知道它是否安全。其次:你可以檢查createEditDialog函數需要什麼,並將它們放在INode或者是類型爲createDialog的繼承類中。

總的來說,我沒有看到你描述的問題真的有問題,沒有看到整個代碼很難給出更多的建議,我認爲這是不可行的。

0

你的節點注入到工廠的方法通常是我發現有用的模式,但也有常當你在創建工廠時沒有對目標對象的引用時,就像你在這裏做的那樣。因此,在這種情況下,這可能適合您,在一般情況下比處理這類問題更簡單。

對於更一般的情況,您需要使用接口的概念並建立一個機制,通過該機制,您的對象可以發佈它支持的接口併爲客戶端提供對這些接口的訪問。完全動態地做到這一點導致類似於COM的方法需要動態註冊和投射。但是,如果您想要公開一組相對穩定的接口,並且在需要添加新的組件接口時可以編輯接口,您也可以以靜態類型的方式執行此操作。

因此,這將是怎樣做簡單的靜態類型的方法的例子:

struct INode 
{ 
    virtual INodeSize* getNodeSizeInterface() = 0; 

    virtual INodeProperties* getNodePropertiesInterface() = 0; 

    virtual INodeColor* getNodeColorInterface() = 0; 

    ... // etc 
} 

現在每個INode實現可以返回部分或全部組件接口(它只是返回NULL,如果它沒」實施它們)。然後,您的對話框將在組件接口上進行操作,而不是試圖找出傳入的實際實現是INode。這將使對話框和節點實現之間的映射更爲靈活。對話框可以通過驗證它是否爲對話框感興趣的每個接口返回有效對象來快速確定它是否具有「兼容」對象。

0

我認爲在這種情況下,createEditDialog內部的劇組並不是一件壞事即使你放棄編譯時間檢查。如果節點的類型在運行時沒有更改,則可以使用模板而不是抽象的INode-類。

否則,您提出的解決方案就是我所想到的解決方案。但是,我會將方法重命名爲「getSelectedNodeDialogFactory」(我知道,長名稱),這樣很明顯返回的工廠是特定於該節點的。是否有其他對話需要知道對象的具體類型? createAddDialog是否需要父代或前代節點,也許?這些都可以在工廠選擇節點類中進行。