先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
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)
观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。
图表 1观察者模式流程图
观察者模式在C语言里也是实现形式非常明显的模式。逻辑上和责任链模式最相近的一个设计模式为观察者模式。观察者模式和责任链模式的最大的差别在于,事件会被通知到每一个handler,而不是逐级处理。也不存在优先级的说法,也不会出现事件没有处理需要异常函数收尾。一个Observer是否注册和执行不应该影响其他的Observer。而在责任链模式上,前面的责任handler在传递给下一个handler时,是可以改变事件相关变量。
但是在C语言实现上,观察者模式的handler绝大部分也是按照链表来组织的,在代码执行上,实际上相当于遍历链表。和责任链模式的区别在于每个handler没有优先级,没有权力决定是否停止遍历,最后事件也不需要被handler消费掉,也就是没有异常函数。
所以从C语言代码实现上讲,观察者模式可以看作责任链模式的特例。
\1. 无优先级
\2. 不能修改随事件而来的变量。比如在netfilter使用责任链模式就修改了随事件而来的数据包。
\3. 每个handler/observer只能无条件把事件传给observer链表的下一个节点。
图表 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表示执行命令的实体,这个和面向对象的命令模式里的含义不一样。
图表 1 C语言命令模式示意图
图表 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构建一个网络数据包,需要构建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)介绍
如果做某几件事情的主要方法都差不多,仅有小部分的不同,那么相同的部分可以提取出来成为父类,不同的部分可以做成不同的子类。这种思路叫做模板方法模式。
图表 1建造者模式和模板方法模式对比
和建造者模式切分构建和流程方法类似,模板方法模式首先也要对方法进行切分。建造者模式切分的每个部分都是一个没有继承关系的类,组合起来作为builder类。而模板方法模式的父类实现了相同部分的方法,而子类扩展实现不同的方法。模板方法模式的子类包含了整套的方法。
对于C语言,由于不存在继承,所以建造者方法和模板方法模式就可以混用。每一个part可以成为函数,组合起来成为builder函数,而替换不同的part,可以变成不同的builder。
模式实现总结
对于C语言开发者来说,通常是在构造复杂的数据结构时候会想到建造者模式。比如核间通信消息,进程间通信消息。ISP里面的request消息,就隐性用了建造者模式。
自行编写伪数据包发送代码也非常适合用建造者模式。把数据包的不同层的头部信息用不同的函数进行构造。
设计模式的C语言应用-外观模式-第八章
已剪辑自: https://bbs.huaweicloud.com/blogs/106799
外观模式(Facade)介绍
外观模式也叫门面模式
外观模式就是提供一个统一的接口,用来访问子系统中的一群接口。外观模式定义了一个高层接口,让子系统更容易使用。如下图,是使用外观模式后将子系统的使用变得更加简单。
说起来比较复杂,实际上在日常生活中经常能遇到。比如部门安排出差,需要去淘宝买出差洗漱用品,一共有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的引用就可以。这个就是浅复制。
图表 1浅复制skb_clone
可以看出,浅复制skb_clone只是复制了sk_buff句柄,而下面的packet data storage指向的是同一个。
如果发送数据包要按照顺序变换数据包IP头的序列号和校验码。那么就需要同时复制句柄和data作为新的数据包,使用skb_copy函数。
图表 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),
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
nt()
{
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的引用就可以。这个就是浅复制。
图表 1浅复制skb_clone
可以看出,浅复制skb_clone只是复制了sk_buff句柄,而下面的packet data storage指向的是同一个。
如果发送数据包要按照顺序变换数据包IP头的序列号和校验码。那么就需要同时复制句柄和data作为新的数据包,使用skb_copy函数。
图表 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),
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-BJmF2KJt-1713244482588)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!