[英]Managing diverse classes with a central manager without RTTI
我有一個設計問題已經困擾了我一段時間,但是我找不到一個好的(從OOP角度來看)解決方案。 語言是C ++,我一直回到RTTI-通常被稱為不良設計的指標。
假設我們有一組不同種類的模塊,它們被實現為不同的類。 每種模塊都有一個定義的接口,但是實現可能會有所不同。 因此,我的第一個想法是為每種模塊(例如IModuleFoo,IModuleBar等)及其單獨的實現創建一個接口(純抽象)類。 到現在為止還挺好。
class IModuleFoo {
public:
virtual void doFoo() = 0;
};
class IModuleBar {
public:
virtual void doBar() = 0;
};
另一方面,我們有一組(應用程序)類,每個類都使用幾個模塊,但只能通過接口使用-甚至模塊本身也可以使用其他模塊。 但是,所有應用程序類將共享相同的模塊池。 我的想法是為所有模塊創建一個管理器類(ModuleManager),應用程序類可以查詢它們所需的模塊類型。 可用的模塊(以及具體的實現)是在管理器初始化期間設置的,可能會隨時間而變化(但這並不是我提出的問題的一部分)。
由於不同模塊種類的數量很可能大於10,並且隨着時間的推移可能會增加,因此我認為不適合單獨存儲指向它們的引用(或指針)。 此外,管理器可能需要在所有托管模塊上調用幾個功能。 因此,我創建了另一個接口(IManagedModule),其好處是現在可以使用IManagedModules的容器(列表,集合等)將它們存儲在管理器中。
class IManagedModule {
public:
virtual void connect() = 0;
{ ... }
};
結果是,要管理的模塊需要繼承自IManagedModule和其類型的適當接口。
但是當我想到ModuleManager時,事情變得很難看。 可以假設每次存在最多每種模塊類型的一個實例。 因此,如果可以做這樣的事情(其中manager是ModuleManager的實例),那么一切都會很好:
IModuleFoo* pFoo = manager.get(IModuleFoo);
但我很確定事實並非如此。 我還考慮過基於模板的解決方案,例如:
IModuleFoo* pFoo = manager.get<IModuleFoo>();
那可能行得通,但是如果我只有一組IManagedModules,我不知道如何在管理器中找到合適的模塊-當然,這是不使用RTTI的。
一種方法是為IManagedModule提供虛擬的getId()方法,依靠實現為每種模塊使用非歧義ID,並自行進行指針轉換。 但這只是重新發明輪子(即RTTI),並且在實現類中需要進行大量訓練(提供正確的id等),這是不希望的。
長話短說-問題是,這里是否真的沒有解決RTTI的方法,在這種情況下,RTTI甚至可能是一個有效的解決方案,或者是否存在一種更好的(更清潔,更安全的...)設計,可以顯示出相同的靈活性(例如,應用程序類和模塊類之間的松散耦合...)? 我有想念嗎?
聽起來您正在尋找與COM的QueryInterface類似的東西。 現在,您不需要完全實現COM,但是基本原理仍然存在:您擁有一個帶有虛函數的基類,並向其傳遞了一個標識符,該標識符指定了所需的接口。 然后,虛擬函數將查看它是否可以實現該接口,如果可以,則將指針傳遞回該接口。
例如:
struct IModuleBase {
// names changed so as not to confuse later programmers with true COM
virtual bool LookupInterface(int InterfaceID, void **interfacePtr) = 0;
// Easy template wrapper
template<typename Interface>
Interface *LookupInterface() {
void *ptr;
if (!LookupInterface(Interface::INTERFACE_ID, &ptr)) return NULL;
return (Interface *)ptr;
}
};
struct IModuleFoo : public IModuleBase {
enum { INTERFACE_ID = 42 };
virtual void foo() = 0;
};
struct SomeModule : public IModuleFoo {
virtual bool LookupInterface(int interface_id, void **pPtr) {
switch (interface_id) {
case IModuleFoo::INTERFACE_ID:
*pPtr = (void*)static_cast<IModuleFoo *>(this);
return true;
default:
return false;
}
}
virtual void foo() { /* ... */ }
};
這有點笨拙,但是還算不錯,沒有RTTI,除了這樣的方法之外,您別無選擇。
我認為bdonlan的建議是好的,但是要求每種模塊類型聲明一個不同的INTERFACE_ID
都是維護上的麻煩。 通過使每種模塊類型聲明一個靜態對象並將其地址用作ID,可以自動實現區別:
struct IModuleFoo : public IModuleBase {
static char distinct_; // Exists only to occupy a unique address
static const void *INTERFACE_ID;
virtual void foo() = 0;
};
// static members need separate out-of-class definitions
char IModuleFoo::distinct_;
const void *IModuleFoo::INTERFACE_ID = &distinct_;
在這種情況下,我們使用void *
作為接口ID類型,而不是int
或枚舉類型,因此某些其他聲明中的類型將需要更改。
同樣,由於C ++中的古怪之處,盡管INTERFACE_ID
值被標記為const
,但其“常量”不足以用於switch
語句(或數組大小聲明或少數其他地方)中的case
標簽,因此您需要將switch
語句更改為if
。 如該標准第5.19節所述, case
標簽需要一個整數常量表達式 ,大致來說,編譯器可以通過查看當前翻譯單元來確定該常量表達式 。 而INTERFACE_ID
僅僅是一個常量表達式 ,其值直到鏈接時間才能確定。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.