使用没有RTTI的中央管理员管理不同的课程

时间:2011-02-26 17:21:11

标签: c++ oop rtti

我有一个设计问题一直困扰我一段时间,但我找不到一个好的(在OOP意义上)解决方案。语言是C ++,我一直回到RTTI - 这通常被称为糟糕设计的指标。

假设我们将一组不同类型的模块实现为不同的类。每种模块的特征在于定义的接口,但是实现可以变化。 因此,我的第一个想法是为每种模块(例如IModuleFoo,IModuleBar等)创建一个接口(纯抽象)类,并在单独的类中创建实现。到目前为止一切都很好。

class IModuleFoo {
  public:
    virtual void doFoo() = 0;
};

class IModuleBar {
  public:
    virtual void doBar() = 0;
};

另一方面,我们有一组(应用程序)类,每个类都使用其中的几个模块,但只能通过接口 - 甚至模块本身也可能使用其他模块。但是,所有应用程序类将共享相同的模块池。我的想法是为所有模块创建一个管理器类(ModuleManager),应用程序类可以查询它们所需的模块类型。可用模块(以及具体实现)是在管理器初始化期间设置的,并且可能随时间而变化(但这不是我的问题的一部分)。

由于不同模块类型的数量很可能> 10并且可能随着时间的推移而增加,因此我似乎不适合将它们分别存储到它们的引用(或指针)。此外,管理员可能需要在所有托管模块上调用几个函数。因此,我创建了另一个接口(IManagedModule),其好处是我现在可以使用IManagedModules的容器(list,set,无论如何)将它们存储在管理器中。

class IManagedModule {
  public:
    virtual void connect() = 0;
    { ... }
};

结果是,应该管理的模块需要从IManagedModule和适当的接口继承它的类型。

但是当我想到ModuleManager时,事情会变得丑陋。可以假设每次存在每个模块类型最多一个实例。因此,如果有可能做这样的事情(管理器是ModuleManager的实例),一切都会好的:

IModuleFoo* pFoo = manager.get(IModuleFoo);

但我很确定不是。我还想到了一个基于模板的解决方案,如:

IModuleFoo* pFoo = manager.get<IModuleFoo>();

这可行,但我不知道如何在管理器中找到正确的模块,如果我拥有的是一组IManagedModules - 当然不使用RTTI。

一种方法是为IManagedModule提供虚拟 getId()方法,依靠实现为每种模块使用非ambidous id并自行执行指针转换。但这只是重新发明轮子(即RTTI)并且需要在实施类中提供大量的纪律(提供正确的ID等等),这是不可取的。

长话短说 - 问题是这里是否真的没有办法解决某种RTTI问题,在这种情况下,RTTI甚至可能是一个有效的解决方案,或者是否有更好的(更清洁,更安全......)设计它表现出相同的灵活性(例如应用程序类和模块类之间的松散耦合......)?我错过了什么吗?

2 个答案:

答案 0 :(得分:1)

听起来你正在寻找与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你除了这样的方法之外别无选择。

答案 1 :(得分:0)

我认为bdonlan's suggestion很好,但要求每个模块类型声明一个不同的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,但并不“足够常量”,无法用于case中的switch标签语句(或数组大小声明,或少数其他地方),因此您需要将switch语句更改为if。如标准第5.19节所述,case标签需要整数常量表达式,粗略地说,编译器可以通过查看当前的转换单元来确定它;虽然INTERFACE_ID仅仅是常量表达式,但在链接时间之前无法确定其值。