有了上面所说的 extractor framework 之后,我们会碰到几个有意思的问题:
- 如果有几个同类型的 extractor,我想把他们都执行了怎么办
- 如果有一个 A->B 的 extractor,后面又有一个 B->C 的 extractor,我们是不是可以直接获得一个 A->C 的 extractor?
- 如果有一个 A->C 的 extractor,B 是 A 的子类,我们能否将 A->C 的 extractor 当做 B->C 的 extractor 来用?这个问题简化的版本是如果一个人有 DEFINE_EXTRACTOR(Foo, A, B),另一个人写的是 DEFINE_EXTRACTOR(Bar, A, B),根据前面的设计这两个类虽然有公共的祖先,但是其 reflection lib 提供的 factory 却是两个不同的版本,因此两个 extractor 不能共用
可以看到的是,这些东西跟具体哪个 extractor<In, Out> 没有关系,它们处理的是“结构”,我们能否为之前的 framework 添加一些小 utility 呢?这其实跟 functional programming 里面一些思维比较类似了。好在我们发现 ExtractorConfig 本身是 type agnostic 的,这为我们创造了一种可能。
为了解决第一个问题,我们可以引入如下的 config
message CompositeConfig { repeated ExtractorConfig extractor = 1; extend ExtractorConfig { optional CompositeConfig config = 18; } }
我们实现这个功能巨简单
template <typename Child> class composite { public: DEFINE_EXTRACTOR_CTOR(composite, CompositeConfig::config) composite(const CompositeConfig& config) { for (const auto& ec : config.extractor()) { extractors.emplace_back(Child::create(ec)); } } bool extract(const typename Child::InType& in, typename Child::OutType& out) const override { flag = false; for (auto& extractor : extractors) flag |= extractor->extract(in, out); return flag; } private: std::vector<std::unique_ptr<Child> > extractors; };
通过一个简单的宏,我们可以在 DEFINE_EXTRACTOR 的时候将 Composite 直接注册,这样这个功能就变成所有 extractor 都有的一个结构了。第二个问题我们可以在 config 里面放两个 ExtractorConfig,但是很显然这个只能作为一个给定第三个类(中间)之后的一个 wrapper,为此我们可以在实现类似的 Chain<Child, C1, C2> 之后,要求使用一个 macro 来注册这个 wrapper。最后一个类似。为了保证 type safety,我们可以在这些实现里面直接 static_assert 对应的类是否复合一些显而易见的约束条件。
一个比较奇怪的要求是为某些特定的类提供“counter”,比如某些 extraction 可能需要为某些细节提供更加细粒度的统计信息,而不仅仅是这个 extraction 成功与否(return value),那么如何改动现有的 API?另一个问题是如果 extraction 还有副产品,又怎么能把副产品输出?很显然这些都是实现类的问题,但是在我们提供的 API 上却不能很容易的解决。
如果不考虑问题二和三导致的结构,我们可以为 extractor<In, Out> 提供一个如下的 interface:
template <typename In, typename Out, typename Child> void extractor<In, Out, Child>::apply(const function<void (Child&)> func) { func(*this); }
对于 composite 来说我们只需要简单的把这个 func 应用到每个子 extractor 即可。这样我们可以
extractor.apply([](XYZExtractor& extractor) { SomeInterface* some = dynamic_cast<SomeInterface*>(&extractor); if (some) { some->api_of_some(); } });
但是比如如果有 chaining 的行为,子 extractor 的类型不能匹配 func 的要求,无法自动的传递下去。有什么更好地策略能够让我们能够为这些需求打开合理的天窗呢?我们继续研究研究…
——————
And Timna was concubine to Eliphaz Esau’s son; and she bare to Eliphaz Amalek:these were the sons of Adah Esau’s wife