标签 C 下的文章

Adapter模式的C实现

Adapter(适配器)模式是《Design Pattern》一书中结构类模式集中的第一个模式,也是一个真正被我的同事在产品代码中应用的模式。

Adapter模式也是一个相对容易理解的模式,多数书籍和网络资料在描述这个模式时都使用了一个与电源适配器有关的例子,说不定Adapter模式还真的是源于对电源适配器的再思考和挖掘呢。
 
我们在重构遗留代码时引入了Adapter模式。遗留系统中存在的问题大致是这样的:按照规范,最初的系统只需要支持一种通信协议,这里把这个协议暂叫做proto_a。后来随着规范的丰富,客户又先后引入了两种协议proto_b和proto_c。前辈们在处理这些需求时偷了一个懒儿,选择了直接copy两份一模一样的业务逻辑代码以应对两个新协议,这样在遗留系统中就形成了一份业务逻辑,三份相同的代码的局面。可想而知,这样的代码结构是多么的不易于后续维护啊。一旦业务逻辑发生一处变化,我们就需要修改三个位置。代码味道那是相当的浓烈。在遗留代码的重构过程中,我们决定尝试用Adapter模式解决这个问题。
 
我们的目标是:一套业务逻辑,一套业务代码,支持三种不同的协议。还好这三种协议是基于同源设计的(从同一种协议演化而来),也就是说具备设计统一操作接口的基础,这似乎天生就适合Adapter^_^。
 
我们首先定义多种协议的统一操作接口:
 
/* xx_proto_interface.h */
struct xx_proto_i {
    int (*xx_proto_login)(void *trans);
    int (*xx_proto_logoff)(void *trans);
    int (*xx_proto_req)(void *trans, void *req);
    int (*xx_proto_resp)(void *trans, int *status );
    int (*xx_proto_heartbeat)(void *trans);
};
 
显然Adapter的引入就是因为proto_a、proto_b和proto_c原生的接口与统一接口有差别,无法直接使用。我们以proto_a为例,增加一个proto_a的Adapter层:
 
/* xx_proto_a_adapter.h */
#include "xx_proto_interface.h"
#include "proto_a.h"
 
struct xx_proto_i* get_proto_a_adapter_impl();
 
 
/* xx_proto_a_adapter.c */
#include "xx_proto_a_adapter.h"
 
static int xx_proto_a_login_adapter(void *trans);
static int xx_proto_a_logoff_adapter(void *trans);
static int xx_proto_a_req_adapter(void *trans, void *req);
static int xx_proto_a_resp_adapter(void *trans, int *status );
static int xx_proto_a_heartbeat_adapter(void *trans);
 
struct xx_proto_a_adapter_t {
    struct xx_proto_i impl;
};
 
struct xx_proto_i* get_proto_a_adapter_impl() {
 
    struct xx_proto_a_adapter_t *p = malloc(sizeof(*p));
    if (p == NULL) return NULL;
 
    p->impl.xx_proto_login = xx_proto_a_login_adapter;
    p->impl.xx_proto_logoff = xx_proto_a_logoff_adapter;
    p->impl.xx_proto_req = xx_proto_a_req_adapter;
    p->impl.xx_proto_resp = xx_proto_a_resp_adapter;
    p->impl.xx_proto_heartbeat = xx_proto_a_heartbeat_adapter;
 
    return (struct xx_proto_i*)p;
}
 
static int xx_proto_a_login_adapter(void *trans) {
    printf ("proto_a_login\n");
    /* 这里使用proto_a的原生接口实现login */
    return 0;
}
… …这里省略若干个实现
 
int xx_proto_a_heartbeat_adapter(void *trans) {
    printf ("proto_a_receive_a_heartbeat\n");
    /* 这里使用proto_a的原生接口实现heartbeat */
    return 0;
}
 
实际上我们用proto_a的adapter包装(wrap)了proto_a的原生接口,也就是说proto_a的原生协议实现对客户是不可见的。在《设计模式》书中,Adapter模式的别名也叫Wrapper。
 
客户端是这么来使用协议的,客户端只需要使用统一的xx_proto_i中的接口即可:
 
#include "xx_proto.h"
#include "xx_proto_a_adapter.h"
#include "xx_proto_b_adapter.h"
#include "xx_proto_c_adapter.h"
 
int main() {
    /* 根据需要我们灵活选择使用proto_a、proto_b或proto_c的adapter实现 */
    struct  xx_proto_i *p = get_proto_a_adapter_impl();
    struct proto_a_trans;
    /* some initializations */
    … …
 
    p->xx_proto_login(&trans);
    p->xx_proto_req(&trans, …);
 
    … …
}
 
任何事情都是有代价的,从上面可以看出Adapter模式的引用也使得代码变得庞大,很多proto的Adapter接口的实现可能都会是类似的和浅包装的。但与之前的问题相比,这些代价显然要小很多,并且用宏可以做适当改善。
 
题外话:适度抽象可以使得代码更加清晰,易于改变和维护。但物极必反,过度抽象反倒会让代码的可读性和易维护性下降,特别是在代码中使用了模式等手法时,千万不能沉迷,因为大家在理解你的多层过度抽象上所要付出的代价可能更大。

使用Jenkins实现多平台并行集成

我们的后端C应用都是支持跨平台的,至少目前在LinuxSolaris上运行是没有问题的,这样一来我们在配置持续集成环境时就要考虑如何实现在代码Commit后触发多平台并行(同时)集成这个需求。

之前使用Buildbot时是通过为一个Scheduler配置多个Builder满足这个需求的。但现在要换成Jenkins,我们如何来实现呢?昨天在折腾Jenkins时我把问题想简单了,今天细致查看了一下Build Log后才发现之前的配置并未真正实现多平台并行集成。

最初的Jenkins配置大致是这样的:我在Jenkins上添加了两个节点(Slave Node),分别为x86-linux-ci-slave和x86-solaris-ci-slave,并且为这两个节点设置了一个相同的标签"foo-ci-slaves"。之后我创建了一个新Job – "foo-multiplatform-ci",选择的是"构建一个自由风格的软件项目(Build a free-style software project)"。为了使得该Job执行并行集成,我选择了"Restrict where this project can be run",在"Label Expression"中填上了"foo-ci-slaves",其他配置这里就不赘述了。

按照我最初的理解,这样配置后点击"立即构建",两个Slave Node上就会同时进行相关的集成。但Build Log告诉我事实并非我想象的那样:Jenkins只是在一个Slave Node上执行了Job。那使用Jenkins如何来实现前面所说的多平台并行集成呢?查来查去,我发现原来是我在创建Job时选错了配置,我应该选择"构建一个多配置项目(Build multiconfiguration project)"。

与free-style project相比,multiconfiguration project的配置页面中不见了"Restrict where this project can be run"配置选项,但却多出了一个"Configuration Matrix"配置区域。在该区域中,我们可以选择Slaves,在Node/Label中,我们可以看到当前Jenkins中配置的所有Label和Nodes。选择一个Label是无法满足我们的要求的,那样Jenkins只会从Label中的若干个节点中选择一个来执行集成。所以我选择Nodes,将x86-linux-ci-slave和x86-solaris-ci-slave都选上,保存后我们就会在"foo-multiplatform-ci" Job的主页面上看到两个configuration: x86-linux-ci-slave和x86-solaris-ci-slave。点击"立即构建",这两个configuration对应的小球标志就会同时闪动,这说明"foo-multiplatform-ci"正在两个Slave Node上并行运行呢,这才是我想要的结果。

支持多平台并行集成只是Multiconfiguration Project的一个用途之一,《Jenkins: The Definitive Guide》一书对此有更为细致的讲解,你可以结合自定义Axis(坐标轴)以及parameterized Build实现更为复杂的构建需求。但目前我尚未遇到类似需求,所以这里也不敢乱说^_^。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats