目录
整体框架
模板方法介绍
关于本案例设计
c++ impl惯用法
c++impl惯用法好处
此案例impl惯用法的设计
关于序列化和反序列化
序列化和反序列化
本项目使用介绍
谷歌测试
谷歌测试环境
谷歌测试用例
完整源码地址
概述
本文介绍了从
- 设计模式之模板方法模式
- 协议消息设计之序列化和反序列化
- 谷歌单元测试
- c++ impl惯用法,用于接口设计,隐藏设计细节,只暴露接口
整体框架
模板方法介绍
模板方法模式是设计模式中比较常用的一种,此模式就是提供了一个很好的代码复用平台,意在将不变的行为放到基类,去除子类中的重复代码。当不变的和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现,模板方法模式把这些行为搬移到单一的地方,这样就可以帮助子类拜托重复不变的行为的纠缠。
由于类图太简单,就不单独画出,就一个基类和n个派生类,main函数中多态起对象(类似简单工厂模式),通过基类对象调用基类的固定方法,通过基类的固定方法调用虚函数实现多态的效果。
关于本案例设计
本案例重点实现IMessage基类,和衍生出来的各个消息子类,由于每个消息的数据结构不同,导致序列化反序列化的方法不同,但是头部数据的序列化反序列化方法是相同的,而且尽管方法不同,但是步骤都是一致的,因此套用模板方法模式。
下面是基类的实现方法serialize和deserialize实现了对外接口序列化和反序列化,内部再通过多态,调用到各个子类的serializeContent和deserializeContent这两个实际的接口。
整个项目结构如下,一个IMessage基类,实现了两个子类消息都继承于IMessage类,types.h中放置所有消息的结构体。
c++ impl惯用法
c++impl惯用法好处
《c++服务器开发精髓》——张远龙书中得知此方法,网上也有很多demo,此设计多用于接口类的编写,由于不想对外暴露太多的内部实现方法和成员变量,将内部方法和变量写入一个内部类中,对外只暴露一个此内部类的指针或者智能指针。
此案例impl惯用法的设计
此案例的这个思想主要体现在IMessage类中,设想我们是设计此类的第三方公司,此类给到客户是只写给出客户用得到的接口不想给客户知道我们使用那些内部方法和变量,可以像如下设计在message.h头文件中写入一个不可拷贝只能指针,指向IMessageImpl这个类,当然,使用之前先声明一下。
之后在message.cpp中定义这个IMessageImpl内部类,使用IMessage::IMessageImpl类似继承的方法表面是内部类,定义MsgHead私有变量,serializeHeader、deserializeHeader私有方法,分别用来存储消息的头部内容,和实现对消息的头部内容的序列化和反序列化。
在IMessage类构造时IMessage::IMessage(): m_pImpl(make_unique<IMessageImpl>())将暴露在外的m_pImpl只能指针实例化,在IMessage中能直接使用m_pImpl->msgHead变量或m_pImpl->deserializeHeader方法,隐藏了内部实现。
关于序列化和反序列化
序列化和反序列化
程序员在编写应用程序的时候往往需要将程序的某些数据存储在内存中,然后将其写入某个文件或是将它传输到网络中的另一台计算机上以实现通讯。这些过程将会涉及到程序数据转化成能被存储并传输的格式,因此被称为“序列化”(Serialization),而它的逆过程则可被称为“反序列化” (Deserialization)
简单来说,序列化就是将对象实例的状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它根据流重构对象。这两个过程结合起来,可以轻松地存储和传输数据。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。
- 序列化:将对象变成字节流的形式传出去。
- 反序列化:从字节流恢复成原来的对象。
成熟的案例:Google Protocol Buffers(protobuf)
本项目使用介绍
本案例将序列化和反序列化实现在types.h头文件中,分别实现了以下几种类型的序列化和反序列化:(给出普通类型和string类型的实现方法)
- 普通类型 <> 字节流
- string类型 <> 字节流
- vector类型 <> 字节流
- vector of strings <> 字节流 (多个string)
- vector of vectors <> 字节流 (多个数组)
谷歌测试
谷歌测试环境
接触过单元测试的同学应该都了解谷歌测试,这里就简单说一下怎么Qt集成谷歌测试的一种方法吧。首先需要先有谷歌测试的库文件和头文件(此处没有的同学可以上我的git里面取),当然也有另一种方法直接集成googletest的源码的我这里就不说了大家网上自己找下吧,我也没实践过。下面是谷歌测试的依赖目录:
我这边使用的是cmake,因此需要在CMakeLists.txt中加入以下库和头问价的引用,windows下包括目录的引用和库文件夹的引用,库文件的引用也可以在main函数中用 #pragma comment(lib, "../mypro2buf/gtest/lib/gtest.lib") 代替,此处我们还是使用target_link_libraries,第二种方法需要知道执行文件目录,或者写库的绝对路径,每个人不统一。
linux下先使用软件源下载google test的dev包,之后同样使用target_link_libraries引用gtest(linux软件安装之后会到默认添加过环境变量的目录则不需要指定库和头文件目录),注意linux下需要添加一个pthread库。
最后在main函数中 #include "gtest/gtest.h" 环境就搭建好了
谷歌测试用例
此处测试两个消息,序列化和反序列化之后的内容是否一致,main函数和测试用例试下如下
此处详解一个消息的过程,消息名称是MessageLoginRequest::IMessage,实现了登录消息,此消息包含的数据是两个string类型。一个是用户名一个是密码,通过MessageLoginRequest对象调用基类的serialize方法,将序列化之后的字节流buff存在msgBuf中,实际情况下,这个msgBuf会通过网络或者其他底层传输协议传输到对端,对端收到buff之后实现deserialize,当然,对端会通过头部信息获取此消息的种类。
此处本地模拟对端直接收到了msgBuf,直接创建loginRequest2来反序列化这个buff,反序列化之后会将解出来的用户名和密码存在loginRequest2中,此处使用谷歌测试对原始值和loginRequest2中的值对比测试是否相等,相等则表面序列化和反序列化成功。
EXPECT_EQ(data.username, loginRequest2.getLogin().username); EXPECT_EQ(data.password, loginRequest2.getLogin().password);
谷歌测试还有判断是否为真,和其他类型变量的测试方法类似EXPECT_EQ,大家可以在网上搜索做一些别的字段的测试。这样我们的测试就结束了。
测试结果:
最后,欢迎大家一起学习c/c++,一起探讨和改进,一起进步。
完整源码地址
mypro2buf · gaotao/mytest-projects - 码云 - 开源中国 (gitee.com)