24

根据我的经验,我经常在 Java 等面向对象语言中看到一些设计模式,例如访问者模式、策略模式……但我在 C 等过程语言中没有看到太多模式……我想知道那些程序语言中存在模式吗?

4

2 回答 2

45

过程语言确实有设计模式。但是由于程序方法通常被忽略,而倾向于基于类的 OOP,因此它们并未得到广泛认可。

我用 C 开发高性能软件,并且有几种重复出现的模式。因此,我将提供一些我经常看到的模式的见解。

把手

这就是在过程编程中进行封装的方式。构造函数不返回结构或对象。但是一个句柄:它通常是一个不透明的指针或只是一个整数。你不能用它做任何有趣的事情,因为它只是一个数字。细节完全隐藏。但是您可以将此句柄传递给处理它的函数:

例子:

  • 在 Windows 上,CreateWindow函数返回一个 HWND。这是一个窗口句柄,可以传递给其他函数,如 ShowWindow、DestroyWindow 等。
  • 在 Linux 上是open系统调用。返回just和int。这是一个文件句柄。

上下文

在过程语言中,对象通常称为上下文。上下文是一个包含某些系统状态的结构,就像对象的成员一样。在 OOP 中,您编写object.method(parameter). 在过程编程中,您编写function(addressOfContext, parameter). 内部函数直接使用上下文结构,而公共函数仅使用句柄,实现将其解析为实际的上下文结构。

回调

或函数指针。函数的用户传递他的函数地址以向系统添加自定义行为。这就是多态性在过程编程中的实现方式。这允许编写通用函数。

一个值得注意的例子是qsort C 函数。这需要元素数组的地址。获取一个元素的大小以及数组中有多少元素以及执行比较的比较器函数。这是完全通用的实现,允许对各种数据进行排序。

设置结构

当一个函数可以通过多种方式进行参数化时。通常使用设置结构。规范通常要求这些结构默认填充为零,并且只填充相关成员。如果某些成员是互斥的,则将它们置于联合中。这种设置结构的典型示例是来自 WinAPI的WNDCLASS 。

可变大小数据

好吧,这是一种 C 模式,而不是一般的设计模式。有时对象可能包含任意大小的二进制有效负载。这种模式通常发生在从包含多种类型数据块的二进制文件中读取数据时。这是由这样的结构完成的。

typedef struct
{
    int someData;
    int otherData;
    int nPayloadLength;
    unsigned char payload[1];
} VariableSized;

在代码中完成了以下操作:

VariableSized *vs = malloc(sizeof(VariableSized) + extraLength);

这会分配比结构更大的内存,从而为可变长度的有效负载留出空间。然后可以通过例如访问其第5个字节。vs->payload[4].

这样做的好处是可以在一次free调用中释放整个对象。并且保证它在内存中有一个连续的块。因此,它比在堆中的其他地方分配相应的缓冲区更好地利用缓存。

OOP 设计模式的过程对应物

OOP 模式在过程语言中从不以它们的名字命名。所以我只能在这里猜测。

创建模式

  • 抽象工厂:抽象工厂通常是单例的。在这种情况下,根本不使用此模式,而是使用条件编译。否则设置提供创建功能的结构。
  • Builder:使用设置结构。
  • 工厂方法:回调用于创建。
  • 延迟初始化:在 C++ 中,静态局部变量用于此目的。在 C 语言中,您可以if (!initialized) { initialize(); initialized = 1; }在对性能不重要的地方使用该模式。对于性能关键代码,根本不使用延迟加载。用户必须找到一个地方来初始化上下文。
  • 原型:在程序世界中,我们只需将句柄返回给库存对象。WinAPI 中的GetStockObject函数就是一个例子。对于可变对象,出于性能原因,通常使用写时复制机制。
  • 单例:只需编写顶级函数(并在绝对需要全局状态时使用全局变量)。

结构图案

  • 适配器外观:在现有接口上构建另一个接口的模式。只需新函数将调用旧函数和其他函数。
  • Bridge:对具体实现的回调以 setup struct 的形式提供。
  • Composite:顶级函数用于指定它应该操作的父节点的句柄。
  • Decorator:装饰行为以回调的形式提供。或者为接收各种消息并决定是否处理它们的所有可能的装饰提供一个事件处理程序回调(例如 WinAPI 中的窗口过程)。
  • Flyweight:只读二进制数据,用于打包在结构和数组中。
  • 代理:与 OOP 中的几乎相同,但没有类。

行为模式

  • 责任链:循环遍历的回调数组或链表。规范描述了回调应该如何表明他们处理了导致循环中断的请求。
  • 命令:命令是包含 adoundo回调的结构。这些回调通常需要某种上下文来操作。并维护命令数组以执行撤消。
  • 解释器:编译器/解析器/解释器是使用 lex 和 yacc 编写或生成的。
  • Iterator:使用句柄,否则相同。出于 C 中的性能原因,我们经常坚持使用数组。
  • Mediator:通常通过使用一些消息调度机制和消息循环和事件处理程序来实现。
  • Memento:与 OOP 中的相同,但没有类。
  • 观察者:与责任链相同,但循环不会中断。一个例子是atexit
  • 状态:由二维调度表实现,将当前状态和请求的操作映射到一个函数中。(在稀疏的情况下,只使用 ifs。)
  • 策略:这是回调的基本用例。
  • 模板方法:通常框架让用户为某些函数提供自己的回调。库通常提供一种使用自定义内存分配功能的方法,该功能提供自定义mallocfree.
  • 访问者:通过使用回调的多维数组实现,通常在开始时填充 NULL(对于默认行为),并填充在每个类型对的主初始化代码中。
于 2014-12-30T16:44:42.810 回答
1

《设计模式:可重用的面向对象软件的元素》一书是一本具有里程碑意义的书,它将设计模式的注意力带到了计算机编程、设计和架构的实践中。当时占主导地位的编程范式是面向对象的软件开发,而这本书显然是针对这种范式的,而不是针对其他范式的。尽管您可以争辩说本书中的一些设计模式适用于其他范式,但这并不是本书的重点。因此,在设计师和程序员中流行的是那本书中概述的一组设计模式。从那时起,其他作者、博主和其他网站也记录了其他内容。毫无疑问,已经在各种网站上描述了适用于过程语言的设计模式,但是,就像我说的,当编程社区谈到设计模式时,他们大多指的是那本书中概述的模式。我知道这不是一个真正的答案,因为我没有任何记录在案的程序语言模式,我敢肯定,肯定有一些。我想也许我会说明那本书的重要性,以及它最初针对的范式。

于 2012-05-08T01:15:07.983 回答