如何跟踪(枚举)实现接口的所有类

Posted

技术标签:

【中文标题】如何跟踪(枚举)实现接口的所有类【英文标题】:How can I keep track of (enumerate) all classes that implement an interface 【发布时间】:2010-11-18 15:47:53 【问题描述】:

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

我考虑过的一些事情:

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

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

(P.S. 我搜索过,但我发现的只是这个,而且不一样:How do I enumerate all items that implement a generic interface?。看来他已经知道如何解决我试图弄清楚的问题了。)

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

【问题讨论】:

接口是什么意思?是真正的接口类还是类的具体方法? 真正的接口类:定义与该接口对象交互的各种方法的集合 【参考方案1】:

创建一个单例,您可以在其中使用指向创建者函数的指针注册您的类。 在具体类的 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()

    //....

【讨论】:

并且你可以将注册包装在一个宏中以便于使用:REGISTER_CLASS(ClassY) 谢谢,代码 sn-p 很有帮助。使用这个额外的类而不是将地图存储为接口的静态成员是否有任何特殊原因。据我所知,它提供了一个合理的“create()”方法,但还有其他原因。这作为一个通用的单例可能特别有用,然后我可以为我知道我将来会扩展的每个接口实例化一个。 @cheshirekow 单一责任原则 @TimW,非常感谢。我认为这是一个非常好的解决方案。我已经实现了它并且效果很好。我发布了我的代码分享:cheshirekow.com/blog/?p=40【参考方案2】:

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

static dummy = registerClass (typeid (MyNewClass));

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

【讨论】:

好的,听起来不错。我对静态初始化的知识不是 100% 有信心,但如果这是一个可行的计划,我会开始尝试。 typeid 运算符会很有帮助。如果我让它工作,我会回来并把它作为答案。 重读答案 我认为这个解决方案是最好的解决方案(目前?)。无需使用工厂方法将代码弄乱,它可以与现有代码一起使用,并且比我提出的更直接。由于 C++ 没有很多 RTTI 机制,我认为你不能做得更好...... +1 人。 那么如何创建注册类?【参考方案3】:

我参考这篇文章来实现一个类似于 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

【讨论】:

但是注册是隐式的,您需要为每个具体类创建一个实例。我更喜欢不创建对象的显式注册。 您为每个具体类创建一个工厂代理,但类本身仅在需要时通过工厂创建。今晚回家后我会用一些代码编辑答案:)【参考方案4】:

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

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

【讨论】:

我在 windows 上,但是,唉,程序也需要在 linux 上运行(所以我使用的是 GNU 编译器)。我不需要去查询运行时。事实上,我更喜欢编译时(或者更确切地说,链接时)解决方案。【参考方案5】:

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

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

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

class X; // your interface

class MyConcreteX : public counter<X>

    // whatever
;

当然,如果你使用你不掌握的外部实现,它是不适用的。

编辑:

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

我的 2 美分

【讨论】:

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

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

【讨论】:

这正是我想做的,但我想要一种干净的方式来记账,这样我就不必每次编写新的子类时都更新该工厂方法的代码。

以上是关于如何跟踪(枚举)实现接口的所有类的主要内容,如果未能解决你的问题,请参考以下文章

学妹问我Java枚举类与注解,我直接用这个搞定她!

C#知识点-枚举器和迭代器

学妹问我Java枚举类与注解,我直接用这个搞定她!

OAuth2AuthenticationManager源码跟踪

如何通过 C# 实现对象的变更跟踪 ?

微服务分布式架构中,如何实现日志链路跟踪