我该如何追踪(枚举)所有实现一个接口的类?

8
我有一个情况,我有一个接口定义了程序中某个类的行为,以便填充程序中的某个角色,但此时我不确定我要写多少个类来填充该角色。然而,与此同时,我知道我希望用户能够从GUI组合/列表框中选择实现接口的具体类来填充某个角色。我希望GUI能够列举所有可用的类,但我更希望在决定实现新类来填充该角色时(可能是几个月后),不必返回更改旧代码。
我考虑了一些事情:
  1. 使用枚举
    • 优点:
      1. 我知道如何做
    • 缺点:
      1. 当我添加新类时,必须更新枚举
      2. 枚举遍历起来很繁琐
  2. 在接口中使用某种类型的static列表对象,并从实现类的定义文件中添加一个新元素
    • 优点:
      1. 不需要更改旧代码
    • 缺点:
      1. 甚至不确定是否可能
      2. 不确定要存储什么样的信息,以便工厂方法可以选择适当的构造函数(也许是字符串和返回指向接口对象的指针的函数指针之间的映射)

我猜这可能是更有经验的程序员经常遇到的问题(或类似问题),并且对于这种问题可能有一种常见的解决方案,肯定比我能想出的任何东西都要好。那么,我该怎么做呢?

(附言:我搜索过了,但找到的只有这个如何枚举所有实现泛型接口的项?,而且它并不相同。看起来他已经知道如何解决我正在尝试解决的问题。)

编辑:我将标题改为“如何跟踪...”而不仅仅是“如何枚举...”,因为原始问题听起来更像是我对运行时环境进行了更多的检查,而我真正感兴趣的是编译时的簿记。


什么是接口?是指一个真正的接口类还是该类的特定方法? - TimW
一个真正的接口类:包含各种方法的集合,这些方法定义了与该接口对象的交互。 - cheshirekow
6个回答

8

创建一个单例,其中您可以使用创建函数的指针注册您的类。 在具体类的cpp文件中注册每个类。
像这样:

class Interface;
typedef boost::function<Interface* ()> Creator;

class InterfaceRegistration
{
    typedef map<string, Creator> CreatorMap;
public:
    InterfaceRegistration& instance() {  
        static InterfaceRegistration interfaceRegistration;
        return interfaceRegistration;
    }

    bool registerInterface( const string& name, Creator creator )
    {
        return (m_interfaces[name] = creator);
    }

    list<string> names() const
    {
        list<string> nameList;  
        transform(
            m_interfaces.begin(), m_interfaces.end(), 
            back_inserter(nameList) 
            select1st<CreatorMap>::value_type>() );
    }

    Interface* create(cosnt string& name ) const 
    { 
        const CreatorMap::const_iterator it 
            = m_interfaces.find(name);  
        if( it!=m_interfaces.end() && (*it) )
        {
            return (*it)();
        }
        // throw exception ...
        return 0;
    }

private:
    CreatorMap m_interfaces;
};


// in your concrete classes cpp files
namespace {
bool registerClassX = InterfaceRegistration::instance("ClassX", boost::lambda::new_ptr<ClassX>() );
}

ClassX::ClassX() : Interface()
{
    //....
}

// in your concrete class Y cpp files
namespace {
bool registerClassY = InterfaceRegistration::instance("ClassY", boost::lambda::new_ptr<ClassY>() );
}

ClassY::ClassY() : Interface()
{
    //....
}

1
你可以使用宏来封装注册过程,使其更易于使用: REGISTER_CLASS(ClassY) - Bojan Resnik
1
谢谢,那段代码片段非常有帮助。使用这个额外的类而不是将映射存储为接口的静态成员是否有特定原因?据我所知,它提供了一个合理的“create()”方法,但除此之外还有其他原因吗?这可能特别有用作为通用单例,然后我可以为我将来要扩展的每个接口实例化一个。 - cheshirekow
单一职责原则 - TimW
1
@TimW,非常感谢。我认为这是一个非常好的解决方案。我已经实现了它,而且它运作得非常好。我发布了我的代码以进行分享:http://cheshirekow.com/blog/?p=40 - cheshirekow

3

多年前,我似乎做过类似的事情。你的第二个选项基本上就是我所做的。在那种情况下,它是一个std::map,其中包含std::stringstd::typeinfo。在每个.cpp文件中,我像这样注册了该类:

static dummy = registerClass (typeid (MyNewClass));

registerClass接受一个type_info对象,并简单地返回true。您需要初始化一个变量以确保在启动时调用registerClass。 在全局命名空间中简单地调用registerClass是错误的。 将dummy设置为静态,可以让您在编译单元之间重复使用名称而不会发生名称冲突。


好的,听起来不错。我对静态初始化的知识并不是100%自信,但如果这是一个可行的计划,我会开始尝试它。typeid运算符将会很有帮助。如果我成功了,我会回来把这个作为答案的。 - cheshirekow
重新阅读答案,我认为这个解决方案是最好的(目前为止?)。不需要用工厂方法来混淆代码,它可以与现有代码一起使用,并且比我提出的更直接。由于C++没有太多的RTTI机制,我认为你做不到更好...加1个人。 - neuro
如何创建一个已注册的类? - TimW

2
我参考了这篇文章来实现一个类似于TimW回答中描述的自注册类工厂,但它有一个很好的技巧,使用一个模板化的工厂代理类来处理对象注册。非常值得一看 :)。
C++中的自注册对象 -> http://www.ddj.com/184410633 编辑 这是我做的测试应用程序(稍微整理了一下 ;)):

object_factory.h

#include <string>
#include <vector>
// Forward declare the base object class
class Object;
// Interface that the factory uses to communicate with the object proxies
class IObjectProxy {
public:
    virtual Object* CreateObject() = 0;
    virtual std::string GetObjectInfo() = 0;
};
// Object factory, retrieves object info from the global proxy objects
class ObjectFactory {
public:
    static ObjectFactory& Instance() {
        static ObjectFactory instance;
        return instance;
    }
    // proxies add themselves to the factory here 
    void AddObject(IObjectProxy* object) {
        objects_.push_back(object);
    }
    size_t NumberOfObjects() {
        return objects_.size();
    }
    Object* CreateObject(size_t index) {
        return objects_[index]->CreateObject();
    }
    std::string GetObjectInfo(size_t index) {
        return objects_[index]->GetObjectInfo();
    }

private:
    std::vector<IObjectProxy*> objects_;
};

// This is the factory proxy template class
template<typename T>
class ObjectProxy : public IObjectProxy {
public:
    ObjectProxy() {
        ObjectFactory::Instance().AddObject(this);
    }        
    Object* CreateObject() {
        return new T;
    }
    virtual std::string GetObjectInfo() {
        return T::TalkToMe();
    };    
};

objects.h

#include <iostream>
#include "object_factory.h"
// Base object class
class Object {
public:
    virtual ~Object() {}
};
class ClassA : public Object {
public:
    ClassA() { std::cout << "ClassA Constructor" << std::endl; }
    ~ClassA() { std::cout << "ClassA Destructor" << std::endl; }
    static std::string TalkToMe() { return "This is ClassA"; }
};
class ClassB : public Object {
public:
    ClassB() { std::cout << "ClassB Constructor" << std::endl; }
    ~ClassB() { std::cout << "ClassB Destructor" << std::endl; }
    static std::string TalkToMe() { return "This is ClassB"; }
};

objects.cpp

#include "objects.h"
// Objects get registered here
ObjectProxy<ClassA> gClassAProxy;
ObjectProxy<ClassB> gClassBProxy;

main.cpp

#include "objects.h"
int main (int argc, char * const argv[]) {
    ObjectFactory& factory = ObjectFactory::Instance();
    for (int i = 0; i < factory.NumberOfObjects(); ++i) {
        std::cout << factory.GetObjectInfo(i) << std::endl;
        Object* object = factory.CreateObject(i);
        delete object;
    }
    return 0;
}

输出:

This is ClassA
ClassA Constructor
ClassA Destructor
This is ClassB
ClassB Constructor
ClassB Destructor

但是,如果注册是隐式的,您需要创建每个具体类的实例。我更喜欢显式注册而不需要对象创建。 - TimW
每个具体类都创建一个工厂代理,但实际的类只在需要时通过工厂创建。我今晚回家后会编辑答案并加入一些代码 :) - irh

1

你可以考虑使用对象计数器。这样,你就不需要改变每个分配的地方,只需更改实现定义。这是工厂解决方案的替代方法。请考虑其优缺点。

一种优雅的方法是使用CRTP:奇异递归模板模式。主要示例是这样一个计数器 :)

这样,你只需要在具体类实现中添加:

class X; // your interface

class MyConcreteX : public counter<X>
{
    // whatever
};

当然,如果您使用的是您不精通的外部实现,这就不适用了。

编辑:

要解决确切的问题,您需要有一个计数器,仅计算第一个实例。

我的两分钱


这是一种非常聪明的解决问题的方法。我假设counter<X>扩展了X并包含一个静态对象计数器,在构造时递增并在销毁时递减。虽然我认为这有点间接,但我实际上需要的不是对象计数,而是一种枚举可用子类的方法。 - cheshirekow
并不完全是这样,它使用了值得一看的CRTP模式。请查看我的回答中的链接,因为该计数器的代码已经给出。我回答中的想法是使用一个对象计数器,只计算第一次实例化。我知道有些缺点,比如需要至少实例化一个对象(虽然可以是静态虚拟对象),但似乎比工厂方法解决方案更好。 - neuro

1

如果你使用的是C++/CLI并且在Windows上,这将变得相当容易。.NET框架通过反射提供了这种能力,在托管代码中非常干净。

在本机C++中,这变得有点棘手,因为没有简单的方法来查询库或应用程序的运行时信息。有许多提供此功能的框架(只需查找IoC、DI或插件框架),但自己实现最简单的方法是拥有某种形式的配置,工厂方法可以使用它们进行注册,并返回特定基类的实现。你只需要实现加载DLL和注册工厂方法-一旦你掌握了这个,就相当容易了。


我正在使用Windows,但是不幸的是,程序也需要在Linux上运行(因此我正在使用GNU编译器)。我不需要查询运行时。事实上,我更喜欢编译时(或者说链接时)的解决方案。 - cheshirekow

0

在(本地)C++中,没有办法查询一个类的子类。如何创建实例?考虑使用工厂方法,允许您迭代所有正在使用的子类。当您像这样创建实例时,不可能忘记稍后添加新的子类。


这正是我想做的,但我希望有一种清晰的方法来进行簿记,这样我就不必每次编写新的子类时都更新那个工厂方法的代码。 - cheshirekow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接