C 嵌入式系统设计模式 02:结构化编程与面向对象编程

本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。

本系列描述我对书中内容的理解。

结构化编程将软件组织成两个截然不同的方面:数据和行为。面向对象的方法将两者结合起来,让紧密耦合的元素更内聚,并提高内容的封装。C 是结构化语言,但它可以用于开发面向对象的嵌入式系统。

这里提到了 结构化编程面向对象编程 ,C 语言支持结构化编程, C++ 则支持面向对象编程。其实到目前为止,还有第 3 种编程范式,那就是 函数式编程 。这三种编程范式至今都在广泛使用,它们诞生至今已经有相当长的时间了。正如我在《关于编程》中提到的:

  • 1958 年 John Mccarthy 发明了 LISP 语言,函数式编程 范式诞生
  • 1966 年 Ole Johan DahlKriste Nygaard 的论文开创了 面向对象编程 范式
  • 1968 年 Edsger Wybe Dijkstra 论证了 goto 语句的危害,结构化编程 范式诞生

经过了几十年的发展,今天的编程范式与过去完全一样,也是结构化编程 范式、 面向对象编程 范式和 函数式编程 范式,再没有出现新的编程范式。

结构化编程 的核心理念是将复杂的程序问题分解为更小、更容易管理的子问题。“结构化”在这里意味着用只用有限的几种结构(顺序、分支、循环)来构建程序,避免使用 goto 等可能导致程序流程难以跟踪和控制的结构。

结构化编程是由 Edsger Wybe Dijkstra 于 1968 年提出的。Dijkstra 于1930 年出生于荷兰,他很早就发现编程是一件难度很大的工作。一段程序无论复杂与否,都包含了很多细节信息,这远超一个程序员的认知能力范围。而即便是一个小细节的错误,也会造成整个程序出错。他想用数学来证明程序是正确的,而且他也成功了。

Dijkstra 在研究的过程中发现了一个问题:不加限制的 goto 语句导致模块无法拆分成更小的、可被证明的单元,如果禁用 goto,只使用顺序结构、分支结构(if-then-else)和循环结构(do–while)编程,那么程序就可以被数学所证明。于是,结构化编程诞生了。

1968 年,Dijkstra 写了那封著名的,后来发表于 CACM ,标题为《Go To Statement Considered Harmful》的文章。“goto 是有害的”这个观点,这在当时引起了很多程序员的不满。在当时,使用汇编跳转是家常便饭,因此 Dijkstra 的观点掀起了一场长达 10 年的辩论。然而,最后辩论还是平息了,因为事实证明, Dijkstra 是对的。

Dijkstra 的工作是证明一段程序在数学上是正确的,这在实际操作中可太难了。工程界采用的是方法是证明这个程序是错误的,证伪可比证实简单多了,只要能找到一个 BUG,证明就结束了。如果这段程序经过一定的努力无法证伪,我们则认为它在当下是足够正确的。值得注意的是,只有在可证明的程序上,才可以使用这种方法证伪。如果某段程序采用了不加限制的 goto 语句,那么再多的测试也不能证明其正确性。

结构化编程范式促使我们将功能递归的分解为一系列可证明的小函数,然后再编写相关的测试来试图证明这些函数是错的。如果这些测试无法证伪这些函数,那么我们就可以认为这些函数是足够正确的,进而推导整个程序是正确的。

作者说“结构化编程将软件组织成两个截然不同的方面:数据和行为。面向对象的方法将两者结合起来,让紧密耦合的元素更内聚,并提高内容的封装。”这句话我并不是很理解,在我的认识里,无论结构化编程还是面向对象编程,优秀的程序员们总是首先关注数据结构,不仅要考虑如何表示数据,还要考虑如何使用数据。因此无论用什么编程范式,数据和行为都是天然结合起来的,不存在结构化编程将软件组织成两个截然不同的方面。不同的是,结构化编程语言在 文件 中实现数据和行为的结合,面向对象编程在 中实现数据和行为的结合,从本质上讲,这不过是形式上的不同,思想上,它们是一致的。

然后是关于封装。封装就是隐藏不必要的细节,包括数据结构和实现细节。一个最好的例子是文件系统。文件系统的实现非常复杂,有一个很大的数据结构,但这些都被精心隐藏起来了,你只需要操作几个简单的函数 openreadwrite,就可以完成大部分文件操作。模块的头文件就是模块的接口,这里面可以只有 API 函数声明,没有任何数据结构的声明,但 C++ 就办不到,因为技术的限制,C++ 的头文件中必须包含类的声明,这样类中的所有元素都暴露在外了。如果只是谈封装,C 语言是要好于 C++ 的。

“C 可以用于开发面向对象的嵌入式系统”,这是书中的一个结论。我认同这个结论,不是说要使用复杂的宏定义封装 C 语言,模仿 C++ 的语法,而是要深刻理解面向对象的思想,站在更高的层次上编程,首先要理解的,也是最重要的,是 多态

多态 是指多种形态,就是指同一个方法的行为随上下文而异。归根结底,多态不过是函数指针的一种应用。举一个例子:某设备具有多种运行模式:普通模式、访客模式、特权模式…,对于每种模式,显示方式不同、控制逻辑不同、上传的数据也不同。遇到这样的情况,你会下意识用 switch - case 语句解决吗?如果你使用 switch - case 语句解决,那么在显示、控制、上传这些地方,都会有一个 switch - case 语句,不断地重复各种模式。这还没完,当你新增一个模式时,还需要在所有关于模式的 switch 语句处增加 case 语句,来处理新的模式。可以用函数指针代替 switch - case 语句。

首先,我们抽象出一个接口,接口就是一系列函数指针,这些指针规定了每种模式都需要操作的函数原型。然后每种模式都自己实现这些函数。最后将实现的函数动态的绑定到函数指针上,对使用接口的代码而言,它们再也不需要 switch - case 语句区分模式了,它们甚至无需关注模式,因为接口对此做了隐藏。就像在你的电脑上,文件系统是一个接口,我们只管用 write 函数存储参数,而不用理会存储介质,电脑上装的是硬盘就存硬盘上,装的是刻录机就存光盘上。

当理解了面向对象编程思想,理解了 SOLID 设计模式,我写代码的原则变成了只有以下 2 条:

  • 用测试驱动开发编写最简洁的代码。
  • 编写设备无关的代码

怎么算简洁?一个函数,你看上一遍,能拍着胸脯对自己说,它绝不会有 BUG ,这就是简洁。与之相反的,有一个函数,你看上半天,最后只能说,没有看到明显的 BUG,这就是复杂。

什么是设备无关的代码?不依赖具体硬件、不依赖底层实现,就像在应用层存储数据不必知道存哪种介质,只有这样,你才能更方便的更换存储介质。与之相反的,应用层中的函数一路调用下去,可以看到寄存器,那么应用层就跟硬件深度绑定了,任何硬件的改动,都要更改大量的代码。






读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
千金难买知识,但可以买好多奶粉

  • 23
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值