我有一个几乎所有东西都是插件的架构。该体系结构是图形用户界面的基础,其中每个插件由“表面”(即用户可以通过其与插件交互的UI控件)表示。这些表面也是插件。每当添加新插件时,瘦主机会自动确定哪个可用曲面是最佳UI匹配。如何在C ++中完成动态类型匹配是这个问题的主题。
目前,架构是用C#实现的,主要依赖于反射,正如您将看到的那样。但是,我现在正在为C ++重新设计整个过程,但由于C ++没有反射(因为我是C ++的新手),我需要一些关于如何在没有它的情况下最好地复制这个功能的输入。
以下是C#(简化和伪)中目前的工作方式:
所有插件都是Plugin
类的后代。
每个表面类型都标有“目标插件类型”,表示其设计用于插件的最具体/最深层的后代。
表面中最通用的是只处理插件级别的表面(例如,只显示名称标签)。这保证了所有插件至少有一个能够在屏幕上表示它们的表面,即使它没有提供任何有用的交互方式。
让我们为PluginSurface
调用这个“基线”表面类型。如果 - 为了简洁起见(实际上它是通过类属性完成的) - 我们在表面上使用一个属性来表示它们的目标类型,我们有
PluginSurface.TargetType = typeof(Plugin)
除非添加更多特定表面,否则所有插件都将被分配此通用表面,无论它们位于插件继承层次结构中的哪个位置。
现在,假设我们添加一个名为Father的新插件类型,派生自Plugin:
Father : Plugin
然后我们再添加一些,以创建一个小样本层次结构:
Mother : Plugin
Daughter : Father
Son1 : Mother
Son2 : Mother
Grandson : Son1
此时,所有这些都将通过通用PluginSurface
在屏幕上显示。因此,我们创建了更多专门处理新插件功能的附加表面(所有表面都来自PluginSurface):
MotherSurface.TargetType = typeof(Mother)
GrandsonSurface.TargetType = typeof(Grandson)
由此,插件到表面的有效类型匹配应该是:
Father -> PluginSurface
Daughter -> PluginSurface
Mother -> MotherSurface
Son1 -> MotherSurface
Son2 -> MotherSurface
Grandson -> GrandsonSurface
好的,这就是我们期望最终得到的映射。本质上,匹配是作为主机中的静态函数实现的,它接受插件类型并返回最佳拟合表面的实例:
PluginSurface GetBestFitSurface(Type pluginType)
该函数遍历所有可用的表面,并根据提供的TargetType
检查其pluginType
属性。更具体地说,它检查TargetType IsAssignableFrom
是否为pluginType。它会创建所有正面匹配的列表。
然后需要将这个候选人名单缩小到最合适的。为此,我对可分配性进行了类似的检查,但这次在所有候选人的TargetTypes 之间,检查每个候选人的每个其他人,并且对于每个候选人,我记下了其中有多少人。候选者的TargetTypes其TargetType可分配给。可以将TargetType分配(即强制转换)为大多数其他TargetTypes的候选者是最合适的。
后来发生的事情是,这个表面成为有问题的插件的包装器,在屏幕上安排自己以反映它“理解”的插件的功能,这取决于适合的紧密度。 GrandsonSurface
专门用于表示GrandSon
,因此它将是该插件的完整用户界面。但Son1
和Son2
“仅”获得MotherSurface
,因此与Mother
不具有共同点的任何功能都不会包含在其UI表示中(直到制作了一个更专业的Son
界面,此时 将是最合适的界面等)。请注意,这是预期用途 - 通常,母插件将是抽象,并且针对它的界面实际上是 Son接口,旨在包装所有母亲的子的。因此,单个表面为一类具有相似功能的插件提供了UI。
如果没有反射的支持,如何才能在C ++中做到最好?
我认为挑战是将C ++模拟抽出到C#的Type.IsAssignableFrom(Type)
,其中两个类型都是动态归属的。我正朝着dynamic_cast
和typeid
的方向前进,但是到目前为止,我还没有牢牢掌握如何去做。任何有关架构,模式或实现细节的提示都将不胜感激。
编辑1:我认为我已经解决了这里的主要问题(请参阅下面我自己的答案,其中包括[非常粗略的C ++实现]),但可能有更好的模式。随意撕开它。
编辑2:此处的后续问题中提供了一个改进的模型:“Poor Man's Reflection” (AKA bottom-up reflection) in C++。
答案 0 :(得分:1)
一个选项是在Plugin基类中使用一个函数:
virtual std::string id() const { return "Plugin"; }
哪些派生类可以覆盖,例如Grandson知道它的基类是Mother,所以可能代码:
override std::string id() const { return "Grandson " + Mother::id(); }
这样,当你在插件上调用id()
时,就会得到像“孙子妈妈插件”这样的东西。你可以把它std::stringstream ss(id);
,我们while (ss >> plugin_id)
扔掉,然后用plugin_id
作为表面容器中的钥匙......
我认为您不会灵活地使用typeid
或dynamic_cast
,因为typeid
不会告诉您基类(*)和{{ 1}}必须知道它在编译时要测试的类型。我假设您希望能够使用新插件,并更改一些id->曲面映射的文件或其他配置,而无需编辑代码....
(*)如果您准备为非标准且符合可移植性要求,则C++: using typeinfo to test class inheritance会记录在运行时获取基类dynamic_cast
的方法。
答案 1 :(得分:1)
在考虑之后,我意识到以下内容应该有效:
由于表面类型已经被标记为各自的“目标插件类型”,我意识到他们可能也有硬编码在其中(因为它不会改变一旦编译了一个表面插件。这基本上就是在C#中完成类型标记的方式 - 尽管它是使用类属性完成的.Duh)。
这意味着只要'可分配性'检查在每个表面类内完成(内部知道它所针对的类型)而不是全局函数(需要同时告诉它们)检查的源和目标类型,因此问题),它成为具有已知目标类型的常规强制转换操作(即表面的目标插件类型)。
我猜相关函数可能是模板化的,因此模板类型T代表它们的“目标插件类型”以及演员的已知目标类型。
它可以有这样的功能:
bool TargetTypeIsAssignableFrom(Plugin* plugin)
并且该函数将返回常规dynamic_cast
的结果(即,这样的演员是否成功):
dynamic_cast<T>(plugin)
所以当插件 - &gt;执行表面匹配,而不是在全局函数中检查每个表面的TargetType的pluginType(需要为两种类型提供),每个表面只是询问它们与相关插件的兼容性,它通过尝试对其已知的 T进行dynamic_cast来测试 - 并且返回true
的那些被认为是候选者。
最后,相互检查候选人的TargetTypes以确定最佳匹配。
这是一个快速而肮脏但功能性的过程说明(原谅我的C ++):
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Plugin {
protected:
string _name;
public:
Plugin() { _name = "Plugin"; };
virtual string Name() { return _name; }
};
class Plugin_A : public Plugin {
public:
Plugin_A() { _name = "Plugin_A"; };
};
class Plugin_B : public Plugin_A {
public:
Plugin_B() { _name = "Plugin_B"; };
};
class Plugin_C : public Plugin {
public:
Plugin_C() { _name = "Plugin_C"; };
};
Plugin * Global_Plugin = new Plugin;
Plugin_A * Global_Plugin_A = new Plugin_A;
Plugin_B * Global_Plugin_B = new Plugin_B;
class Surface {
protected:
string _name;
public:
Surface() { _name = "Surface"; };
string Name() { return _name; }
virtual Plugin* TargetType() { return Global_Plugin; }
virtual bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin*>(plugin) != nullptr; } // TargetType: Plugin
};
class Surface_A : public Surface {
public:
Surface_A() { _name = "Surface_A"; };
Plugin* TargetType() { return Global_Plugin_A; }
bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin_A*>(plugin) != nullptr; } // TargetType: Plugin_A
};
class Surface_B : public Surface_A {
public:
Surface_B() { _name = "Surface_B"; };
Plugin* TargetType() { return Global_Plugin_B; }
bool CanHost(Plugin* plugin) { return dynamic_cast<Plugin_B*>(plugin) != nullptr; } // TargetType: Plugin_B
};
vector<Surface*> surfaces;
Surface * GetSurface(Plugin* plugin) {
vector<Surface*> candidates;
cout << "Candidate surfaces for " << plugin->Name() << ":" << endl;
for (auto i = begin(surfaces); i != end(surfaces); ++i) {
if ((*i)->CanHost(plugin)) {
cout << "\t" << (*i)->Name() << endl;
candidates.push_back(*i);
}
}
int bestFit = 0, fit;
Surface * candidate = nullptr;
for (auto i = begin(candidates); i != end(candidates); ++i) {
fit = 0;
for (auto j = begin(candidates); j != end(candidates); ++j) {
if (j == i || !(*j)->CanHost((*i)->TargetType())) continue;
++fit;
}
if (candidate != nullptr && fit <= bestFit) continue;
bestFit = fit;
candidate = *i;
}
cout << "Best fit for " << plugin->Name() << ":" << endl;
cout << "\t" << candidate->Name() << endl;
return candidate;
}
int main() {
Surface * s[3];
s[0] = new Surface;
s[1] = new Surface_A;
s[2] = new Surface_B;
for (int i = 0; i < 3; ++i) {
surfaces.push_back(s[i]);
}
Plugin * p[3];
p[0] = new Plugin_A;
p[1] = new Plugin_B;
p[2] = new Plugin_C;
for (int i = 0; i < 3; ++i) {
GetSurface(p[i]);
cout << endl;
}
for (int i = 0; i < 3; ++i) {
delete p[i];
delete s[i];
}
cin.get();
delete Global_Plugin;
delete Global_Plugin_A;
delete Global_Plugin_B;
return EXIT_SUCCESS;
}
答案 2 :(得分:0)
由于您来自一种具有反思并且在C ++中询问类似功能的语言,我建议您应该查看“模板元编程”和“基于策略的设计”,“类型特征”等等。因此,与反思相比,将这些决定推迟到编制时间。
在您的情况下,我建议您仔细查看类,并使用std::is_same
,std::is_base_of
提供的元编程技术在编译时做出这些决策,
std::is_convertible
,std::enable_if
和std::conditional
由于您似乎正在进行重大重构,因此Alexandrescu,Alexandrescu's book,Di Gennaro's book和{{3}几乎所有关于此主题的更好,更深入的资源这些技术。
我特别发现David Abrahams's book帖子在所有这些问题上都很有见地......
HTH,因为TMP非常有趣