簡體   English   中英

在沒有RTTI的情況下通過中央管理器管理各種班級

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM