设计模式学习(七):适配器模式及其在AWTK中的应用

一、前言

本周参加了第三次设计模式研讨会,主题是适配器(Adapter)模式,接下来我们来看看该模式的具体内容。

二、适配器模式

Adapter模式:将一个类的接口转换成用户希望的另一个接口。

意图:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:主要解决在软件系统中,常常要将一些“现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的,此时可通过适配器来转换接口。

举个例子:开发某个模块时,有一个功能点实现起来很费劲,但之前有一个项目的模块实现了一样的功能点,但现在这个模块和之前那个的接口是不一样的,因此需要在中间加一个适配器( Adapter )。

简单理解:适配器即在不兼容的东西之间搭建一座桥梁,让二者能够一起工作。

三、实际例子

有一个绘图编辑器,可画 LineShape、PolygonShape 等基本图像,图形对象的抽象接口为Shape(即具体图像为子类)。

现在想新增一个绘制字符的子类 TextShape,这实现起来相当困难,但是目前项目中存在一个 TextView 类用于显示和编辑文本。

最理想的情况是复用 TextView 类,实现 TextShape 类 ,但之前设计 TextView 类的时候没有考虑到有 Shape 的存在,因此二者接口不兼容。

此时,我们可定义一个 TextShape 类,继承 Shape 类的接口和 TextView 类实现,将 TextView 实例作为 TextShape 的组成部分。

这个 TextShape 类就被称为 适配器( Adapter )。

四、结构与参与者

4.1 类适配器(多重继承)

在这里插入图片描述

类适配器是该设计模式的一种实现形式,Adapter继承自Target和Adapee类,Adapter类需要重写Target类的Request()函数,在Request()函数中做适当处理,调用Adaptee类的SepcificRequest()函数。最终,Target实际调用的是Adaptee类中的SepcificRequest()函数来完成相关操作,达到适配(兼容)的目的。

4.2 对象适配器(依赖对象组合)

在这里插入图片描述

对象适配器是另一种实现形式,Adapter继承自Target类,且其中有Adaptee类型的成员变量, Adaptee类重写Request()函数时,在Request()函数中使用该成员变量调用的Adaptee类中的SepcificRequest()函数,达到适配(兼容)的目的。

4.3 参与者

  • Target:定义Client使用的与特定领域相关的接口(Shape);
  • Client:与符合Target接口的对象协同(实际上就是Shape的用户);
  • Adaptee:一个已经存在的接口,这个接口需要适配(TextView);
  • Adapter:对Adaptee的接口与Target接口进行适配(TextShape);

4.4 两种适配器实现的比较

类适配器的特点

  • Adapter类直接继承Adaptee类,可以在Adapter类中对Adaptee类的方法进行重定义;
  • 在Adaptee类新增抽象方法,Adapter类也要改动,代码耦合度高;
  • 如果Adaptee类有其他子类, Adapter类中无法调用Adaptee子类的方法。

对象适配器的特点

  • 有时会发现,构造一个Adaptee 类对象比较困难。;
  • 在Adaptee类新增抽象方法, Adapter类无需修改,也能正常使用;
  • 可以使用多态的方式在Adapter类中调用Adaptee子类的方法。

总结:对象适配器耦合度比较低,能使用对象组合的方式,就尽量使用,避免多重继承,但如果构造 Adaptee 对象非常困难,那么可以考虑用类适配器。

五、适配器模式在AWTK中的应用

nanovg是一个非常小巧的矢量绘图函数库。相比cairo和skia的数十万行代码,nanovg不足5000行的C语言代码,称为nano也是名副其实了。

nanovg的设计、接口和代码质量都堪称典范,唯一美中不足的就是性能不太理想。由于nanovg本身只有 OpenGL 的实现,在Android的低端机型和大屏幕的机型上,一个简单的界面每秒只能画十几帧。

因此,AWTK 使用 nanovg 矢量绘图功能的前端,虽然其后端实现只有OpenGL,但 AWTK 补充了 AGG 、AGGE、BGFX 的后端实现。这其中便采用了适配器模式,比如兼容 nanovg 和 AGGE 接口的适配器 nanovg_agge 。

AWTK是 ZLG 开发的开源 GUI 引擎,官网地址:https://www.zlg.cn/index/pub/awtk.html

nanovg仓库:https://github.com/zlgopen/nanovg

以 AGGE 为例,结合本文 4.2 中的类图来看,nanovg 即为 Target,AGGE 即为 Adaptee,nanovg_agge 即为适配器Adapter,其中适配器 nanovg_agge 的部分代码如下,感兴趣的朋友可以查看 AWTK 源码。

//awtk\3rd\nanovg\agge\nanovg_agge.cpp
void nvgReinitAgge(NVGcontext* ctx, uint32_t w, uint32_t h, uint32_t stride, enum NVGtexture format,
                   uint8_t* data) {
  NVGparams* params = nvgGetParams(ctx);
  AGGENVGcontext* agge = (AGGENVGcontext*)(params->userPtr);

  nvgInitAGGE(agge, params, w, h, stride, format, data);
}

NVGcontext* nvgCreateAGGE(uint32_t w, uint32_t h, uint32_t stride, enum NVGtexture format, uint8_t* data) {
  NVGparams params;
  NVGcontext* ctx = NULL;
  AGGENVGcontext* agge = new AGGENVGcontext();

  if (agge == NULL) goto error;

  memset(&params, 0, sizeof(params));
  params.setLineJoin = aggenvg__setLineJoin;
  params.setLineCap = aggenvg__setLineCap;
  params.renderCreate = aggenvg__renderCreate;
  params.findTexture = aggenvg__renderFindTexture;
  params.renderCreateTexture = aggenvg__renderCreateTexture;
  params.renderDeleteTexture = aggenvg__renderDeleteTexture;
  params.renderUpdateTexture = aggenvg__renderUpdateTexture;
  params.renderGetTextureSize = aggenvg__renderGetTextureSize;
  params.renderViewport = aggenvg__renderViewport;
  params.renderCancel = aggenvg__renderCancel;
  params.renderFlush = aggenvg__renderFlush;
  params.renderDelete = aggenvg__renderDelete;
  params.clearCache = aggenvg__clearCache;
  params.userPtr = agge;
  params.edgeAntiAlias = 1;

  nvgInitAGGE(agge, &params, w, h, stride, format, data);

  ctx = nvgCreateInternal(&params);
  if (ctx == NULL) goto error;

  return ctx;

error:
  if (ctx != NULL) nvgDeleteInternal(ctx);
  return NULL;
}

void nvgDeleteAGGE(NVGcontext* ctx) {
  nvgDeleteInternal(ctx);
}

需要注意的是,观察 nanovg_agge 的构造函数 nvgCreateAGGE(),其中构建了一个 AGGE 的对象 AGGENVGcontext* agge ,通过该对象来调用 AGGE 接口中的具体实现,因此 nanovg_agge 是对象适配器。

六、总结

优点

  • 可以让两个没有关联的类一起运行,协同工作。
  • 提高了类的复用;
  • 增加了类的透明度;
  • 灵活性好;

缺点:过多使用适配器,会让系统非常凌乱,比如看到的是A接口,实际上内部被适配成了B接口,这种情况多了,无异于异常灾难,因此如果不是必要情况,尽量不要使用适配器,而是直接对系统进行重构。

应用场景:使用一个已经存在的类,如果它的接口和你实际要求不一致时,可以考虑使用适配器模式,需要注意的是最好在调用者和功能提供者双方都不太容易修改的时候再使用适配器模式,而不是一有不同就使用它。

注意事项:适配器不是在详细设计时添加的,而是解决正在服役项目的问题。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,198评论 6 514
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,334评论 3 398
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,643评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,495评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,502评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,156评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,743评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,659评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,200评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,282评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,424评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,107评论 5 349
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,789评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,264评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,390评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,798评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,435评论 2 359

推荐阅读更多精彩内容