学以致用C++设计模式 之 “适配器模式”

时间:2022-07-23
本文章向大家介绍学以致用C++设计模式 之 “适配器模式”,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这个模式啊,怎么说呢,很多书里都说它是个补救模式,在设计的时候不要去考虑。

出国旅游必备之“电源适配器”

见过电源适配器吗?电脑手机充电器就是“电源适配器”,但是我国的标准电压是220V,出了国可就不好乱插,别的国家的电压和我国不同。这时候就需要一个便携的电源变压器将国外电压转换成220V,这便是“适配器模式”的精髓:将你不能用的东西,在不破坏原物的前提下,通过中转变成你能用的东西。

生火做饭

上面呢,是对于“适配器模式”的标准理解,但是我有自己的理解。 我们的祖先呢,一开始是吃生的东西,后来有了火呢,他们就吃熟的东西了,那时候没那么多讲究,就是熟的好吃,所以吃熟的。看现在的人,也是可以吃生肉的,只是通过火的作用,会使得食物更加健康。 这个火,在我看来就是属于“适配器”,将本来可吃可不吃的生肉,变成了熟肉。我觉得吧,只要能使得项目某些小方面更加的便利,就可以采用适当的适配转换,不一定非要到了“别无他法”的时候才采用“适配器”。

舍它其谁的场景

来看个解压包模块的部分代码:

//Packetbase.h
#define MAX_PACKET_LENTH 1024
//设置包最大长度为1024,
// ! ! ! 暂时不考虑会太小的情况 ! ! !

typedef struct packet_header_st
{
    int fd;//用于前后端通信即目标客户端fd(服务器用到)
    int funcId; // 功能号
        //登录包0x01,注册包0x02,找回密码0x03,修改密码0x04

        //客户端获取文件列表0x11,上传文件0x12,下载文件0x13,共享文件0x14,
        //新增目录0x15,删除目录0x16,删除文件0x17,文件移动0x18,目录移动0x19,文件重命名0x20,目录重命名0x22
        //心跳0x21
        //边缘服务器向中控服务器认证 0x30
   
    int optid; // 操作码:请求0x00 和 应答0x01

    int usrlenth;// 包体的长度
    int packet_seq; //包序号
    int packet_sum; //包总数

    char to_fd[6];      //目标服务器名称
    char dstAddr[6]; //预留

    int syn; // 判断包头是否正确 0x04
}packet_header_t;

/************接入层数据包尾************/

typedef struct packet_tali_st//包尾,用来验证数据包的完整性
{
    int pack_tail;//设置为0x05
}packet_tali_t;

/************数据包报文整体************/

typedef struct  packet_all_st
{
    packet_header_t Head;
    char* body;
    packet_tali_st tail;
}packet_all_st;

上面的部分,是属于包头、包体、包尾的,属于解压包模块内聚部分,不对外开放。 接下来是实际拓展包:

//客户端登录请求包
typedef struct login
{
    int id;
    int pwd;       //密码
}Login_t;

//登录应答包
typedef struct res_login_st
{
    int login_ret;  //登录结果: 1-登录成功,0-登录失败
    int dir_id;  //初始文件列表id
    char reson[20];    //如果登录失败,返回失败原因,如果登录成功,返回初始目录名
}res_login_t;

//客户端注册请求包
typedef struct Register
{
    int id; //账号
    char tel[12];	//11位手机号
    int pwd;       //密码
}Register_t;

//注册应答包
typedef struct res_register_st
{
    int register_ret;	//失败返回0,成功返回1
    char reson[20];
}res_register_t;

那么,要如何将不同的结构体塞进固定数据类型的包体 char* body; 呢? 使用适配器:

bool PacketCommand1::replyLogin(int state, int dir_id, char* reson, int fd) {
    int sz = sizeof(res_login_t);
    this->setBodySize(sz);
    this->Body = new char[sz];

    Head.funcId = 0x01;
    Head.optid = 0x01;
    Head.fd = fd;
    Head.usrlenth = sz;   
    Head.syn = 0x04;
    strcpy(Head.to_fd, "Front");
    
    res_login_t* body = (res_login_t*)Body;		//适配器
    body->login_ret = state;
    body->dir_id = dir_id;
    strcpy(body->reson, reson);

    Tail.pack_tail = 0x05;

    return this->pack();
}

这里的适配器,体现在这句:res_login_t* body = (res_login_t*)Body; 以及后面的部分。

解压包模块中的包,动辄几十上百种,且包大小都不一样,如果每种包都要有一个专属包体的话,那成何体统?所以包体初始化只为char*,具体情况具体定。

可用可不用的场景

我呢,弄了个中控服务器,和几个边缘服务器。 中控服务器的功能呢,转接各边缘服务器的数据通信,开放接口:accept、read、send 边缘服务器各司其职,比方说epoll吧,开放接口:accept(面向客户端)、read(面向客户端)、send(面向客户端)。

所以呢,这两个服务器之间并不能直接通信。其实也可以直接在原有功能上加一个connect的函数,然后配置信息使这条连接面向中控,在在其中读写信息。但是,我的边缘服务器都写好了,硬塞这个功能进去实在是烦,而且每个边缘服务器都有向中控服务器通信的需求,于是我干脆弄了个转接类,专门处理边缘到中介之间的转接:

class EtoC	//Edge to Command
{
private:
	int connect_fd;
public:
	EtoC();
	void Start(const char* commandIP);	//建立连接
	void send_name(char* name);			//发送认证包
	int Read_date(char* date);			//读取数据
	int get_fd();						//获取通信套接字
	int Write_date(char* date,int len);	//写入数据
};

功能很简单,但是足以满足它们之间连通的需求了。

这么多设计模式嘛,还是要活用,死守定律就没啥意思了。