Navigator Pattern: 导航者模式


模式名称

  • Navigator/导航者, 一种通信软件报文处理模式

意图

  • 封装报文数据复杂的内部结构, 通过提供有业务含义的寻址操作来避免危险的指针运算, 以减少重复和出错的可能, 并提供更清晰的业务意义

动机

在网络通信软件的开发中, 为了传输效率或完整性的考虑, 通常在应用层协议的定义中, 一次可以发送多个单位的净荷数据, 其具体数量可用报文头中的某个字段来描述. 另外一些时候报文体的长度是不定的, 通常也用报文头中的某个字段来表示实际的报文多长.

而此类软件通常以C语言开发完成. 经典的实现方案是为报文定义如下的数据结构, 并以指针运算来寻址特定的数据. 而当数据结构有嵌套时, 其指针运算将变的异常复杂和易错:

typedef struct SecondLevelPayload {
    int field_1,
    char field_2,
    float field_3
} SecondLevelPayload;

typedef struct TopLevelPayload {
    int some_top_level_field,
    int second_level_payload_count,
    SecondLevelPayload* payload //payload之间有嵌套
} TopLevelPayload;

typedef struct Message {
    int count,
    TopLevelPayload* payload
} Message;

void print_datagram(Message* message) {
    TopLevelPayload* current = message->payload;
    for(int i = 0; i < message->count; i++) {
        PrintTopLevelPayload(current++);
        //or PrintTopLevelPayload(message->payload[i]);
    }
}    

上面最后的函数试图循环打印所有TopLevelPayload, 但寻址方式却是错的, 因为第二个TopLevelPayload的地址并不是第一个的地址加上其自身结构体的长度. 这里的症结在于C语言缺乏描述动态集合的设施, 只有指向首地址的指针, 而指针的大小和其指向的数据的大小是不同的. 又因为数据流是连续的, 因此据此定义的结构体的大小和实际数据的大小是不一致的. 初步的改正如下:

void print_datagram(Message* message) {
    TopLevelPayload* current = message->payload;
    for(int i = 0; i < message->count; i++) {
        PrintTopLevelPayload(current);
        current = (TopLevelPayload*)((char*)current + sizeof(TopLevelPayload) + current->second_level_payload_count * sizeof(SecondLevelPayload));
    }
}    

这样代码就变得晦涩, 看不出意图. 而指针运算容易出错, 且当其它代码需要在报文内部寻址的时候需要重复一遍代码来再算一次, 当报文协议/结构体定义变化的时候, 需要检查所有现存的指针运算看是否还合适. 我们需要更好的设计.

方案

这里的问题是寻址. 而现实生活中, 当我们需要去某个地址的时候, 我们借助导航. 它可以是一部仪器, 也可以是熟悉当地环境的路人. 但接口是一致的: 我们只需要告诉他我们要去哪, 不需要提前了解地形. 在C语言中, 它可以是围绕着报文首地址指针提供的一组有业务含义的接口函数:

TopLevelPayload* goto_nth_toplevel_payload(Message* message, int nth_toplevel_payload) {
    TopLevelPayload* addr = message->payload;
    for(int i = 0; i < nth_toplevel_payload; i++) {
        addr = (TopLevelPayload*)((char*)addr + sizeof_toplevel_payload(addr));
    }
    return addr;
}

SecondLevelPayload* goto_nth_secondlevel_payload(TopLevelPayload* top, int nth_secondlevel_payload) {
    return top->payload + nth_secondlevel_payload;
}

static int sizeof_toplevel_payload(TopLevelPayload* payload) {
    return sizeof(TopLevelPayload) + payload->second_level_payload_count * sizeof(SecondLevelPayload);
}

这样, 通过报文首地址和goto_nth_toplevel_payload(), goto_nth_secondlevel_payload()两个函数, 客户代码就可以在报文体中任意巡航, 而无需理会其内部表示, 无需涉及易错和晦涩的指针运算. 当报文协议变化时, 我们也只需要修改navigator, 无需修改客户代码.

相关模式

Page Object模式描述了在web应用测试领域针对易变的web页面进行封装的方法, 其中也涉及对页面不同元素的导航. 其解决的主要问题是减少相对频繁的页面变化对测试代码的稳定性造成的冲击, 并更清晰的描述测试意图.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值