Encapsulation

From $1

Table of contents

Encapsulation is a term used in object oriented programming (e.g. C++, Java) to indicate that data and algorithms are separated in a computer program. This can be enforced in C as well, and we recommend that all new code is made according to the following standard:

  • Data structures should be hidden in the implementation part of the code by making them abstract
  • Derived code should not modify fields of data structures directly (and this is enforced by abstract data types), but rather use functions
  • All functions should return some number or pointer to check the result
  • All data structures should at least have an init routine to initiate the data structure and a done (or destroy) routine to free memory etc.

To make this somewhat more tangible here is an example, of an algorithm to harvest apples from a tree.

/* gmx_apple.h */

/* Abstract type defines a pointer to a structure */
typedef gmx_apple *
gmx_apple_t;

/* Initiate the orchard. This routine takes a pointer to an abstract, uninitialized, apple
  * datatype (which internally is a pointer-to-a-pointer, but that is none of the users business!),
  * and tries to initialize it, e.g. by allocating memory and settings stuff in the contents.
  * The return value tells us if the initialization was successful (0), or if something happened.
  * By using the return value for an error code rather than the data type we can check what went wrong.
  */
int 
gmx_apple_init(gmx_apple_t *apple);

/* Get the harvest in kilograms for this year. Return 0 if OK, error code in case of problems */
int 
gmx_apple_get_harvest(gmx_apple_t apple,double *harvest);

/* Set the amount of precipitation (mm/year). Return 0 if OK, error code otherwise */
int 
gmx_apple_set_precipitation(gmx_apple_t apple,double precipitation);

/* Obliterate the orchard. Typically destructors do not return anything since we cannot do much
  * when/if they fail (what if the contents was partially destructed?). 
  */
int 
gmx_apple_done(gmx_apple_t apple);

A test program would look like this:

/* gmx_apple_test.c */
#include <stdio.h>
#include "gmx_apple.h"

int main(int argc,char *argv[])
{
    gmx_apple_t apple;
    double      harvest;
    int         rc;

    rc = gmx_apple_init(&apple); 

    /* handy trick - put constants first in comparisons! if you make a typo and turn this 
      * into an assignment, the compiler won't accept assigning to a constant value.
      * Second, it is better to have short if-statements with a separate return statement
      * rather than a huge if-statement spanning dozens of lines and increasing indentation.
      */
    if( 0 != rc )
    {
          printf("No orchard could be established on this barren soil.\n"); 
          return rc;
    }

   /* Note that we have hard-coded the precipation level for this year. 3 points deducted. */
   rc = gmx_apple_set_precipitation(apple,130.5);
   if ( 0 == rc ) 
   {
       rc = gmx_apple_get_harvest(apple,&harvest);
   }

   if ( 0 != rc )
   {
      printf("A storm prevent harvesting the otherwise healthy tree. Error code was %d\n",code);
   }
   else
   {
      printf("The harvest was %lf kg of juicy apples this year\n",harvest);
   }
        
   rc = gmx_apple_done(apple);
   if ( 0 != rc ) 
   {
       printf("Can not even get rid of the orchard. Error code was %d\",rc);
   } 

   return 0;
}

Finally, the implementation of gmx_apple.c is left as an exercise to the reader. Since we have a good API, who cares about the implementation? And, more important, next year when we plan to have our SSE-optimized orchard ready all routines using this API will magically see an amazing increase in apple harvest!

Tags:
 
Comments (0)