设计模式的C语言应用

文章目录

  • 设计模式的C语言应用-导言-第一章
      • C语言应用设计模式的误区
        • 用C模拟面向对象
        • 用很多特定的宏来将C在形式上封装为C++
        • 盲目追求设计模式
      • 设计模式的分类
  • 设计模式的C语言应用-状态机模式-第二章
    • 模式介绍
      • 有限状态机
    • 简单状态机模式实现
    • 普通状态机模式实现
    • 复杂状态机模式实现
    • 模式实现总结
  • 设计模式的C语言应用-责任链模式-第三章
    • 模式介绍
    • 责任链模式实现
      • 责任链节点定义
      • 责任链和处理函数
      • 注册和反注册函数
      • 调用流程
    • 内核的责任链模式实例
      • 事件触发的处理函数
    • 模式实现总结
  • 设计模式的C语言应用-观察者模式-第四章
    • 模式介绍:观察者模式(Observer)
    • 观察者模式实现
      • 观察者节点定义
      • 观察者链和处理函数
      • 注册和反注册函数
      • 调用流程
    • 内核的观察者模式实现
      • 观察节点模型
      • 事件触发的处理函数
    • 模式实现总结
  • 设计模式的C语言应用-命令模式-第五章
    • 模式介绍:命令模式(command)
    • 命令模式实现
      • 实现流程
      • 命令模式的示例代码
        • 命令码的定义
        • 命令封装结构体
        • 命令的实际处理函数
        • 命令码数组
        • Invoker和receiver
      • 内核的实现例子
    • 模式实现总结
  • 设计模式的C语言应用-适配及系列模式-第六章
    • 模式介绍:适配系列模式
      • 适配器模式Adapter
      • 装饰者模式Decorator
      • 代理模式Proxy
      • 共同点和区别
    • 适配系列模式实现
      • 原函数样例
      • 传统适配模式函数样例
      • 传统装饰者模式函数样例
      • 传统代理模式函数样例
      • C语言适配系列模式
    • 模式实现总结
  • 设计模式的C语言应用-建造者模式-第七章
    • 模式介绍
    • 建造者模式实现
      • 原始函数样例
      • 建造者模式函数样例
    • [模板方法模式(Template Method)](https://bbs.huaweicloud.com/blogs/064678e3c9bd11e7b8317ca23e93a891)介绍
    • 模式实现总结
  • 设计模式的C语言应用-外观模式-第八章
    • 外观模式(Facade)介绍
    • 外观模式实现
      • 内部原始接口
      • 原始函数样例
      • 外观模式函数样例
    • 模式实现总结
  • 设计模式的C语言应用-访问者模式-第九章
    • 访问者模式(Visitor)介绍
      • 面向对象实现的访问者模式
      • C实现的访问者模式
  • 设计模式的C语言应用-非典型模式-第十章
    • [原型模式(Prototype) ](https://bbs.huaweicloud.com/blogs/161a067ecdbc11e7b8317ca23e93a891)介绍
      • 深复制和浅复制
    • [单例模式(Singleton) ](https://bbs.huaweicloud.com/blogs/161a067ecdbc11e7b8317ca23e93a891)介绍
    • 组合模式(Composite)
    • 享元模式(Flyweight)
    • 工厂模式和抽象工厂模式(Factory)
      • 工厂模式样例代码
    • 解释器模式(Interpreter)介绍
    • [迭代器模式(Iterator) ](https://bbs.huaweicloud.com/blogs/161a067ecdbc11e7b8317ca23e93a891)介绍
    • 备忘录模式(Memento)介绍
    • 策略模式(Strategy)介绍
    • 桥接模式(Bridge)介绍

设计模式的C语言应用-导言-第一章

已剪辑自: https://bbs.huaweicloud.com/blogs/100076

【摘要】 软件编写模式是开发过程中的重要经验总结。灵活运用设计模式,一方面利于我们编写高质量的代码,另一方面也方便我们对代码进行维护。

软件编写模式是开发过程中的重要经验总结。灵活运用设计模式,一方面利于我们编写高质量的代码,另一方面也方便我们对代码进行维护。

设计模式最开始是针对面向对象语言提出的。经典的书如《设计模式:可复用面向对象软件的基础》,《java与模式》。《设计模式》的作者俗称”GOF”, gang of four,书中从面向对象的设计中精选出23个设计模式,这个也成了后来设计模式的范例。

​ C语言设计模式的书非常少,目前没有看到合适的。有一本《C嵌入式编程设计模式》,作者douglass。书一共6章,里面没有太多高含金量的内容。书的主要问题在于没有真正把设计模式应用到C,而是把一些基本的嵌入式开发注意点包装成模式,中断算一种设计模式,轮询算一种设计模式,互斥算一种模式。照这个套路,C函数指针使用肯定也算一种模式,强制类型转换肯定也得算。我觉得是为了出书而滥造模式。这本书就状态机模式描述比较多些。这人还合写了另外一本书叫《Real-Time Design Patterns》,我也感觉内容非常稀,那些内容根本不能称为设计模式(Design Patterns)。我以后不会看他的书了。

本专题尝试简单讲解设计模式的思路,在嵌入式中的应用和样例代码入手,特别是注意设计模式在linux内核和实际开发中的应用,总结出真正在C语言和嵌入式开发中有生命力的少数几个模式。C语言实现设计模式的几个利器有结构体,函数指针,利用数组实现多态。

​ 本专题讲解模式会按照如下格式:

u 模式的介绍

u 模式的应用场景

u 样例代码

u 模式总结

样例代码全部以C代码实现,可能会穿插讲解java样例以方便对比为什么C实现模式演化成这个样子以及和面向对象语言的差异。

C语言应用设计模式的误区

在C语言开发中应用设计模式有几种现象,分析如下

用C模拟面向对象

用C模拟面向对象,用结构体模拟类,用结构体包含看作继承。这种做法在特定需要的场合少量使用尚可,但是如果为了模拟面向对象的特性就南辕北辙了。

用很多特定的宏来将C在形式上封装为C++

用C模仿面向对象和设计模式有另外一种流派,就是用很多特定的宏来封装。我非常反对这种用法,因为基本上对于程序员而言,已经对C语言的阅读和书写造成了严重的干扰,不能为了面向对象而面向对象,C语言自然有其简洁高效的一面。

盲目追求设计模式

​ 随着设计模式的思想的普及,很多设计师对设计模式盲目崇拜和过分追求,为了用模式而用模式,削足适履。对于嵌入式开发常用的C语言而言,少了很多面向对象的特点,经典的23个设计模式肯定不会都适用,而且在实际工作中,其实也不会为了模式而模式。而且从技术上讲23个设计模式当时是围绕面向对象提出的,有的模式偏向于逻辑,那么可以被C借鉴,有的是为了解决面向对象本身的集成,关联等问题,那就没有必要借鉴。

设计模式的分类

设计模式如下分为创建型模式,结构型模式,行为型模式。

创建型模式

​ 1、抽象工厂模式(Abstract Factory)

​ 2、建造者模式(Builder)

​ 3、工厂方法模式(Factory Method)

​ 4、原型模式(Prototype)

​ 5、单例模式(Singleton)

结构型模式

​ 1、适配器模式(Adapter)

​ 2、桥接模式(Bridge)

​ 3、组合模式(Composite)

​ 4、装饰者模式(Decorator)

​ 5、外观模式(Facade)

​ 6、享元模式(Flyweight)

​ 7、代理模式(Proxy)、

行为型模式

​ 1、职责链模式(Chain of Responsibility)

​ 2、命令模式(Command)

​ 3、解释器模式(Interpreter)

​ 4、迭代器模式(Iterator)

​ 5、中介者模式(Mediator)

​ 6、备忘录模式(Memento)

​ 7、观察者模式(Observer)

​ 8、状态模式(State)

​ 9、策略模式(Strategy)

​ 10、模板方法模式(Template Method)

​ 11、访问者模式(Visitor)


设计模式的C语言应用-状态机模式-第二章

已剪辑自: https://bbs.huaweicloud.com/blogs/111099

设计模式的C语言应用-状态机模式

模式介绍

有限状态机

状态机示例1

状态机示例2

简单状态机模式实现

普通状态机模式实现

复杂状态机模式实现

模式实现总结

模式介绍

状态(state)模式是C语言实现相当常用的模式,也是能够在C语言***现出来的最显性的模式之一。在面向对象里,状态模式允许一个对象在内部状态改变的时候改变其行为。

状态用法很多,最常见的是状态机,分为无限状态机和有限状态机。

有限状态机 finite-state machine, FSM, 输入集合和输出集合都是有限的,并只有有限数目的状态。 一般说到状态机即是对有限状态机的简称。

无限状态机 infinite-state machine,ISM ,输入和输出集合无线,状态数目无限的状态机。

在C语言里,状态模式有且仅有一种经典用法,就是有限状态机(FSM)的实现。实现的方式极为突出明显,大部分情况都能直接照搬框架。

状态机最常见的使用场景是实现协议。通常协议会有几个核心状态机描述。

有限状态机

有一个灯,按下开按钮,就会开灯,按下关按钮就会关灯。这就是一个很典型的简单的有限状态机。简单的描述有2个状态,关灯[STATE_OFF],亮[STATE_LIGHT_ON] 。有两个事件,开和关按钮。这两个事件促使状态机间的转换。

设计模式的C语言应用_第1张图片

有一个灯,按下开按钮,就会开灯,按下关按钮就会关灯。和一般等不同的是,两次开之间的灯的明暗不一样。也就是说,第一次开的时候,是高亮,关灯后,再开是低亮,下次再开是高亮,循环往复。

​ 这就是一个很典型的简单的有限状态机。简单的描述有3个状态,关灯[STATE_OFF],高亮[STATE_HIGH_LIGHT],低亮[STATE_LOW_LIGHT]。

设计模式的C语言应用_第2张图片

简单状态机模式实现

​ 以状态机示例1为目标,如果用if/switch/case来,就没有什么设计和模式的意义,那只是最普通的流程开发技能。以下是简单状态机模式实现,适用于转移条件单一,对结果很确定的状态机。

#define STATE_OFF 0

#define STATE_LIGHT_ON 1

#define STATE_MAX 2

#define EVETN_BTN_OFF

#define EVETN_BTN_ON

#define EVETN_MAX

int light_fsm_simple_table[STATE_MAX][EVETN_MAX] =

{

[STATE_OFF][EVETN_BTN_OFF] = STATE_OFF,

[STATE_OFF][EVETN_BTN_ON] = STATE_LIGHT_ON,

[STATE_LIGHT_ON][EVETN_BTN_OFF] = STATE_OFF,

[STATE_LIGHT_ON][EVETN_BTN_ON] = STATE_LIGHT_ON

};

int light_fsm_event(int cur_stat, int event)

{

int next_state;

next_state = light_fsm_simple_table[cur_stat][event];

}

int main()

{

int light_state = STATE_OFF;

int eve1 = EVETN_BTN_OFF;

int eve2 = EVETN_BTN_ON;

light_state = light_fsm_event(light_state, eve1);

printf(“now light state is %d\n”, light_state);

light_state = light_fsm_event(light_state, eve2);

printf(“now light state is %d\n”, light_state);

}

以上代码有几个要点

\1. 状态转移数组。由于简单模式某种状态下发生某事件的结果是确定的,所以数组的值就是下一个状态。

\2. 需要一个状态处理的封装函数light_fsm_event。里面除了转移状态,可以增加扩展处理。不然简单模式应用就很局限。比如可以在light_fsm_event里面加入

If(next_state == STATE_LIGHT_ON)

{ printf(“light is on”;}

普通状态机模式实现

​ 大型一点的项目,比如复杂协议的实现,一个状态转移到下一个状态的情况是比较复杂的,无法用当前状态和事件简单确定,所以一般需要函数。

以下代码实现了状态机示例二,为样例代码,未运行实验过。

#define STATE_DEPEND 4

#define STATE_OFF 0

#define STATE_HIGH_LIGHT 1

#define STATE_LOW_LIGHT 2

#define STATE_MAX 3

#define EVETN_BTN_OFF

#define EVETN_BTN_ON

#define EVETN_MAX

int last_state = STATE_LOW_LIGHT;

int last_light_state = STATE_LOW_LIGHT;

struct {

int (*func) ();

int next_state;

} light_fsm [STATE_MAX][EVETN_MAX] =

{

//STATE_OFF

{

​ { lfsm_ignore, STATE_MAX }, /EVETN_BTN_OFF/

​ { lfsm_btn_on, STATE_DEPEND }, /EVETN_BTN_ON/

}

//STATE_HIGH_LIGHT

{

​ { lfsm_btn_off, STATE_OFF }, /EVETN_BTN_OFF/

​ { lfsm_ignore, STATE_MAX }, /EVETN_BTN_ON/

}

//STATE_LOW_LIGHT

{

​ { lfsm_btn_off, STATE_OFF }, /EVETN_BTN_OFF/

​ { lfsm_ignore, STATE_MAX }, /EVETN_BTN_ON/

}

}

int lfsm_ignore(int cur_stat, int event)

{

​ printf(“invalid state or event\n”);

​ return 0;

}

int lfsm_btn_on(int cur_stat, int event)

{

if(last_light_state == STATE_HIGH_LIGHT)

{

​ return STATE_LOW_LIGHT;

}

else if(last_light_state == STATE_LOW_LIGHT)

{

​ return STATE_HIGH_LIGHT;

}

else

{

​ printf(“invalid state\n”);

​ return STATE_MAX;

}

}

int lfsm_btn_off(int cur_stat, int event)

{

last_light_state = cur_stat;

return 0;

}

int light_change_state(int cur_stat, int next_state,int event)

{

//if light on has special handling

if(next_state = STATE_HIGH_LIGHT)

{

​ printf(“rejoice, now bright light\n”)

};

//other state change related handlings, maybe use current state and next state, or event type

last_state = cur_stat;

cur_stat = next_state;

return 0;

}

int light_event_happen(int event)

{

//if light on has special handling

if(event = EVETN_BTN_OFF)

{

​ printf(“someone turn off light\n”);

}

//other event type related handlings

return 0;

}

int light_fsm_event(int cur_stat, int event)

{

int next_state, next_state_tmp;

next_state_tmp = *(light_fsm[cur_stat][event].func);

if(next_state_tmp == STATE_MAX)

{

​ printf(“fsm error\n”);

​ return -1;

}

if(light_fsm[cur_stat][event].next_state == STATE_DEPEND)

{

​ next_state = next_state_tmp;

}

else

{

​ next_state = light_fsm[cur_stat][event].next_state;

}

light_change_state(next_state, cur_stat, event);

light_event_happen(event);

}

int main()

{

int light_state = STATE_OFF;

light_fsm_event(light_state, EVETN_BTN_OFF);

light_fsm_event(light_state, EVETN_BTN_ON);

light_fsm_event(light_state, EVETN_BTN_OFF);

light_fsm_event(light_state, EVETN_BTN_ON);

}

普通模式的状态机的几个关键点

\1. 状态机数组由状态事件处理函数+下一个状态数组代替简单模式的下一个状态的数组

\2. 由于在特定模式特定事件发生时,有的情况不能确定下一个状态的跳转,有的情况可以。所以下一状态有个特殊值为STATE_DEPEND。如果遇到这个值,就从状态变化函数里获得下一个状态。否则按照状态机数组设定的状态。

\3. 设定一个状态STATE_MAX用来表示错误事件,加上一个lfsm_ignore函数来处理这种情况。比如本例中,设定EVETN_BTN_ON不可能在开灯的时候发生。

\4. 状态机里除了状态机数字函数执行,有两类通用的函数,不参与主要的状态机运行,但是对状态机有影响。一类和特定的状态或状态转移有关,另外一类是和特定的事件有关。在样例代码里分别以light_change_state和light_event_happen来表示。

\5. 一般情况下,有一个全局变量保存当前状态和上一个状态。

面向对象语言实现状态机通常是一个状态的抽象父类,每个状态有一个子类和一个实例。C语言里状态转移表的函数指针是通过状态子类的成员函数实现。其他的写法思路比较接近。

复杂状态机模式实现

​ 最常见的复杂状态机是为了实现网络协议。比如OSPF,可以参见我写的另外一篇文章ZEBRA中FSM编写总结.doc

模式实现总结

\1. 项目开发里最常见的使用为普通状态机,网络协议使用的复杂状态机也是在普通状态机上添加一些特性而来,基本特征是非常类似的。

\2. C语言实现状态机的模式是非常固定的。状态转移表和核心的状态转移函数是核心。普通状态机的几个要素,不管在初始设计中有没有使用到,建议都写上。


设计模式的C语言应用-责任链模式-第三章

已剪辑自: https://bbs.huaweicloud.com/blogs/100397

设计模式的C语言应用-责任链模式

模式介绍

责任链模式实现

责任链节点定义

责任链和处理函数

注册和反注册函数

调用流程

内核的责任链模式实例

handler的格式

handler的注册

事件触发的处理函数

模式实现总结

模式介绍

​ 责任链将需要触发的对象组成一条链,发送者将请求发给链的第一个接收者,并且沿着这条链传递,直到有一个对象来处理它或者直到最后也没有对象处理而留在链末尾端。

设计模式的C语言应用_第3张图片

图表 1责任链模式流程图

​ 责任链在C语言里也是实现形式非常明显的模式。最典型的责任链有linux内核的中断处理机制的纯软件部分和内核网络netfiler的HOOK机制。这两者均强化了责任链机制,重点在引入了责任优先级方法和增加了通过/终结两种处理结果。

​ 责任链模式的最重要的数据结构是handler链表。事件发生时,handler链表上的回调函数会被以此调用。优先级决定了那个handler会被先调,哪些会被后调用。在扩展特性里,每个handler可以有不处理和处理完之后继续交给下一个handler两种选择。如果该事件最后没有被消费,会有一个异常处理函数。如果责任链上任意一个handler消费了事件,那么就不传给下一个handler,直接结束。

逻辑上和责任链模式最相近的一个设计模式为观察者模式。流程图如下。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个平等的handler,而不是逐级处理。也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。

设计模式的C语言应用_第4张图片

图表 2观察者模式流程图

责任链模式实现

​ 责任链模式事件怎么触发不要紧,关键就是handler的数据结构组织和处理逻辑。

责任链节点定义

//两类处理结果,子类可以扩展

#define CHAIN_PASS 0

#define CHAIN_STOP 1

typedef int (*chain_func)(char *buf);

struct chain_ops_node {

​ struct list_head list; //内核链表标准结构

​ chain_func *handler; //handler的回调函数

​ int priority; //优先级

};

责任链和处理函数

//全局的责任链

struct list_head chain_global_list;

//具体的处理函数

int chain_handler1(char *buf)

{

//do something

if(/some conditions/)

{

​ return CHAIN_PASS;

}

return CHAIN_STOP;

}

int chain_handler2(char *buf)

{

//do something

if(/some conditions/)

{

​ return CHAIN_PASS;

}

return CHAIN_STOP;

}

//封装成节点

struct chain_ops_node node1 =

{

.handler = chain_handler1,

.priority = 0

}

struct chain_ops_node node2 =

{

.handler = chain_handler2,

.priority = 1

}

注册和反注册函数

特别注意,一般是需要信号量锁定的,因为很可能链条上的数据正在执行。内核里喜欢用rcu锁,可以避免资源互斥引起cpu浪费。

int chain_register(struct chain_ops_node *node)

{

//lock chain_global_list

//add node into chain_global_list according to priority

//unlock chain_global_list

return 0;

}

int chain_unregister(struct chain_ops_node *node)

{

//lock chain_global_list

//delete node into chain_global_list

//unlock chain_global_list

return 0;

}

调用流程

int main()

{

struct list_head *node;

struct chain_ops_node *node_func;

char buf[16];

chain_register(&node1);

chain_register(&node1);

//something happend, should trigger responsibility chain

//fill buf with event

list_for_each(node, &chain_global_list)

{

​ node_func = (struct chain_ops_node *)node;

​ if(node_func.handler(buf) == CHAIN_STOP)

​ {break;}

}

return 0;

}

内核的责任链模式实例

内核里最典型的就是内核中断处理和和内核网络netfiler的HOOK机制。而内核网络netfiler的HOOK机制的责任链模式体现更为完整充分。所以本文以netfiler的HOOK机制为例讲解。

内核的hook链就是责任链模式的handler链。nf_hook_ops就是handler链的一个handler节点。

struct nf_hook_ops {

​ struct list_head list; //内核链表标准结构

​ /* User fills in from here down. */

​ nf_hookfn *hook; //handler的回调函数

​ struct module *owner; //模式无关,可忽略

​ u_int8_t pf; //协议族,用来区分事件处理的,可以看作辅助标记。作为单链可忽略。

​ unsigned int hooknum; //挂在在哪个hook链上,netfiler的hook设计支持多hook链条,不过同一类事件只是触发一个hook链条的函数。所以从设计模式上讲这里只是同时实现了4条互不相干的责任链模式的handler链。作为单链可忽略。

​ /* Hooks are ordered in ascending priority. */

​ int priority; //优先级

};

比如如下定义:

static struct nf_hook_ops nf_nat_ops[] __read_mostly = {

​ /* Before packet filtering, change destination */

​ {

​ .hook = nf_nat_in,

​ .owner = THIS_MODULE,

​ .pf = NFPROTO_IPV4,

​ .hooknum = NF_INET_PRE_ROUTING,

​ .priority = NF_IP_PRI_NAT_DST,

​ },

}

int nf_register_hook(struct nf_hook_ops *reg)

非常简单的操作,在锁的保护下,将handler节点加入到链表nf_hooks[reg->pf][reg->hooknum]上。插入链表的顺序由priority决定,升序排列。

从这里可以看出,链表是二维的,区分了协议族和hooknum(网络通路的位置)。本质上,对于一个固定handler,可以认为只是和一个handler链条发生关系。

事件触发的处理函数

以IPV4的NF_INET_PRE_ROUTING hook为例,ip_rcv函数最后会调用nf_hook_slow,遍历链表调用handler函数。Handler返回处理结果有NF_ACCEPT,NF_STOLEN,NF_DROP等好几种。nf_hook_slow会根据这些结果决定接着调用链表上下一个handler还是终止等一系列动作。

​ linux内核遍历的方法基本上都是list_for_each_xxx函数。

模式实现总结

\1. 责任链模式在内核的实现很普遍,实现代码典型而简单,都是先定义各异handler的链表节点,包含list结构体,优先级,回调处理函数3个要素即可。更复杂的责任链模式实现只不过多条链,但是单个链的属性是没有改变的。而netfilter的链已经算比较复杂的,所以绝大部分编码学习到这个水平就足够了。

2.每个handler的处理结果根据需要定义,总体上讲都是继续和不继续两种。


设计模式的C语言应用-观察者模式-第四章

已剪辑自: https://bbs.huaweicloud.com/blogs/101255

设计模式的C语言应用-观察者模式

模式介绍:观察者模式(Observer)

观察者模式实现

观察者节点定义

观察者链和处理函数

注册和反注册函数

调用流程

内核的观察者模式实现

观察节点模型

事件触发的处理函数

模式实现总结

模式介绍:观察者模式(Observer)

​ 观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。

设计模式的C语言应用_第5张图片

图表 1观察者模式流程图

观察者模式在C语言里也是实现形式非常明显的模式。逻辑上和责任链模式最相近的一个设计模式为观察者模式。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个handler,而不是逐级处理。也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。一个Observer是否注册和执行不应该影响其他的Observer。而在责任链模式上,前面的责任handler在传递给下一个handler时,是可以改变事件相关变量。

​ 但是在C语言实现上,观察者模式的handler绝大部分也是按照链表来组织的,在代码执行上,实际上相当于遍历链表。和责任链模式的区别在于每个handler没有优先级,没有权力决定是否停止遍历,最后事件也不需要被handler消费掉,也就是没有异常函数。

​ 所以从C语言代码实现上讲,观察者模式可以看作责任链模式的特例。

\1. 无优先级

\2. 不能修改随事件而来的变量。比如在netfilter使用责任链模式就修改了随事件而来的数据包。

\3. 每个handler/observer只能无条件把事件传给observer链表的下一个节点。

image.png

图表 2观察者模式和责任链模式对比

左边是责任链模式,右边是观察者模式的内核代码实现流程。

观察者模式实现

观察者节点定义

//不需要处理结果

typedef int (*observer_func)(char *buf);

struct observer_ops_node {

​ struct list_head list; //内核链表标准结构

​ observer_func *handler; //handler的回调函数,没有优先级

};

观察者链和处理函数

//全局的观察者链

struct list_head observer_global_list;

//具体的处理函数

int observer_handler1(char *buf)

{

//do something

return 0;

}

int observer_handler2(char *buf)

{

//do something

return 0;

}

//封装成节点

struct observer_ops_node node1 =

{

.handler = observer_handler1,

}

struct observer_ops_node node2 =

{

.handler = observer_handler2,

}

注册和反注册函数

特别注意,一般是需要信号量锁定的,因为很可能链条上的函数正在执行。内核里喜欢用rcu锁,可以避免资源互斥引起cpu浪费。

int observer_register(struct observer_ops_node *node)

{

//lock observer_global_list

//add node into observer_global_list

//unlock observer_global_list

return 0;

}

int observer_unregister(struct observer_ops_node *node)

{

//lock observer_global_list

//delete node into observer_global_list

//unlock observer_global_list

return 0;

}

调用流程

不检查观察者结果,必须全部遍历完。

int main()

{

struct list_head *node;

struct observer_ops_node *node_func;

char buf[16];

observer_register(&node1);

observer_register(&node1);

//something happend, should trigger responsibility observer

//fill buf with event

list_for_each(node, &observer_global_list)

{

​ node_func = (struct observer_ops_node *)node;

​ node_func.handler(buf);

}

return 0;

}

内核的观察者模式实现

观察节点模型

struct notifier_block {

​ int (*notifier_call)(struct notifier_block *, unsigned long, void *); //观察者回调函数

​ struct notifier_block __rcu *next; //链表结构

​ int priority; //优先级, 内核里这个属于扩展的用法。

};

下面的例子。

static struct notifier_block arp_netdev_notifier = {

​ .notifier_call = arp_netdev_event,

};

最后调用notifier_chain_register注册arp_netdev_notifier到netdev_chain链表上。

事件触发的处理函数

那么当网络接口状态发生变化时,就通过call_netdevice_notifiers(NETDEV_PRE_UP, dev);调用通知所有注册的observer回调函数。

​ 函数简化如下。里面需要注意的只有一点,返回结果可能会有NOTIFY_STOP_MASK,允许某个observer停止遍历调用。从这个意义讲,observer既有优先级又能阻止调用,观察者模式和责任链模式的区别就很小了。

static int __kprobes notifier_call_chain(struct notifier_block **nl,

​ unsigned long val, void *v,

​ int nr_to_call, int *nr_calls)

{

​ int ret = NOTIFY_DONE;

​ struct notifier_block *nb, *next_nb;

​ nb = rcu_dereference_raw(*nl);

​ while (nb && nr_to_call) {

​ next_nb = rcu_dereference_raw(nb->next); //取下一个observer

​ ret = nb->notifier_call(nb, val, v);

​ if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)

​ break;

​ nb = next_nb;

​ }

​ return ret;

}

模式实现总结

​ 总体用法和责任链模式类似,而在内核里实现的观察者模式其实并没有那么“纯粹”,而是扩展了优先级特性和可停止特性。这个破坏了Observer之间的独立性,因为原则上,一个Observer是否注册和执行不应该影响其他的Observer,内核的扩展这就使观察者模式变成了责任链模式模式。

设计模式的C语言应用-命令模式-第五章

已剪辑自: https://bbs.huaweicloud.com/blogs/109745

模式介绍:命令模式(command)

命令模式的解释如下:

向对象发送一个请求,但是并不知道该请求的具体接收者是谁,具体的处理过程是如何的,只知道在程序运行中指定具体的请求接收者即可,对于这样将请求封装成对象的我们称之为命令模式。所以命令模式将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。同时命令模式支 持可撤销的操作。

​ 命令模式的C语言实现也是非常显性的。命令发送方不通过直接调用的方式,而是通过发一个命令消息给接收方,让接收方执行操作。C语言里采用命令模式的最常见的原因是核间通信,进程间交互。如果是核间通信,通常是把命令按协定的格式封装在消息数据包里。如果是进程间通信,通常封装成一个结构体,把参数带过去。命令的通道通常是队列。

命令模式实现

实现流程

C语言命令模式经典方式如下,和面向对象是有明显的不同的。下图的invoker表示发命令的实体,而handler表示执行命令的实体,这个和面向对象的命令模式里的含义不一样。

设计模式的C语言应用_第6张图片

图表 1 C语言命令模式示意图

设计模式的C语言应用_第7张图片

图表 2面向对象命令模式

C语言实现的命令模式核心数据结构是命令。发布命令的是invoker,多个invoker将命令封装起来,送到队列里。有一个函数或者线程称为receiver,检查队列里是否有没有处理的命令。由receiver负责调用各个handler。另外一个被经常使用的辅助数据结构是命令码数组,在如果invoker和handler运行于不同的环境,这种做法几乎是必选,如核间通信,内核和应用态通信。命令码作为索引,handler调用函数作为元素,Receiver根据不同的命令码调用handler。

也有不使用消息队列的C语言实现。

如果invoker和handler运行于相同的环境,可能直接把handler的回调函数的指针挂在命令结构体上,receiver可以直接调用handler的回调函数。很显然,不同的运行环境是没法这么做的。所以命令码数组是一个更为通用,封装性更好的方法。

​ 面向对象的命令模式并没有提及到命令的消息队列,也没有提及命令码数组。消息队列本身并不是命令模式的一部分,而是在C语言实现里经常会用到的,特别是命令和执行不再同一个运行环境。命令码数组对于面向对象来说可以用多个子类来实现,所以也不体现出来。

命令模式的示例代码

以下代码为伪码。

命令码的定义

#define CMD_1 0

#define CMD_2 1

#define CMD_MAX 2

命令封装结构体

#define CMD_LEN 256

struct cmd_msg

{

int cmd_code;

char buf[CMD_LEN];//如果是不同环境的,只能用buffer数组,否则可以用指针

};

命令的实际处理函数

typedef int (*cmd_func)(char *buf);

int cmd1_handler(char *buf)

{

return 0;

}

int cmd2_handler(char *buf)

{

return 0;

}

命令码数组

命令码数组有两种方式,一种是将命令码作为数据的索引。另外一种情况是由于命令码太大,有一些特殊的规定,没法作为索引。所以在一个结构体里封装命令码和handler,最后实现一个结构体数据,这个在复杂的内核实现里会出现。

​ 下面是简单的命令码,就是函数指针数组。

cmd_func cmd_table[] =

{

cmd1_handler,

cmd2_handler,

};

Invoker和receiver

Invoker的工作很简单,填充命令命令封装结构体,将其放入队列。

int invoker1()

{

struct cmd_msg cmd1_case;

memset(&cmd1_case, 0, sizeof(cmd1_case));

cmd1_case.cmd_code = CMD_1;

//send cmd1_case to queue

return 0;

}

int invoker2()

{

struct cmd_msg cmd1_case;

memset(&cmd1_case, 0, sizeof(cmd1_case));

cmd1_case.cmd_code = CMD_2;

//send cmd1_case to queue

return 0;

}

Receiver的工作就是监视命令队列,取出命令调用handler。

int cmd_receiver()

{

struct cmd_msg *cmd_case;

while(1)

{

​ //get cmd_case from queue while queue is not empty

​ (*cmd_table[cmd_case->cmd_code])(cmd_case->buf);

}

return 0;

}

命令队列有很多形态,比如IPC通道,用信号量,也能不要队列直接调用,总之就是让命令交到reciever手上然后分发调用handler。

​ 伪码main程序:

int main()

{

invoker1();

invoker2();

cmd_receiver();

return 0;

}

内核的实现例子

内核有非常多的例子,典型的是wireless extension的接口。上层应用通过ioctl下发命令到内核,内核解析后,调用相应的wireless extension内核侧处理函数。这就是典型的不同运行环境的命令模式。参数是buffer,带命令码而不是直接发送函数指针。

/* -------------------------- IOCTL LIST -------------------------- */

typedef int (*iw_handler)(struct net_device *dev, struct iw_request_info *info,

​ void *wrqu, char *extra);

/* Wireless Identification *///命令码

#define SIOCSIWCOMMIT 0x8B00 /* Commit pending changes to driver */

#define SIOCGIWNAME 0x8B01 /* get name == wireless protocol */

#define SIOCSIWNWID 0x8B02 /* set network id (pre-802.11) */

#define SIOCGIWNWID 0x8B03 /* get network id (the cell) */

#define IW_HANDLER(id, func) \

​ [IW_IOCTL_IDX(id)] = func

//命令码数组

static const iw_handler wl_handler[] =

{

​ IW_HANDLER(SIOCSIWCOMMIT, (iw_handler) wireless_commit),

​ IW_HANDLER(SIOCGIWNAME, (iw_handler) wireless_get_protocol),

​ IW_HANDLER(SIOCSIWFREQ, (iw_handler) wireless_set_frequency),

​ IW_HANDLER(SIOCGIWFREQ, (iw_handler) wireless_get_frequency),

​ …

}

//典型的receiver

static int ioctl_standard_iw_point(xxx)

{

{

​ /* Check need for ESSID compatibility for WE < 21 */

​ switch (cmd) {

​ case SIOCSIWESSID: //没法用索引,所以用了switch case

​ case SIOCGIWESSID:

​ case SIOCSIWNICKN:

​ case SIOCGIWNICKN:

​ if (iwp->length == descr->max_tokens + 1)

​ essid_compat = 1;

​ else if (IW_IS_SET(cmd) && (iwp->length != 0)) {

​ char essid[IW_ESSID_MAX_SIZE + 1];

​ unsigned int len;

​ len = iwp->length * descr->token_size;

​ if (len > IW_ESSID_MAX_SIZE)

​ return -EFAULT;

​ err = copy_from_user(essid, iwp->pointer, len);

​ if (err)

​ return -EFAULT;

​ if (essid[iwp->length - 1] == ‘\0’)

​ essid_compat = 1;

​ }

​ break;

​ default:

​ break;

​ }

}

可以看出,由于内核命令码是有特别含义的,所以不能作为索引,只能receiver干脆用switch case。在ioctl_standard_iw_point函数里就是用switch case。

模式实现总结

命令模式也是C语言实现的显性的设计模式,角色分为发布命令的invoker,分派命令的receiver和实际执行命令的handler。命令队列和命令码数组是核心的辅助元素。命令码数组目前只有两种类型。命令队列的实现类型就非常多,甚至未必是队列形式,需要设计人员根据经验把握。

设计模式的C语言应用-适配及系列模式-第六章

已剪辑自: https://bbs.huaweicloud.com/blogs/101043

模式介绍:适配系列模式

​ 在《设计模式》中提出的23种模式,其中适配器模式(Adapter),装饰者模式(Decorator),代理模式(Proxy)都属于原始功能到目标功能之间的桥梁。

在面向对象里的设计里,这3种由于类的继承等面向对象特性,有比较明显的不同。在C语言里这些区别明显减弱,而且在实际的开发中,也没有这么多约束,所以统称为适配系列模式。

以下引用设计模式的一些定义和说明。

适配器模式Adapter

将两个不同接口的类来进行通信,在不修改这两个类的前提下用中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将一个类的接口,转换成客户期望的另一个接口。

装饰者模式Decorator

如果通过继承和组合的方式来给一个对象添加行为,会造成关系复杂,过多的功能造成类爆炸。装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。

代理模式Proxy

代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的,同时也在一定程度上面减少了系统的耦合度。

共同点和区别

​ 这3个模式听起来都很类似,都是在原对象上增加或者强化一些功能。

按面向对象里的说法,代理模式和被代理的对象拥有完全相同的接口,不增加和强化功能。比如给一个取火车票的函数写一个代理函数,虽然接口一样,但是增加了取票人合法性,票的合理性等功能。把这个称为不增加和强化,我认为都是语义上的,而不是技术上的。

而且由于与C语言不是类继承,接口是可以改的。比如取*** 函数增加一个时间入参功能,判断是不是合理的取票时间,如果根据接口改变就称这个不是代理模式了,那设计模式这个语义也没有什么意义了。从内核实现的观察者模式实现就知道,C语言里是灵活的,不因为改变了某些不影响架构的细节就换了一个模式。

​ 装饰模式不改变接口,但是会强化一些离原功能远一些的功能。但是同样由于C语言的灵活性,多远算远,怎么样才算强化都是难以量化的,就算改了一个入参强化,也不能说这个和装饰模式差别很大,况且由于C语言不存在类继承保持父类接口这回事,所以装饰模式也没有什么特殊的。

​ 适配器模式将两个不同接口的类来进行通信,也就是说,接口是不一致的,同上面的分析,在C语言里没有什么实质性区别,只不过对原接口改变多少的程度有差异而已。

适配系列模式实现

​ 没什么特别的,在C里面,适配系列模式是一种隐性的模式,最常见的称呼是封装下接口,开发这就在不知不觉中使用了适配系列模式。下面举一个简单的例子。

原函数样例

也就是被适配和代理的函数

int original_process(int a, int b)

{

​ //do something

​ ret = //xxx

​ return ret;

}

传统适配模式函数样例

可以看出传统适配模式将(int a, int b)接口适配成了(int c, int d, int e),并且返回值也做了适配。

int adapt_process(int c, int d, int e)

{

int a, b;

int ret;

​ //do something

a = (c + d)%e; //some formula includes c,d,e or some other operation

b = d + e;

​ ret = original_process(a, b) + 1;

​ //return something accroiding to ret

​ return ret;

}

传统装饰者模式函数样例

传统意义上的装饰者模式不会改变接口,但是会增加功能。

int decorator_process(int a, int b)

{

int a1, b1;

​ //do something optimization or other function

a1 = //do something with a or b

b1 = //do something with a or b

​ ret = original_process(a1, b1)

​ //do something more, like optimize

​ //return something accroiding to ret

}

传统代理模式函数样例

传统代理模式既不能改变接口,也不能增加功能,通常只能做一些管控。

int proxy_process(int a, int b)

{

int ret;

​ //check a, b

if(a < 5)

{printf(“error:a should bot less than 5\n”);}

if(b < 0)

{printf(“error:b should >= 0\n”);}

​ ret = original_process(a, b)

return ret;

}

C语言适配系列模式

​ 如上分析,C语言在实际开发中,没有这么多传统模式限制,可以混合所有适配类模式的功能。

int c_adapt_process(char c, char d, char e)

{

int a, b;

int ret;

​ //check c, d

if(c < 5)

{printf(“error:c should bot less than 5\n”);}

if(d < 0)

{printf(“error:d should >= 0\n”);}

a = (c + d)%e; //some formula includes c,d,e or some others operation

b = d + e;

//do something optimization or other function

​ ret = original_process(a, b) + 1;

​ //return something accroiding to ret

​ return ret;

}

模式实现总结

​ 非常常用的设计模式,使用中都是自然而然的,没有想到其实也是几种退化的面向对象设计模式。

设计模式的C语言应用-建造者模式-第七章

已剪辑自: https://bbs.huaweicloud.com/blogs/100432

模式介绍

​ 建造者模式将复杂产品的构建过程封装分解在不同的方法中,使得创建过程非常清晰。它隔离了复杂产品 对象的创建和使用,使得相同的创建过程能够创建不同的产品。若几个 产品之间存在较大的差异,则不适用建造者模式

​ 面向对象里的建造者模式,对于C语言,就无需这么复杂了。

设计模式的C语言应用_第8张图片

比如用C构建一个网络数据包,需要构建Dmac域,smac域,长度域,IP等各层头。如果代码写在一个函数里,那么会很长很复杂。可以把Dmac域,smac域,长度域合并到二层头的构建函数,ip的各个域写到一个函数。

建造者模式实现

原始函数样例

struct packet

{

int part_a[4];

int part_b[4];

int part_c[4];

}

void original_func(struct packet *pkt)

{

pkt->part_a[0] = 1;

pkt->part_a[1] = 3;

pkt->part_a[2] = 4;

pkt->part_a[3] = 7;

pkt->part_b[0] = 3;

pkt->part_b[1] = 5;

pkt->part_b[2] = 7;

pkt->part_b[3] = 9;

​ pkt->part_c[0] = 4;

​ pkt->part_c[1] = 5;

​ pkt->part_c[2] = 1;

​ pkt->part_c[3] = 2;

}

建造者模式函数样例

下面的例子很简单,但是实际上用建造者模式是因为各部分的建造函数可以复用,建造出某类型产品的不同的具体实例,同时有利于模块化,避免过长的函数。

void builder_parta(struct packet *pkt)

{

pkt->part_a[0] = 1;

pkt->part_a[1] = 3;

pkt->part_a[2] = 4;

pkt->part_a[3] = 7;

}

void builder_partb(struct packet *pkt)

{

pkt->part_b[0] = 3;

pkt->part_b[1] = 5;

pkt->part_b[2] = 7;

pkt->part_b[3] = 9;

}

void builder_partc(struct packet *pkt)

{

​ pkt->part_c[0] = 4;

​ pkt->part_c[1] = 5;

​ pkt->part_c[2] = 1;

​ pkt->part_c[3] = 2;

}

void builder_func(struct packet *pkt)

{

builder_parta(pkt);

builder_partb(pkt);

builder_partc(pkt);

}

模板方法模式(Template Method)介绍

​ 如果做某几件事情的主要方法都差不多,仅有小部分的不同,那么相同的部分可以提取出来成为父类,不同的部分可以做成不同的子类。这种思路叫做模板方法模式。

image.png

图表 1建造者模式和模板方法模式对比

​ 和建造者模式切分构建和流程方法类似,模板方法模式首先也要对方法进行切分。建造者模式切分的每个部分都是一个没有继承关系的类,组合起来作为builder类。而模板方法模式的父类实现了相同部分的方法,而子类扩展实现不同的方法。模板方法模式的子类包含了整套的方法。

​ 对于C语言,由于不存在继承,所以建造者方法和模板方法模式就可以混用。每一个part可以成为函数,组合起来成为builder函数,而替换不同的part,可以变成不同的builder。

模式实现总结

​ 对于C语言开发者来说,通常是在构造复杂的数据结构时候会想到建造者模式。比如核间通信消息,进程间通信消息。ISP里面的request消息,就隐性用了建造者模式。

​ 自行编写伪数据包发送代码也非常适合用建造者模式。把数据包的不同层的头部信息用不同的函数进行构造。

设计模式的C语言应用-外观模式-第八章

已剪辑自: https://bbs.huaweicloud.com/blogs/106799

外观模式(Facade)介绍

​ 外观模式也叫门面模式

外观模式就是提供一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。如下图,是使用外观模式后将子系统的使用变得更加简单。

设计模式的C语言应用_第9张图片

说起来比较复杂,实际上在日常生活中经常能遇到。比如部门安排出差,需要去淘宝买出差洗漱用品,一共有4中东西要选择。洗面奶,牙膏,洗发水,润肤露。消费者可以自己一个买,也可以买套餐。把上图大方框外的小方块看成不同的消费者,里面的小方块看成洗面奶之类。Façade就是店家提供的不同套餐选择的那个接口。

外观模式实现

内部原始接口

#define BIG_BOTTLE 0

#define MID_BOTTLE 1

#define SMALL_BOTTLE 2

char *toiletries_type[] =

{

“big bottle”,

“middle bottle”

“small bottle”

};

void buy_face_soap(int type)

{

printf(“buy %s face soap\n”, toiletries_type[type]);

}

void buy_shampoo(int type)

{

printf(“buy %s shampoo\n”, toiletries_type[type]);

}

void buy_toothpaste(int type)

{

printf(“buy %s toothpaste\n”, toiletries_type[type]);

}

void buy_bodylotion(int type)

{

printf(“buy %s body wash\n”, toiletries_type[type]);

}

原始函数样例

在普通模式里,外部client可以随意调用内部的接口。

void client_buy_normal()

{

buy_face_soap(MID_BOTTLE);

buy_face_soap(MID_BOTTLE);

buy_shampoo(BIG_BOTTLE);

buy_shampoo(MID_BOTTLE);

buy_toothpaste(SMALL_BOTTLE);

buy_toothpaste(MID_BOTTLE);

buy_bodylotion(BIG_BOTTLE);

}

外观模式函数样例

实现了两个封装好的接口pack_for_woman和pack_for_man。不允许外部client调用内部接口,只允许调用封装好的facade接口pack_for_woman和pack_for_man。

void pack_for_woman()

{

buy_face_soap(MID_BOTTLE);

buy_shampoo(BIG_BOTTLE);

buy_toothpaste(SMALL_BOTTLE);

buy_bodylotion(BIG_BOTTLE);

}

void pack_for_man()

{

buy_face_soap(MID_BOTTLE);

buy_shampoo(MID_BOTTLE);

buy_toothpaste(MID_BOTTLE);

}

void client_buy_with_facade()

{

pack_for_woman();

pack_for_man();

}

在原始函数和外观模式实现里,买的东西都一样。

模式实现总结

引入外观模式,是客户对子系统的使用变得简单了,减少了与子系统的关联对象,实现了子系统与客户之间的松耦合关系。但是,灵活性变差了,客户不能自由选择子系统内部的接口,只能使用封装好的一套接口。

实际生活里,客户并不是需要选子系统内部接口。比如DIY电脑就相当于普通的模式,消费者会买cpu,主板等各个组件。而品牌PC就类似于门面模式,只能购买特定个型号。对于手机就更不用说了,没有消费者能买soc,flash来装手机。

设计模式的C语言应用-访问者模式-第九章

已剪辑自: https://bbs.huaweicloud.com/blogs/101612

访问者模式(Visitor)介绍

​ 把对象数据和操作分离,使操作可以独立演化。一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。访问者模式是适用于那些数据结构比较稳定的模式。这个算是在C里面退化的不是那么厉害的一种模式思想, 或者说这种方法和C实现天然结合而不成为模式。因为C里面本来就很少将数据和访问方法封装在一起,数据的组织形式是数据结构的范畴,访问函数是代码流程设计的范畴。

面向对象实现的访问者模式

以下是用面向对象实现的访问者模式。两个帽子子类实例的不包含特有访问方法,也就是说,设计上只是想把这两个子类当作数据。帽子有两种访问方法price_show和size_show。

​ 这里变化的因素有两个,不同的帽子和不同的访问方法(price和size)。解决不同帽子的方法是增加不同的帽子具体类。解决不同的访问方法是不同的具体访问方法子类price_show和size_show。这就是所谓的把对象数据和操作分离。

​ 可以看出,虽然是对象数据和操作分离,但是数据的父类也必须提供了统一的访问接口,只不过不需要在子类里有特定的访问接口。

如果增加别的访问方法,就继续增加访问抽象父类和子类,并且修改帽子的抽象父类。

public abstract class Visitor {

public abstract void visitor(capA a);

public abstract void visitor(capB b);

}

public class price_show extends Visitor{

public int visitor(capA a) {

​ return a.getPrice();

}

public int visitor(capB b) {

​ return b.getPrice();

}

}

public class size_show extends Visitor{

public int visitor(capA a) {

​ return a.getsize();

}

public int visitor(capB b) {

​ return b.getsize();

}

}

public abstract class cap {

protected int size;

protected int price;

public cap (int size,int price){

​ this.size = size;

​ this.price = price;

}

public int getsize() {

​ return this.size;

}

public int getPrice() {

​ return this.price;

}

public abstract void accept(Visitor visitor);

}

public class capA extends cap{

public capA(int size, int price) {

​ super(size, price);

}

public void accept(Visitor visitor) {

​ visitor.visitor(this);

}

}

public class capB extends cap{

public capB(int size, int price) {

​ super(size, price);

}

public void accept(Visitor visitor) {

​ visitor.visitor(this);

}

}

public class shop {

List list = new ArrayList();

public void accept(Visitor visitor){

​ Iterator iterator = list.iterator();

​ while (iterator.hasNext()) {

​ iterator.next().accept(visitor);

​ }

}

public void addcap(cap cap){

​ list.add(cap);

}

public void removecap(cap cap){

​ list.remove(cap);

}

}

public class Client {

public static void main(int[] args) {

​ cap a = new capA(38, 201);

​ cap b = new capB(41, 95);

​ shop shop = new shop();

​ shop.addcap(a);

​ shop.addcap(b);

​ Visitor price_show = new price_show();

​ Visitor size_show = new size_show();

​ shop.accept(price_show);

​ shop.accept(size_show);

}

}

C实现的访问者模式

struct cap

{

int size;

int price;

}

struct cap shop[] =

{

[0] = {

​ .size = 38,

​ .price = 201

},

[1] = {

​ .size = 41,

​ .price = 95

},

}

int client()

{

int i;

for(i =0; i++; i< ARRAY_SIZE(shop))

{

​ printf(“cap %d size %d”, i, shop[i].size);

}

for(i =0; i++; i< ARRAY_SIZE(shop))

{

​ printf(“cap %d price %d”, i, shop[i].price);

}

}

对比两者的代码,可以明显看出,C实现里从来不在用于存放数据的结构体里放函数。也就是说,面向对象里数据的抽象父类提供的getsize子类的方法是不存在的。

如果想要访问数据,根据数据的组织形式,直接操作就可以。从这个意义上讲,如果C语言实现里想要增加访问方法,根本不用修改数据相关的东西,这个比向对象里还需要修改数据的父类更为纯粹。这个才是更干净的访问者模式,也可以换个角度说,C里面根本不存在访问者模式

设计模式的C语言应用-非典型模式-第十章

已剪辑自: https://bbs.huaweicloud.com/blogs/101513

上一章为止,C语言里显性和隐性的设计模式都已经介绍完了。

非典型模式章节开始介绍23种设计模式里在C语言退化的,不适用的。这些模式在使用中很难想到其实也是一种设计模式,代码的实现也没有什么特点。开发人员在设计时并不需要特意考虑这些模式,随遇而安即可。

​ 了解这些模式,只是加强一下开发人员的全局观,在开发中能润物无声的应用和衍生。

原型模式(Prototype) 介绍

​ 某些对象的结构比较复杂,但是我们又需要频繁的使用它们。通过复制这些原型创建新的对象。

​ 比如写一个内核发送大量数据包的程序,比较好的办法就是做好一个标准数据包,然后不停的复制,再发送出去。

深复制和浅复制

​ 熟悉内核网络实现的人一定知道,如果按照复制数据包的做法,最好的办法是使用skb_clone函数而不是skb_copy函数。原因是,内核里表示数据包由skb和data部分组成。Skb只是管理的句柄,而data是数据包的真实数据。如果是同样的数据包发送,只需要复制句柄,保持对data的引用就可以。这个就是浅复制。

设计模式的C语言应用_第10张图片

图表 1浅复制skb_clone

可以看出,浅复制skb_clone只是复制了sk_buff句柄,而下面的packet data storage指向的是同一个。

​ 如果发送数据包要按照顺序变换数据包IP头的序列号和校验码。那么就需要同时复制句柄和data作为新的数据包,使用skb_copy函数。

设计模式的C语言应用_第11张图片

图表 2深复制skb_copy

可以看出,深复制skb_copy同时复制了sk_buff句柄和下面的packet data storage。

单例模式(Singleton) 介绍

​ 单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点。在C语言的实现中,最常见的使用方法有且仅有一个:作为全局变量存在的控制数据。在嵌入式驱动开发中经常使用,比如driver定义,device定义。例子如下。

​ static struct platform_driver dwc3_exynos_driver = {

​ .probe = dwc3_exynos_probe,

​ .remove = __devexit_p(dwc3_exynos_remove),

​ .driver = {

​ .name = “exynos-dwc3”,

​ },

};

​ 在ISP里用一个全局变量保存camera的属性,都可以算是单例模式。

但是C语言里,由于这种方法太常见,除了一个全局变量,也没有别的代码,所以从来没有人认为这是一个设计模式。

组合模式(Composite)

​ 组合模式也称为合成模式,有时候又成为部分-整体(part-whole)模式。

先看看在面向对象里的定义。组合模式将对象组织到树结构里,可以用来描述整体和部分的关联。合成模式可以使客户端将单纯元素和符合元素同等看到。

用C语言翻译上面的话,就是把数据按照树结构组织起来,访问的时候能将叶子节点和中间节点同等处理,用的是递归的方法。

剩下的,大家参考数据结构的书吧。在C里面,这东西算数据结构,不算设计模式。

享元模式(Flyweight)

​ 享元模式以共享的方式支持大量的细粒度对象。享元模式把对象属性分为内部状态和外部状态。内部状态是对象本质属性,不可改变。外部的可以随着环境改变。

​ 享元模式最常见的在编辑器的实现里。如字母a,内部状态就是a本身,外部状态是位置,字体。这样就能共享一个a对象,在编辑器里实现不同的表现。

​ 在C里,并没有发现这个模式有什么应用的场景。如果有人知道,请留言。

image.png

工厂模式和抽象工厂模式(Factory)

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。

对于C开发者,工厂模式更通俗一点理解就是,客户并不直接malloc一个对象A或者B,而是调用一个工厂函数,输入A或者B的类型。由工厂函数返回对象A或者B

对于C语言,没有类的约束,创建对象也不过是一个malloc。靠输入不同的参数决定产生不同的对象,谈不上模式。实际应用也很少。

抽象工厂模式用来成体系的对象,C语言也用不上。

工厂模式样例代码

cap_factory生产不同的帽子

#define RED_TYPE 0x01

#define BLUE_TYPE 0x02

struct cap

{

int type;

void (process_cap)(struct cap);

};

void process_red_cap(struct cap* pcap)

{

printf(“This is a red cap!\n”);

}

void process_blue_cap(struct cap* pcap)

{

printf(“This is a blue cap!\n”);

}

struct cap* cap_factory(int type)

{

struct cap* pcap = (struct cap*)malloc(sizeof(struct cap));

memset(pcap, 0, sizeof(struct cap));

if(RED_TYPE == type)

{

​ pcap->type == RED_TYPE;

​ pcap->process_cap = process_red_cap;

}

else

{

​ pcap->type == BLUE_TYPE;

​ pcap->process_cap = process_blue_cap;

}

return pcap;

}

解释器模式(Interpreter)介绍

​ 解释器模式就是定义语言的语法,并且建立一个解释器来解释该语言中的句子。比如用C写一个xml语言的解析器,这个开发就算是解释器模式。显然,C里面这个算是一个开发项目,不是设计模式。

​ 另外一个常见的使用场景是C语言学习课程里,编写一个计算器。需要将用户输入的算式的字符串解析并表达出来。但是编程和语法上也没有什么特殊的。

​ 在正常项目里也许有少量的地方根据字符串的语法含义做一些处理,但是场景非常少,用有意义的字符串做为控制参数显然不如用整数或者结构体。

迭代器模式(Iterator) 介绍

​ 迭代器模式是将迭代元素的责任交给迭代器,而不是对象,可以在不需要知道该聚合对象的内部结构就可以实现该聚合对象的迭代。

​ 比如一组元素,可能是链表组成的,可能是树状结构。可以写一个迭代器函数,屏蔽具体元素组织结构的差异,遍历全部的元素,那么就算迭代器模式。显然,C里面并不太需要这种方式。一个元素的组织结构,是在设计时综合考虑效率,内存空间,场景,就已经确定了。如果一定要实现迭代器,最多也就是封装一个函数。在C实现里,这种也不算是设计模式,需求也不明显。

备忘录模式(Memento)介绍

​ 备忘录模式又叫做快照模式(shapshot)。在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

​ 在vmware虚拟机上用过linux的人一定对快照功能非常清楚。在android之前,一般的嵌入式linux的代码都不多,在性能强的vmware虚拟机上安装linux编译,调试就能完成绝大部分任务。对于写内核程序的开发者而言,可能一点错误就导致内核崩溃,需要重启。所以一般会在调试之前用vmware保存一个snapshot。崩溃之后不用重启,直接恢复快照(snapshot)就原地满血复活, 回到快照那一刻的状态。

​ 对于C语言,如果有的过程数据需要保留,那么小的用临时变量,大的可以分配一片内存。一般规模和需求都很小。也没有一个特别的操作可以称为模式。这种使用只在低功耗,寄存器保存恢复时使用,但是实现上没有任何特别的代码,都是整片的赋值。由于寄存器的特殊性,基本上不用memcpy,曾经发生过bug,因为嵌入式系统下memcpy未必是按照通常理解的word赋值,而是替换库文件的memcpy函数改写之后的。

策略模式(Strategy)介绍

​ 开发中可以通过很多种不同的方式来完成一件事情,这里的每一种方式都可以称作为一种策略。比如计算ISP的自动曝光,有两种不同的算法,也可以称为不同的策略。策略模式就是可以算法和策略但是外界不需要修改的设计模式。很显然,只要算法和其他组件间的接口保持一致就可以。替换自动曝光算法,外部不修改代码就可以运行。

​ 再比如,有一个算法比较复杂,还在开发的过程中,主流程里很可能用相同的接口进行打桩。桩和实际算法在运行时可以动态用函数指针动态配置,那么这个不算通常意义的算法更替,但也算是用到的策略模式的思想。但是很显然,对于C语言,这种肯定不算是一种明显的设计模式,而是一种函数或者功能替换,也没有特别的语法和架构支持,最多也就是使用函数指针方便替换策略。

​ 很多帖子和书都拿替换排序算法为例,

中介者模式(Mediator)介绍

​ 中介者模式也翻译成调停者模式,就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用。

image.png

图表 1 相互引用的模型

image.png

图表 2中介者模式

​ 在C里面,这个也很难算是一种设计模式,只能算是一种思路吧。中介者模式和门面模式看起来有点像,但差别还是很明显的。门面模式只是为了封装系统内细节,对外提供套餐服务。而中介者模式并不提供套餐,而是隔离,协调各对象的行为。现实中,项目经理就担任着中介者的角色。

桥接模式(Bridge)介绍

如果某个系统能够从多个角度来进行分类,且每一种分类都可能会变化,那么我们需要做的就是讲这多个角度分离出来,使得他们能独立变化。桥接模式将继承关系转化成关联关系。

更文艺的说法是,将”抽象化和实现化解耦”。 抽象化在面向对象就是将对象共同的性质抽取出去而形成类的过程。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

Java与模式书上举了飞机的例子。飞机有两个属性,制造商(波音和空客)和用途(运输机和客机)。书上把飞机作为抽象类,制造商作为具体实现类。不过如果再增加一个旋翼和固定翼的属性呢?桥梁模式没法解决超过2个维度的分类集成问题。

C就没有真正意义上的继承,所以我觉得,纯粹用面向对象继承改关联的思路看桥接模式比较局限,C不存在这个模式的需求。如果有不同的因素,那多维数组是一个选择。

你可能感兴趣的:(设计模式,设计模式,c语言,java)