26

In my experiences, I often see some design patterns such as visitor patterns, strategy pattern,... in object oriented languages like Java... But I haven't seen much patterns in procedural languages like C... I wonder if those patterns exist in procedural languages?

Charles
  • 50,943
  • 13
  • 104
  • 142
  • 2
    Please use care when selecting tags for your questions. Tags are *not* keywords. Tagging a question with [tag:design] and [tag:patterns] doesn't mean the same thing as tagging it with [tag:design-patterns]. – Charles May 08 '12 at 01:25
  • Could OOP be considered a design pattern in a procedural language? – savagent Mar 21 '16 at 12:30
  • @savagent It depends on what you consider to be a design pattern, usually it's considered as a re-usable solution to a common problem that can be applied in many different languages, thus not relying on language-specific tools, so most of the time OOP is not a design pattern. But in ANSI-C it could be considered one (see Schreiner's book [OOP with ANSI-C](https://www.cs.rit.edu/~ats/books/ooc.pdf) for a good example). See this question for some useful definitions http://stackoverflow.com/q/4787799/929395 – k3oy Nov 22 '16 at 19:25
  • @savagent OOP is a paradigm, which is much larger than a pattern; but the features of OOP could certainly be patterns. A procedural language could have _encapsulation_, _inheritance_, and _polymorphism_ as patterns. Indeed, C has these patterns. – jaco0646 Oct 12 '18 at 17:54

2 Answers2

51

Procedural languages indeed have design patterns. But since the procedural approach is generally neglected in favor of the class based OOP, they are not widely recognized.

I develop high performance software in C, and there are several recurring patterns. So I'll provide some insight what patterns I see often.

Handles

This is how encapsulation is done in procedural programming. The constructing function doesn't return a struct or an object. But a handle: it's generally an opaque pointer or just an integer. You cannot do absolutely nothing interesting with it because it's just a number. The details are completely hidden. But you can pass this handle to the functions that deal with it:

Examples:

  • On Windows the CreateWindow function returns a HWND. Which is a handle to a window, which can be passed to other functions like ShowWindow, DestroyWindow, etc.
  • On Linux the open system call. Which returns just and int. Which is a file handle.

Contexts

Objects are usually called contexts in procedural language. Context is a struct that contains the state of some system, just like the members of an object. In OOP you write object.method(parameter). In procedural programming you write function(addressOfContext, parameter). The internal functions use the context structure directly, while public functions take a handle only and the implementation resolves it to the actual context structure.

Callbacks

Or function pointers. The user of the function passes the address of his function to add custom behavior to a system. This how polymorphism is done in procedural programming. This allows writing generic functions.

A notable example of this is the qsort C function. This takes the address of an array of elements. Takes how large one element is and how many elements in the array and a comparator function which performs the comparison. That's completely generic implementation and allows sorting all kinds of data.

Setup structs

When a function can be parameterized in lots of ways. Typically a setup structure is used. Specifications often require that these structs are zero filled by default, and only the relevant members are filled. If some members are mutually exclusive they are placed in an union. Typical example of such setup struct is the WNDCLASS from WinAPI.

Variable size data

Well this is rather a C pattern than a general design pattern. Sometimes objects may hold an arbitrary size binary payload. This pattern typically occur when reading data from binary files than can contain several types of data chunks. That's done by a struct like this.

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

And in the code the following is done:

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

This allocates memory thats larger than the struct allowing space for a variable length payload. Whose 5th byte then can be accessed by eg. vs->payload[4].

The advantage of this that the whole object can be freed in one free call. And it's guaranteed that it have a continous block in the memory. So it utilizes the cache better than allocating the corresponding buffer somewhere else in the heap.

Procedural counterparts of OOP design patterns

OOP patterns are never called in their names in procedural languages. So I can only guess here.

Creation patterns

  • Abstract factory: An abstract factory is generally a singleton. In that case this pattern is not used at all and conditional compilation is used instead. Otherwise setup structs providing the creation functions.
  • Builder: setup structs are used.
  • Factory method: callbacks are used for creation.
  • Lazy initialization: In C++ static local variables are used for this purpose. In C you can use the if (!initialized) { initialize(); initialized = 1; } pattern at places that are not performance critical. For performance critical code, lazy loading is not used at all. The user must find a place to initialize the context.
  • Prototype: In procedural world we simply return handles to stock objects. An example of this is the GetStockObject function in the WinAPI. For mutable objects a copy-on-write mechanism is often used for performance reasons.
  • Singleton: simply write top level functions (and use global variables when you absolutely need global state).

Structural patterns

  • Adapter and Facade: A pattern for building another interface upon an existing one. Simply new functions will call the old and other ones.
  • Bridge: Callbacks to the concrete implementations are provided in a form of setup struct.
  • Composite: top level functions are used specifying a handle to the parent node it should operate on.
  • Decorator: The decorating behavior is supplied in a form of callbacks. Or one event handler callback is provided for all possible decorations that receives various messages and decides to handle them or not (example is the window procedure in WinAPI).
  • Flyweight: Read-only binary data used packed in structs and arrays.
  • Proxy: Pretty much the same as in OOP, but without classes.

Behavioral patterns

  • Chain of responsibility: An array or linked list of callbacks traversed by a loop. Specification describes how should the callbacks indicate that they handled the request causing the loop to break.
  • Command: Commands are structs that contain a do and undo callback. These callbacks usually take some kind of context to operate on. And array of commands are maintained to perform the undo.
  • Interpreter: A compiler/parser/interpreter is written or generated using lex and yacc.
  • Iterator: Handles are used, otherwise the same. For performance reasons in C we often stick to arrays.
  • Mediator: Usually realized by using some message dispatching mechanism and message loops and event handlers.
  • Memento: Same as in OOP, but without classes.
  • Observer: Same as the chain of responsibility, but the loop won't break. An example is the atexit.
  • State: Realized by 2 dimensional dispatch tables that maps the current state and the requested operation into a function. (In sparse cases simply ifs are used.)
  • Strategy: This is the basic use case of callbacks.
  • Template method: Typically frameworks let the user supply his own callbacks for some functions. Libraries often provide a way to use custom memory allocating function providing a custom malloc and free.
  • Visitor: Realized by using multidimensional arrays of callbacks, which is typically NULL filled at the start (for default behavior), and populated in the main initialization code for each type pair.
Calmarius
  • 18,570
  • 18
  • 110
  • 157
  • 2
    Fantastic answer. Really illustrates how OOP is not the ultimate solution, and that there are other ways of thinking about the same problem as well. – starikcetin Jul 11 '21 at 16:39
  • Regarding variable sized data, are you sure that for some architectures a compiler won't reorganize **VariableSized** so that vs->payload[4] would point to `someData` or `otherData`? – mercury0114 Jun 27 '23 at 20:39
  • @mercury0114 The C standard guarantees that struct fields appear in the memory in the same order they declared. Only the gaps between the fields may vary. – Calmarius Jun 28 '23 at 09:27
2

The book "Design Patterns: Elements of Reusable Object-Oriented Software" was a landmark book that brought attention to Design Patterns to the practice of computer programming, design and architecture. The dominant programming paradigm at the time was Object-Oriented software development, and the book was clearly targeted to that paradigm, and not to others. Although you could argue that some of the design patterns in the book applied to other paradigms, it wasn't the focus of the book. So what has become popular with designers and programmers was the set of design patterns outlined in that book. Since then, others have been documented by other authors, bloggers, and other web sites. No doubt that there are design patterns that apply to procedural languages that have been described on various web sites, however, like I said, when the programming community speaks of design patterns, they are mostly referring to the patterns outlined in that book. I know this is not a real answer because I don't where any documented patterns are for procedural languages, there certainly are some, I'm sure. I thought maybe I'd state the importance of that book, and the paradigm it was originally targeted to.

SunKing2
  • 311
  • 2
  • 10