2

我正在包装一个我没有编写的库,以使其更加用户友好。有大量非常基本的函数,因此当真正需要的只是结果的类型转换时,必须包装所有这些函数并不理想。

一个人为的例子:

假设库有一个类 QueryService,它有这个方法:

WeirdInt getId() const;

我想在我的界面中有一个标准的 int 但是,我可以从 WeirdInt 中得到一个 int 没问题,因为我知道如何做到这一点。在这种情况下,可以说 WeirdInt 具有:

int getValue() const;

这是一个非常简单的例子,通常类型转换更复杂,并不总是只调用 getValue()。

从字面上看,有数百个函数调用会返回类似这些类型,并且一直在添加更多类型,所以我想尝试减轻自己的负担,每次库所做的只是为了将 WeirdType 变成类型。

我想最终得到一个 QueryServiceWrapper,它具有与 QueryService 相同的功能,但我已经转换了类型。我是否必须编写一个名称相同的方法来包装 QueryService 中的每个方法?还是我缺少一些魔法?还有更多内容,但与这个问题无关。

谢谢

4

4 回答 4

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));
于 2013-01-27T00:45:34.780 回答
1

If the wrapper methods for QueryService have a simple pattern, you could also think of generating QueryServiceWrapper with some perl or python script, using some heuristics. Then you need to define some input parameters at most.

Even defining some macros would help in writing this wrapper class.

于 2013-01-27T01:13:43.057 回答
0

简而言之,如果您的目标是完全封装功能,WeirdInt并且QueryService不暴露给“客户端”代码,这样您就不需要在客户端代码中包含任何声明它们的标头,那么我怀疑您采用的方法会能够从任何魔法中受益。

当我之前完成此操作时,我的第一步是使用pimpl 习惯用法,以便您的标头不包含以下实现细节:

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();
}

在不知道需要使用哪些不同的方法来进行转换的情况下,很难在这里添加太多,但是您当然可以使用模板函数来进行最流行的类型的转换。

这里的缺点是您必须自己实现所有内容。这可能是一把双刃剑,因为这样就可以只实现您真正需要的功能。包装从未使用过的功能通常没有意义。

我不知道将实现这些功能的“灵丹妙药” - 甚至是功能上的空包装器。我通常通过组合 shell 脚本来创建我想要的空类或获取标题的副本并使用 sed 或 Perl 使用文本操作将原始类型更改为包装类的新类型来完成此操作。

在这些情况下,很容易使用公共继承来启用对基本函数的访问,同时允许函数被覆盖。但是,这不适用于您的情况,因为您想要更改返回类型(对于重载来说还不够)并且(大概)您想要防止暴露原始Weird类型。

这里的前进方式必须是使用聚合,尽管在这种情况下,除非您准备自动创建类(使用代码生成),否则您无法轻松避免重新实现(某些)接口程度。

于 2013-01-27T01:19:10.313 回答
-1

more complex approach is to introduce a required number of facade classes over original QueryService, each of which has a limited set of functions for one particular query or query-type. I don't know that your particular QueryService do, so here is an imaginary example:

  • suppose the original class have a lot of weired methods worked with strange types

    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)
    };
    
  • then you may write some facade classes like this:

    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(...);
    };
    

    every method delegate call to m_instance and decorated w/ input/output types conversion in a way you want it. Types conversion can be implemented as @Jack describe in his post. Or you can provide a set of free functions in your namespace (like Desired fromWeird(const Weired&); and Weired toWeired(const Desired&);) which would be choosen by ADL, so if some new type arise, all that you have to do is to provide overloads for this 2 functions... such approach work quite well in boost::serialization.

    Also you may provide a generic (template) version for that functions, which would call getValue() for example, in case if lot of your Weired types has such member.

于 2013-01-27T01:09:01.003 回答