C++编程实践—接口数据对象的分析

一、接口数据对象

无论是通过函数或接口传递参数时,针对于具体的对象目标(而非标准的XML,JSON协议对象等),往往是各种情况满天飞,大多数的开发者对此并没有多重视。虽然在前面的接口分析和相关的参数处理里有过介绍,不少开发者也有着相当丰富的开发经验。但从整体上看,对其的重视程度并不如其它的技术点。
毕竟对于接口来说,有很多的方便快捷的处理机制,不需要深入的理解其内部的情况,就可以轻松的达到目的。这也是接口数据对象往往被忽略的一个重要因素。而且对于大多数的开发场景下,确实也不需要对这类数据对象进行严格的处理。

二、分析

不过在一些复杂的应用场景或者说特定的场景下,对接口数据对象的处理,往往有一些实际的要求。如效率、速度或性能,或者说设计的方便、扩展性和可维护性等。这都对如何设计这个传输的数据类对象提出了更高的要求。
还是举前面的硬件规约的通信类设计,使用继承又简单又方便,而且非常容易理解和扩展。同时,硬件的变化又不会象软件需求一样,一年可能变几次,而更大概率是几年变一次。因此,使用继承就是一种不错的机制。同样,如果在一些通信数据传输的接口中,使用多态很可能会产生运行时的开销,这对于性能要求严苛的环境下就不能使用。
因此,使用哪种对象的处理机制,需要开发者根据实际情况以及特定的场景要求具体情况具体对待,不能一概而论。当然,在多种情况可选的情况下,就看开发者个人的偏好了。

三、常见形态

对于使用传统的数据对象作为接口数据传递的形式,一般有以下几种情况:

  1. 使用继承
    使用继承的优势是代码重用,便于后期的扩展。但缺点就是打破了封装,性能开销相对较大。或者说引入了复杂度。使用继承特别是引入虚函数,更多的推荐在业务逻辑数据的传递上
  2. 使用容器
    这也是很常见的一种情况,通过容器进行数据的自动扩容和管理,方便安全。缺点就是可能会浪费资源和性能的损失,对于内存小或性能有严格要求的场景下不太适合
  3. 使用共用体或Variant
    它们两个可以划成一类,其实也可以看作一种类型擦除的方式。不过使用共用体的优势在于,其实际占用的空间可控,不会盲目的扩大内存空间。而且可以很灵活的对数据类型进行自动的转换。这对于一些场景下非常有优势。但缺点也相对凸出,就是需要开发者自行处理成员,增加了风险。
    使用Variant最大的问题在于它需要C++17以上的版本。而且内存占用相对要大一些,不过其不需要开发者手动管理成员,类型安全可控
  4. 使用类型擦除
    在前面专门分析过类型擦除,它的优势在于可以动态的处理多种数据类型对象,非常灵活。同时,这样做的结果就是对数据类型进行了解耦。缺点则非常明显,它会明显的增加性能的开销(额外的检查机制),同时擦除的类型往往意味着类型转换的安全风险增加。最简单的实践就是使用std::any进行处理,其带来的异常也会导致程序的崩溃
  5. 使用函数或类函数对象(如Lambda)
    函数对象多用于对数据处理的异步场景。优势在于效率高、类型安全而且非常灵活。缺点是引入了复杂度,需要开发者全程关注

四、具体例程

下面看一个简单全例程:

#include <functional>
#include <iostream>
#include <string>

struct Data {
    int id;
    std::string name;
    double score;
};

// 1. 函数对象
class DataPrinter {
public:
    void operator()(const Data& data) const {
        std::cout << "[函数对象] "
                  << "id = " << data.id
                  << ", name = " << data.name
                  << ", score = " << data.score << '\n';
    }
};

void sendByFunctor(const Data& data, DataPrinter printer) {
    printer(data);
}

// 2. Lambda表达式
template <typename Func>
void sendByLambda(const Data& data, Func func) {
    func(data);
}

// 3. 类型擦除:std::function隐藏具体可调用对象的类型
using DataHandler = std::function<void(const Data&)>;

void sendByTypeErasure(const Data& data, DataHandler handler) {
    handler(data);
}

int main() {
    Data data{1001, "Tom", 96.7};

    DataPrinter printer;
    sendByFunctor(data, printer);

    sendByLambda(data, [](const Data& data) {
        std::cout << "Lambda  "
                  << "id = " << data.id
                  << ", name = " << data.name
                  << ", score = " << data.score << std::endl;
    });

    DataHandler handler = [](const Data& data) {
        std::cout << "类型擦除 "
                  << "id = " << data.id
                  << ", name = " << data.name
                  << ", score = " << data.score << std::endl;
    };
    sendByTypeErasure(data, handler);
}

代码很简单,如果对类型擦除感兴趣,可以查看前面的相关的文章。

五、总结

思想是指导开发的利器,但它又必须落到实际的代码中来。再好的抽象,如果不能结合到实际的工程,那么就是无源之水。从理论到实践是每个开发者必须跨过的一个门槛。只有真正的能把抽象实践在项目中,才代表着真正的掌握了理论到实践的流程。从而为下一步的自发的从实践到理论打下坚实的基础。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值