Separating interface and implementation

Read about uniform reference semantics first.

Read about function-oriented modules and data-oriented-modules first.

In a modular design, it is quite important to separate interface from implementation. If the modules are data-oriented, then a module represents an object type and the operations that can be used on objects of that type. If, in addition, we use uniform reference semantics, then instances of objects are represented by pointers.

For our design to be good, we must clearly separate the `clients' of a module from the code that implements the module. The clients are other modules that are allowed to use the services of the module (i.e. the operations on the object instances), but nothing else. Another way of putting it is that only the code that implements the module is allowed to know about its internal implementation details. Clients should not have to know such details, nor should they be able to take advantage of them.

In the C language, a good way to separate interface from implementation is to put the interface in a `header file' with suffix .h, and the implementation in a file with suffix .c. Clients get access to the interface by including the header file.

The purpose, then, is to write a header file that contains as little information as possible about the internal implementation details of the module. With the C language, we can go quite far, but not quite all the way. Let us look at an example:

	#ifndef STACK_H
	#define STACK_H
	
	struct stack;
	typedef struct stack *stack;
	
	extern stack stack_create();
	extern void stack_push(stack s, void *element);
	extern void *stack_pop(stack s);
	extern int stack_empty(stack s);
	
	#endif
The first two lines and the last line are preprocessor commands. Their purpose is to make sure that all the other lines are processed only once by the compiler in each compilation. This header file might be included several times, directly or indirectly. The first time, the preprocessor symbol STACK_H is not defined, so all intermediate lines are processed, including the definition of the symbol STACK_H, which prevents the other lines to be processed a second time. This speeds up compilation, but more importantly, it may prevent the compiler from being confused by having to see the same declaration twice. In this case, it probably doesn't matter, but there are other cases where the compiler would reject the program.

The next two lines are the only ones that reveal some of the implementation details. They tell us that a stack is implemented as a pointer to a struct of some kind. We can't remove these lines, as the compiler needs a definition of the symbol stack in order to further process the file. Luckily, we do not have to show the internals of the stack struct. We can therefore alter it without even recompiling the clients. This last point is important, because it makes it possible to put our module is a `shared library' that can be distributed and compiled separately from the clients. We can update the shared library without recompiling client code.

Next follows the definition of the interface in the form of function headers. The keyword extern is optional, but we recommend you always put it in. Here, stack_create is the constructor that returns an empty stack, stack_push pushes an element (of type void *, i.e., any object represented as a pointer), onto the stack, stack_pop removes the top element of the stack and return it, stack_empty is a predicate to test whether there are any elements at all on the stack. We recommend you use some unique prefix such as stack_. Otherwise there will most certainly be conflicts between operations such as insert, delete, etc.