本书的原著为:《Design Patterns for Embedded Systems in C ——An Embedded Software Engineering Toolkit 》,讲解的是嵌入式系统设计模式,是一本不可多得的好书。
本系列描述我对书中内容的理解。
- 实现类的最简单方法是使用文件作为封装边界:公共变量和函数声明在
.h
文件中,函数实现和私有变量在.c
文件中。- 一种更灵活的方法是使用文件中的结构体表示类。与结构体位于同一文件中的函数定义类的操作。为了确保函数能够访问正确的对象数据,我们需要传入一个
me
指针。- 在结构体本身中嵌入函数指针,这可以实现子类的继承。
以上就是本书中用 C 语言模拟类的三种方法。这三种方法复杂度逐渐增加,不要因为第一种方法简单就认为它用处不大,不是这样的。实际上,这三种方法在 C 编程中都十分常用,尤其在编写模块化程序时,它们甚至与类无关,只是恰好可以模拟类的行为。
这三种方法都以文件作为封装边界,只是后两种没有明确指出。封装的目的在于降低整体复杂度。在编程界,封装是一种重要的思想。《UNIX 哲学》告诉我们:“要编制复杂软件而又不至于一败涂地的唯一方法就是降低其整体复杂度——用清晰的接口把若干简单的模块组合成一个复杂软件。如此一来,多数问题只会局限于某个局部,那么就还有希望对局部进行改动而不至牵动全身。”
第 1 种方法中将公共变量声明在 .h
文件,我自己并不认同这种方法,我推荐使用函数封装全局变量,对外只提供函数,这实际上对外隐藏了数据,因此封装性更好。此外,这样更灵活,有利于以后的扩展。这怎么理解呢?用函数封装一下后,函数能做的事情,比使用变量要多的多,你可以在函数中轻松的修改或扩展功能,而无需修改使用该函数(原来这里是变量)的所有代码
许久之前,我认为这样影响效率。直到我意识到,我手上的这颗 M3 芯片,1ms 可以执行110000 条单周期指令,或者换句话,1ms 可以执行 220KB 的代码。现代的单片机速度今非昔比了,效率固然重要,但可扩展性有些时候更重要。何况,对现代的编译器来说,用函数封装变量,真的会影响效率吗?还真不一定。
对于第一种方法,我并不赞同将公共变量声明在 .h 文件中。我主张使用函数来封装全局变量,仅对外提供函数接口。这样能更好地隐藏数据,从而提高封装性。此外,这种做法更灵活,有利于未来的扩展。这怎么理解呢?使用函数封装后,函数能做的事情,比变量要多的多。你可以在函数内部轻松地修改或扩展功能,而无需修改使用该函数的所有代码。
许久之前,我认为这样影响效率。直到我意识到,我手上的这颗 M3 芯片,1ms 可以执行110000 条单周期指令,或者换句话,1ms 可以执行 220KB 的代码。时至今日,单片机的速度今非昔比了!效率固然重要,但可扩展性有些时候更重要。何况,对现代的编译器来说,用函数封装变量,真的会影响效率吗?还真不一定。
在标准 C 编程中,复杂的算法仍然可以嵌入类中,但这些类通常是
单例
(singletons),这意味着应用程序中只有一个类的实例
(instance)。例如,单例 Printer 类可能具有 currentPrinter 等变量和 print() 等操作,但应用程序只有一个实例。即使只有一个实例在运行,将类使用的数据与作用于该数据上的操作封装在一起仍然是有益的。在其他情况下,通常以数据为中心(与以算法或服务为中心相反)的类会有多个实例。
这段话描述了一些设计模式:单例和多实例。即使不采用面向对象的思想,了解这些知识也是非常重要的。在模块化设计中,我们经常会遇到单一实例模块(single-instance module)和多实例模块(multiple-instance module),它们在封装模块的数据方式上,有着本质的区别。
对于单一实例模块,其数据以静态形式存在于 .c
文件中,并通过 .h
文件中提供的接口来访问这些数据。由于使用了静态数据,这种方法适用于只需要处理一套数据的模块。这种方式简单,简单意味着可靠。
当一个模块要为不同的客户管理不同的数据时,要使用多实例模块。在多实例模块中,必须要初始化数据结构并把它传回给客户以保持其上下文。比如一个环形缓冲区模块。
在面向对象编程中,单例是一种设计模式,其中确保一个类只有一个实例,并提供一个全局访问点来访问该实例。假如 Printer 类是一个单例,这意味着在整个应用程序中,只有一个 Printer 类的实例存在。
类生来具有一些特殊的操作:构造函数和析构函数。
在面向对象编程中,构造函数
和 析构函数
是两个非常重要的成员函数:
Xxxx_Init()
。此外,在本书中,创建对象的函数命名为 Xxxx_Create()
,在这个函数中调用构造函数 Xxxx_Init()
。Xxxx_Cleanup()
。此外,在本书中,销毁对象的函数命名为 Xxxx_Destroy()
,在这个函数中调用析构函数 Xxxx_Cleanup()
。