我正在包装一个我没写过的库,以使其更加用户友好。有大量的函数是非常基本的,因此当真正需要的是结果的类型转换时,必须包装所有这些函数并不理想。
一个人为的例子:
假设该库有一个QueryService类,它有这个方法:
WeirdInt getId() const;
我想在我的界面中使用标准的int,但是我可以从WeirdInt中获取一个int没问题,因为我知道如何做到这一点。在这种情况下,我们可以说WeirdInt有:
int getValue() const;
这是一个非常简单的例子,通常类型转换更复杂,并不总是只是调用getValue()。
实际上有数百个函数调用返回类似这些类型的函数,并且会不断添加更多函数调用,所以我想尝试减轻自己不得不每次在库中执行时不断添加bajillion方法的负担将WeirdType转换为类型。
我想最终得到一个QueryServiceWrapper,它具有与QueryService相同的功能,但我转换了类型。我是否必须编写一个相同的名称方法来包装QueryService中的每个方法?还是有一些我不知道的魔法?还有一点,但与这个问题无关。
由于
答案 0 :(得分:4)
我认为第一种方法是尝试使用模板
getValue()
方法的包装类型提供标准实现类似的东西:
class WeirdInt
{
int v;
public:
WeirdInt(int v) : v(v) { }
int getValue() { return v; }
};
class ComplexInt
{
int v;
public:
ComplexInt(int v) : v(v) { }
int getValue() { return v; }
};
template<typename A, typename B>
A wrap(B type)
{
return type.getValue();
}
template<>
int wrap(ComplexInt type)
{
int v = type.getValue();
return v*2;
};
int x = wrap<int, WeirdInt>(WeirdInt(5));
int y = wrap<int, ComplexInt>(ComplexInt(10));
答案 1 :(得分:1)
如果QueryService
的包装器方法有一个简单的模式,你也可以考虑用一些perl或python脚本生成QueryServiceWrapper
,使用一些启发式方法。然后你需要最多定义一些输入参数。
甚至定义一些宏也有助于编写这个包装类。
答案 2 :(得分:0)
简而言之,如果您的目标是完全封装功能,那么WeirdInt
和QueryService
不会暴露给客户&#39;代码使得您不需要包含在客户端代码中声明它们的任何标题,然后我怀疑您采取的方法将能够从任何魔法中受益。
我之前完成此操作时,我的第一步是使用pimpl idiom,以便您的标头不包含以下实施细节:
<强> QueryServiceWrapper.h 强>
class QueryServiceWrapperImpl;
class QueryServiceWrapper
{
public:
QueryServiceWrapper();
virtual ~QueryServiceWrapper();
int getId();
private:
QueryServiceWrapperImpl impl_;
};
然后在定义中,您可以放置实现细节,安全地知道它不会泄漏到任何下游代码:
<强> QueryServiceWrapper.cpp 强>
struct QueryServiceWrapperImpl
{
public:
QueryService svc_;
};
// ...
int QueryServiceWrapper::getValue()
{
return impl_->svc_.getId().getValue();
}
在不知道需要采用哪些不同的方法进行转换的情况下,这里很难添加太多,但你当然可以使用模板函数来转换最流行的类型。
这方面的缺点是你必须自己实施一切。这可能是一把双刃剑,因为它可能只实现您真正需要的功能。在包装从未使用过的功能方面,通常没有任何意义。
我不知道一颗银弹&#39;这将实现函数 - 甚至是函数的空包装器。我通常通过组合shell脚本来创建我想要的空类或者使用标头的副本并使用sed或Perl的文本操作将原始类型更改为包装类的新类型
在这些情况下,使用公共继承来启用对基本函数的访问同时允许覆盖函数是很诱人的。但是,这不适用于您的情况,因为您想要更改返回类型(不足以进行重载)和(可能)您希望防止暴露原始Weird
类型。
前进的方法必须是使用聚合,尽管在这种情况下你无法轻易避免重新实现(某些)接口,除非你准备自动创建类(使用代码生成) )在某种程度上。
答案 3 :(得分:-1)
更复杂的方法是在原始QueryService
上引入所需数量的facade类,每个类对于一个特定查询或查询类型具有有限的一组函数。我不知道你的特定QueryService
是什么,所以这里有一个想象的例子:
假设原始类有很多 weired 方法使用奇怪的类型
struct OriginQueryService
{
WeirdType1 query_for_smth(...);
WeirdType1 smth_related(...);
WeirdType2 another_query(...);
void smth_related_to_another_query(...);
// and so on (a lot of other function-members)
};
然后你可以写一些像这样的门面课:
struct QueryFacade
{
OriginQueryService& m_instance;
QueryFacade(OriginQueryService* qs) : m_instance(*qs) {}
// Wrap original query_for_smth(), possible w/ changed type of
// parameters (if you'd like to convert 'em from C++ native types to
// some WeirdTypeX)...
DesiredType1 query_for_smth(...);
// more wrappers related to this particular query/task
DesiredType1 smth_related(...);
};
struct AnotherQueryFacade
{
OriginQueryService& m_instance;
AnotherQueryFacade(OriginQueryService* qs) : m_instance(*qs) {}
DesiredType2 another_query(...);
void smth_related_to_another_query(...);
};
每个方法委托调用m_instance
并以您想要的方式修改输入/输出类型转换。类型转换可以在@Jack描述的帖子中实现。或者,您可以在命名空间中提供一组免费函数(如Desired fromWeird(const Weired&);
和Weired toWeired(const Desired&);
),这些函数将由ADL选择,因此如果出现某种新类型,则需要执行所有操作是为这两个函数提供重载......这种方法在boost::serialization
中工作得很好。
此外,您可以为该功能提供通用(模板)版本,例如,如果您的getValue()
类型的许多类型具有此类成员,则会调用Weired
。