设计模式的C语言应用_c设计模式及其应用场景分析,2024年最新深入浅出

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的注册

事件触发的处理函数

模式实现总结

模式介绍

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

image.png

图表 1责任链模式流程图

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

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

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

image.png

图表 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)

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

image.png

图表 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表示执行命令的实体,这个和面向对象的命令模式里的含义不一样。

image.png

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

image.png

图表 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语言,就无需这么复杂了。

image.png

比如用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;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

age.png](https://img-blog.csdnimg.cn/img_convert/b114b22dcd0e82269effc5b3dfd3c7ed.png)

比如用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;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-PQl1nOw3-1713078648617)]
[外链图片转存中…(img-qi85KB8i-1713078648618)]
[外链图片转存中…(img-L2bObRtj-1713078648618)]
[外链图片转存中…(img-TRArcFAJ-1713078648619)]
[外链图片转存中…(img-gNJunlyU-1713078648619)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-Lvz3Y618-1713078648619)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值