Created
August 10, 2015 16:27
-
-
Save hosaka/6e4133809c2fc8ef56e6 to your computer and use it in GitHub Desktop.
Virtual Method Tabble, implementing dynamic binding (polymorphism) in C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
// vtable is a virtual method table that implements dynamic binding | |
// The goal is to write code that operates abstractly on Shapes rather than on | |
// specific Circles or Rects as a form of // polymorphism // | |
// abstract and concrete objects | |
typedef struct Shape Shape; | |
typedef struct Circle Circle; | |
typedef struct Rect Rect; | |
//////////////////////////////////////////////////////////////// | |
// operations to perform on Shapes, note each takes a Shape * // | |
//////////////////////////////////////////////////////////////// | |
// a generic draw method that does not concern with which type of shape it is | |
void drawPicture(Shape **begin, Shape **end); | |
// individual draw function that provide different implementation | |
void drawShape(const Shape *s) | |
{ | |
printf("Not a concrete type\n"); | |
} | |
void drawCircle(const Shape *s) | |
{ | |
printf("Drawing circle\n"); | |
} | |
void drawRect(const Shape *s) | |
{ | |
printf("%p\n", s); | |
// how to access shape fields, to set the width/height for example? | |
printf("Drawing rect\n"); | |
} | |
////////////////////////////////////////////// | |
// vtables for each type of concrete object // | |
////////////////////////////////////////////// | |
// vtable is a grouping of function pointers, this is an interface for the | |
// manipulations we would like to do on a Shape | |
typedef struct Shape_vtbl | |
{ | |
void (*draw)(const Shape *s); | |
// any other operatins to perform on a shape | |
// transform | |
// resize | |
// color etc. | |
} Shape_vtbl; | |
// each vtable that points to the correct functions of a particular type | |
Shape_vtbl shape_vtbl = { &drawShape }; // draw method for shape | |
Shape_vtbl circle_vtbl = { &drawCircle };// draw circle function pointer | |
Shape_vtbl rect_vtbl = { &drawRect }; // draw rect | |
//////////////////// | |
// abstract types // | |
//////////////////// | |
// in our shape we have one element, which is a vtable | |
struct Shape | |
{ | |
Shape_vtbl *vtbl; | |
// other members related to shapes, points, color | |
}; | |
// other structures have super members of more generic type | |
struct Circle | |
{ | |
Shape super; // circle is a Shape | |
// circle related members | |
}; | |
// same with Rect | |
struct Rect | |
{ | |
Shape super; | |
// others | |
int width; | |
int height; | |
}; | |
////////////////////////////// | |
// initialisation functions // | |
////////////////////////////// | |
// the sub function call the initShape function and change the vtable | |
void initShape (Shape *s) | |
{ | |
s->vtbl = &shape_vtbl; | |
} | |
void initCircle (Circle *c) | |
{ | |
initShape( (Shape *)c ); // casting the pointer to Shape as it's the 1st member | |
c->super.vtbl = &circle_vtbl; // reassign the vtable to the circle vtable | |
// maybe other members for Circle | |
} | |
// init for rect is pretty much the same | |
void initRect (Rect *r, int width, int height) | |
{ | |
initShape( (Shape *)r ); | |
r->super.vtbl = &rect_vtbl; | |
r->width = width; | |
r->height = height; | |
} | |
// itarate over the Shapes array given | |
void drawPicture (Shape **begin, Shape **end) | |
{ | |
// with each iteration we want to call the draw method that's being | |
// pointed to in the vtable | |
while (begin != end) | |
{ | |
(*begin)->vtbl->draw(*begin); // access the passed in, call the draw func | |
// located in it's vtable | |
++begin; // increment the pointer | |
} | |
} | |
/////////////////// | |
// usage example // | |
/////////////////// | |
int main(int argc, char const *argv[]) | |
{ | |
// create some shapes and init them | |
Shape shape; initShape(&shape); | |
Circle circle1; initCircle(&circle1); | |
Circle circle2; initCircle(&circle2); | |
Rect rect; initRect(&rect, 10, 20); | |
// create an array of Shapes pointers and put the shapes there | |
// each item (not pointer!) has to be casted to a Shape since the array | |
// is of Shapes, but each element can be safely casted to Shape | |
Shape *shapes[4] = | |
{ | |
(Shape*) &circle1, | |
(Shape*) &rect, | |
(Shape*) &shape, | |
(Shape*) &circle2 | |
}; | |
int size = sizeof(shapes)/sizeof(*shapes); | |
// invoke the generic picture method that takes any Shapes array | |
// and draws them | |
drawPicture(shapes, shapes + 4); | |
printf("%p\n", rect); | |
// should we want to add another type, say a triangle, our drawPicture method | |
// doesn't have to change at all, we would simply have to add another structs | |
// and the vtable | |
// | |
// unlike the switch statements, that would require the whole structure to | |
// change and possibly cause a mess, the dynamic binding lets you avoid that | |
// and separate a clean API with the implementation transparent to the caller | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment