8

我有一种情况,我有一个接口,它定义了某个类的行为以填补我的程序中的某个角色,但此时我不能 100% 确定我将编写多少个类来填补该角色. 但是,与此同时,我知道我希望用户能够从 GUI 组合/列表框中选择实现他们想要使用的接口的具体类来填补某个角色。我希望 GUI 能够枚举所有可用的类,但我不希望每当我决定实现一个新类来填补该角色时不必返回并更改旧代码(这可能是几个月后)

我考虑过的一些事情:

  1. 使用枚举
    • 优点:
      1. 我知道该怎么做
    • 缺点
      1. 添加新类时,我将不得不更新枚举
      2. 难看的迭代
  2. 在接口中使用某种static列表对象,并从实现类的定义文件中添加一个新元素
    • 优点:
      1. 不必更改旧代码
    • 缺点:
      1. 甚至不确定这是否可能
      2. 不确定要存储什么样的信息,以便工厂方法可以选择正确的构造函数(可能是字符串和返回指向接口对象的指针的函数指针之间的映射)

我猜这是一个更有经验的程序员可能以前(并且经常)遇到过的问题(或类似于问题),并且可能有一个针对此类问题的通用解决方案,这几乎肯定比我的任何事情都好我有能力想出。那么,我该怎么做呢?

(PS我搜索过,但我发现的只是这个,而且不一样:我如何枚举所有实现通用接口的项目?。看来他已经知道如何解决我试图弄清楚的问题。)

编辑:我将标题重命名为“我如何跟踪...”而不仅仅是“我如何枚举...”,因为最初的问题听起来像是我对检查运行时环境更感兴趣,因为我'我真正感兴趣的是编译时簿记。

4

6 回答 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()
{
    //....
}
于 2009-08-11T15:34:39.250 回答
3

我依稀记得很多年前做过类似的事情。您的选择(2)几乎就是我所做的。在那种情况下,它是std::map一个std::stringto std::typeinfo。在每个 .cpp 文件中,我注册了这样的类:

static dummy = registerClass (typeid (MyNewClass));

registerClass接受一个type_info对象并简单地返回true. 您必须初始化一个变量以确保registerClass在启动时调用它。简单地调用registerClass全局命名空间是错误的。并且使dummystatic 允许您跨编译单元重用名称而不会发生名称冲突。

于 2009-08-11T15:22:58.510 回答
2

我参考这篇文章来实现一个类似于 TimW 的答案中描述的自注册类工厂,但它有一个很好的技巧,即使用模板化工厂代理类来处理对象注册。非常值得一看:)

C++ 中的自注册对象 -> http://www.ddj.com/184410633

编辑

这是我做的测试应用程序(整理了一下;):

对象工厂.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();
    };    
};

对象.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"; }
};

对象.cpp

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

主文件

#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
于 2009-08-11T16:34:56.743 回答
1

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

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

于 2009-08-11T15:17:29.347 回答
1

您可以考虑的是对象计数器。这样,您无需更改分配的每个位置,而只需更改实现定义。它是工厂解决方案的替代方案。考虑优点/缺点。

一种优雅的方法是使用CRTP:Curiously recurring template pattern。主要的例子就是这样一个计数器:)

这样你只需要添加你的具体类实现:

class X; // your interface

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

当然,如果您使用您不掌握的外部实现,则不适用。

编辑:

要处理确切的问题,您需要有一个只计算第一个实例的计数器。

我的 2 美分

于 2009-08-11T15:48:17.413 回答
0

无法在(本机)C++ 中查询类的子类。您如何创建实例?考虑使用允许您迭代所有正在使用的子类的工厂方法。当您创建这样的实例时,以后不会忘记添加新的子类。

于 2009-08-11T15:22:23.037 回答