[ACCEPTED]-MVC implemented in pure C-design-patterns

Accepted answer
Score: 69

Pshew... this might be a long answer... but 102 here goes...

First, let's start with this 101 statement:

Maybe this doesn't even make sense in C because of the lack of formal OOP language constructs?

Couldn't disagree more with that statement. As 100 I'll show later on; just because C doesn't 99 have nifty keywords like "class" doesn't 98 mean you can't accomplish the same things.

I'll 97 try to go through this step-by-step as best 96 I can - following your question's flow.

OOP in C

I 95 suspect, based on the phrasing of your question, that 94 you have a pretty decent grasp of OOP concepts 93 (you're even thinking in terms of patterns 92 and even have a good idea of how those patterns 91 will play out for your particular scenario) - so 90 let me do an "OOP in C" tutorial 89 in "30 seconds or less".

Once 88 you get the hang of things you'll realize 87 there is a lot more you can do than what 86 I'm going to show here - but I just want 85 to give you a taste.

101

First, we'll start with 84 a basic "class" (go with me on 83 this):

Foo.h:

typedef struct Foo Foo;
Foo * FooCreate(int age, int something);
void FooSetAge(Foo * this, int age);
void FooFree(Foo * this);

Foo_Internal.h: (you'll see why I broke this out in a second)

#include "Foo.h"

struct Foo { 
     int age;
     int something;
};

void FooInitialize(Foo * this, int age, int something);

Foo.c:

#include "Foo_Internal.h"

// Constructor:
Foo * FooCreate(int age, int something) { 
    Foo * newFoo = malloc(sizeof(Foo));

    FooInitialize(newFoo);

    return newFoo;
}

void FooInitialize(Foo * this, int age, int something)
{
    this->age = age;
    this->something = something;
}

// "Property" setter:
void FooSetAge(Foo * this, int age) {
    this->age = age;
}

void FooFree(Foo * this) { 
    // Do any other freeing required here.
    free(this);
}

Couple things to notice:

  • We hid the implementation details of Foo behind an opaque pointer. Other people don't know what is in a Foo because that implementation detail is in the "internal" header file, not the "public" header.
  • We implement "instance methods" just like an OOP language would - except we have to manually pass the "this" pointer - other languages just do this for you - but it's not a big deal.
  • We have "properties". Again, other languages will wrap up property getters/settings in a nicer syntax - but all they are really doing behind the scenes is creating some getter/setter method for you and translating calls to the "properties" into method calls.

Inheritance

So what if 82 we want a "subclass" of Foo - which 81 only adds additional functionality - but 80 can be substituted for a Foo? Simple:

FooSubclass.h:

typedef struct FooSubclass FooSubclass;
FooSubclass * FooSubclassCreate(int age, int something, int somethingElse);
void FooSubclassSetSomethingElse(FooSubclass * this, int somethingElse);
void FooSubclassFree(FooSubclass * this);

FooSubclass_Internal.h:

#include "FooSubclass.h"
#include "Foo_Internal.h"

struct FooSubclass { 
     Foo base;
     int something;
};

void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse);

FooSubclass.c

#include "FooSubclass_Internal.h"

// Constructor:
Foo * FooSubclassCreate(int age, int something, int somethingElse) { 
    FooSubclass * newFooSubclass = malloc(sizeof(FooSubclass));

    FooSubclassInitialize(newFooSubclass, age, something, somethingElse);

    return newFooSubclass;
}

void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse) {
    FooInitialize(this, age, something);
    this->somethingElse = somethingElse;
} 

void FooSubclassSetSomethingElse(Foo * this, int somethingElse)
{
    this->somethingElse = somethingElse;
}

void FooSubclassFree(FooSubclass * this) { 
    // Do any other freeing required here.
    free(this);
}

Now, I 79 should mention, just like we made "initializers" which 78 don't actually call malloc, but are responsible 77 for initializing the member variables - we 76 also really need deallocators - which don't 75 actually free the struct - but instead free/release 74 any "owning" references, etc. However... I'm 73 actually going to mention something in the 72 section below which might explain why I 71 didn't bother with that yet.

You should notice 70 now - that since our FooSubclass's first member is, in 69 fact, a Foo struct - that any reference to 68 a FooSubclass is also a valid reference to a Foo - meaning 67 it can be used as such pretty much anywhere.

However, there 66 are a few small issues with this - like 65 I mentioned in the paragraph before last 64 - this technique doesn't actually let you 63 change behavior of the base class. (Something 62 we'd like to do for deallocating our instance, for 61 example).

Polymorphism

Let's say we have some method 60 - we'll come up with a random BS example 59 - called calculate.

We want calling calculate on a Foo to return 58 one value - but a different value if it 57 was called on a FooSubclass.

This is simple in C - it's 56 really just a matter of creating a wrapper 55 method which actually calls a function referenced 54 by a function pointer. OOP languages do 53 this for you behind the scenes and it's 52 usually implemented via what's referred 51 to as a VTable.

Here's an example (I'm going to 50 stop giving complete examples and instead 49 focus on the relevant parts):

First we define 48 the signature of the method. Here we're 47 saying "calculateMethod" is: a 46 pointer to a method which takes one parameter 45 (a pointer) and returns an int.

typedef int (*calculateMethod)(void *);

Next, we 44 add a member variable in our base class 43 which will point to some function:

struct Foo { 
    // ...
    calculateMethod calc;
    // ...
}

We initialize 42 this with some initial value in the FooInitialize method 41 (for our base implementation):

int FooCalculate(Foo * this)
{
    this->calc(this);
}

int FooCalculateImplementation(void * this)
{
    Foo * thisFoo = (Foo *)this;
    return thisFoo->age + thisFoo->something;
}

void FooInitialize(Foo * this, ...)
{
    // ...
    this->calc = &FooCalculateImplementation;
    // ...
}

Now we make 40 some way for subclasses to override this 39 method - say, for example, a method declared 38 in the Foo_Internal.h file called void FooSetCalculateMethod(Foo * this, calculateMethod value); - and voila! Methods 37 which can be overridden in subclasses.

Model

Our model would typically consist of data acquisition from Analog to Digital converters in the product.

OK 36 - so, Model is probably the easiest thing 35 to implement - simple "classes" which 34 are used as data storage mechanisms.

You'll 33 have to figure something out for your particular 32 scenario (being an embedded system I'm not 31 sure what your exact restrictions will be 30 - and if you're worried about RAM / persistence 29 / etc) - but I think you don't want me to 28 dive into that anyways.

View

The views might be a web page powered by an embedded web server, or else an LCD screen with capacitive touch control.

For physical things 27 your "view" may be fixed buttons 26 on a control panel - or, like you said, it 25 could be an LCD or HTML.

The bottom line 24 here is you simply need classes which are 23 capable of presenting the rest of your system 22 with a "simple" interface for 21 displaying/changing things in the view - and 20 encapsulate the details of IO to the user.

Typically 19 the "I" part of "IO" needs 18 at least some small wedge of code in the 17 view.

I don't think this is ideal - but, most 16 of the time, there isn't a good way around 15 having your "view" proxy user 14 input back to your controllers. Maybe with 13 your system there is a good way around this 12 - given you have total control.

I hope you 11 can see now how you could easily go about 10 creating some view classes which are relevant 9 to your needs.

Controller

Our controllers would more or less be the glue logic that manages the relationship between these two areas of code.

This is usually the guts 8 of the application. You'll likely need more 7 than one controller around at a given time 6 - one for ingress/processing of sensor data, one 5 or more for whatever UI you've got active, and 4 possibly others.

Anyways, I hope that helps... I 3 feel like I'm writing a book now, so I'll 2 stop.

Let me know if you want more, or if 1 that helps at all.

Score: 8

my MVC framework!

typedef struct  
{
    int x;
} x_model;

typedef void (*f_void_x)(x_model*);

void console_display_x(x_model* x)
{
    printf("%d\r\n",x->x);
}

typedef struct  
{
    f_void_x display;
} x_view;

typedef struct 
{
    x_model* model;
    x_view* view;
} x_controller;


void create_console_view(x_view* this)
{
    this->display = console_display_x;
}

void controller_update_data(x_controller* this, int x)
{
    this->model->x = x;
    this->view->display(this->model);
}

void x_controler_init(x_controller* this, x_model* model, x_view* view)
{
    this->model = model;
    this->view = view;
}

int main(int argc, char* argv[])
{
    x_model model;
    x_view view;
    x_controller controller;

    create_console_view(&view);
    x_controler_init(&controller, &model, &view);

    controller_update_data(&controller, 24);
}

You'd probably get a bit 14 fancier than this though. If you had multiple 13 views on one controller you'd want something 12 like an observer pattern to manage the views. But 11 with this, you have pluggable views. I'd 10 probably be a bit more strict in reality 9 and only let the model be changed through 8 a function and the views 'display' function 7 pointer also only callable through a function 6 ( I directly call them ). This allows for 5 various hooks (for starters, check to see 4 if the model or the view / function pointer 3 is null). I left out memory management 2 as it isn't too difficult to add in, but 1 makes things look messy.

Score: 4

I'm interested in what suggestions people 30 might have but I think you've hit the nail 29 on the head - It probably doesn't make sense 28 because of the lack of Formal OOP constructs.

However; it 27 is possible to introduce OOP concepts to ANSI-C; I've 26 had a link to this PDF for a while and whilst 25 I've never really absorbed it (because of 24 no exposure to C in my day to day job) it 23 certainly looks to be fruitful:

http://www.planetpdf.com/codecuts/pdfs/ooc.pdf

It's a big 22 task but you could ultimately come up with 21 some kind of template/frame work that would 20 make it very easy to write further MVC style 19 development; but I guess the trade off is 18 - can you afford the time ? Are the limitations 17 of the embedded platforms such that the 16 benefits of the clarity afforded by MVC 15 are outweighed by the lack of performance/memory 14 protection/garbage collection and of course 13 the sheer effort of having to re-invent 12 the wheel ?

I wish you luck and I'll be very 11 interested to see what you come up with!

Edit:

As 10 an after thought, perhaps just having some 9 of the techniques of OOP without going as 8 far as to do a full MVC implementation might 7 help to solve your problems - If you could 6 implement a proper polymorphic hierarchy 5 with Interfaces, you'd have gone a long 4 way to the goal of code reuse.

This other 3 stackoverflow deals with implementing OOP 2 in Ansi C, it's an interesting read but 1 links to the same pdf: Can you write object-oriented code in C?

More Related questions