一、接口数据对象
无论是通过函数或接口传递参数时,针对于具体的对象目标(而非标准的XML,JSON协议对象等),往往是各种情况满天飞,大多数的开发者对此并没有多重视。虽然在前面的接口分析和相关的参数处理里有过介绍,不少开发者也有着相当丰富的开发经验。但从整体上看,对其的重视程度并不如其它的技术点。
毕竟对于接口来说,有很多的方便快捷的处理机制,不需要深入的理解其内部的情况,就可以轻松的达到目的。这也是接口数据对象往往被忽略的一个重要因素。而且对于大多数的开发场景下,确实也不需要对这类数据对象进行严格的处理。
二、分析
不过在一些复杂的应用场景或者说特定的场景下,对接口数据对象的处理,往往有一些实际的要求。如效率、速度或性能,或者说设计的方便、扩展性和可维护性等。这都对如何设计这个传输的数据类对象提出了更高的要求。
还是举前面的硬件规约的通信类设计,使用继承又简单又方便,而且非常容易理解和扩展。同时,硬件的变化又不会象软件需求一样,一年可能变几次,而更大概率是几年变一次。因此,使用继承就是一种不错的机制。同样,如果在一些通信数据传输的接口中,使用多态很可能会产生运行时的开销,这对于性能要求严苛的环境下就不能使用。
因此,使用哪种对象的处理机制,需要开发者根据实际情况以及特定的场景要求具体情况具体对待,不能一概而论。当然,在多种情况可选的情况下,就看开发者个人的偏好了。
三、常见形态
对于使用传统的数据对象作为接口数据传递的形式,一般有以下几种情况:
- 使用继承
使用继承的优势是代码重用,便于后期的扩展。但缺点就是打破了封装,性能开销相对较大。或者说引入了复杂度。使用继承特别是引入虚函数,更多的推荐在业务逻辑数据的传递上 - 使用容器
这也是很常见的一种情况,通过容器进行数据的自动扩容和管理,方便安全。缺点就是可能会浪费资源和性能的损失,对于内存小或性能有严格要求的场景下不太适合 - 使用共用体或Variant
它们两个可以划成一类,其实也可以看作一种类型擦除的方式。不过使用共用体的优势在于,其实际占用的空间可控,不会盲目的扩大内存空间。而且可以很灵活的对数据类型进行自动的转换。这对于一些场景下非常有优势。但缺点也相对凸出,就是需要开发者自行处理成员,增加了风险。
使用Variant最大的问题在于它需要C++17以上的版本。而且内存占用相对要大一些,不过其不需要开发者手动管理成员,类型安全可控 - 使用类型擦除
在前面专门分析过类型擦除,它的优势在于可以动态的处理多种数据类型对象,非常灵活。同时,这样做的结果就是对数据类型进行了解耦。缺点则非常明显,它会明显的增加性能的开销(额外的检查机制),同时擦除的类型往往意味着类型转换的安全风险增加。最简单的实践就是使用std::any进行处理,其带来的异常也会导致程序的崩溃 - 使用函数或类函数对象(如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);
}
代码很简单,如果对类型擦除感兴趣,可以查看前面的相关的文章。
五、总结
思想是指导开发的利器,但它又必须落到实际的代码中来。再好的抽象,如果不能结合到实际的工程,那么就是无源之水。从理论到实践是每个开发者必须跨过的一个门槛。只有真正的能把抽象实践在项目中,才代表着真正的掌握了理论到实践的流程。从而为下一步的自发的从实践到理论打下坚实的基础。

1289

被折叠的 条评论
为什么被折叠?



