MVC implemented in pure C

2019-01-29 22:22发布

问题:

Does anybody know of any resources that provide a straight forward example of trying to do Model View Controller design pattern in a C context? And in particular an embedded system?

To clarify, I am not interested in C#, C++, Objective-C, Java, PHP or any higher level language examples. I want to know what people think about how to approach this design pattern with pure ansi C99 or even C89. Maybe this doesn't even make sense in C because of the lack of formal OOP language constructs?

Some context: my co-workers and I are working on embedded systems powered by Arm based PSoC chips. We have control over the hardware design and PCBs, and have to do software development to enhance our product's feature set. Our model would typically consist of data acquisition from Analog to Digital converters in the product. The views might be a web page powered by an embedded web server, or else an LCD screen with capacitive touch control. Our controllers would more or less be the glue logic that manages the relationship between these two areas of code. We have lots of different products and variations to support so code reuse is desirable.

Not looking for highly detailed or enterprise level frameworks. But rather simple examples that illuminate good strategies for separating the programming concerns, but with a bias toward the idioms found in lower level C, e.g. structs, functions, event driven logic and a kind of abstract message passing that makes sense in C.

Because of the nature of the hardware we need to use C and have to bootstrap a lot of things ourselves. And in some cases we have access to an OS and in other cases just compile straight to processor and start with a main function. All very primitive, but looking for approaches that allow for code reuse and hopefully speed up the software engineering process.

回答1:

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

First, let's start with this 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 I'll show later on; just because C doesn't have nifty keywords like "class" doesn't mean you can't accomplish the same things.

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

OOP in C

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

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

101

First, we'll start with a basic "class" (go with me on 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 we want a "subclass" of Foo - which only adds additional functionality - but 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 should mention, just like we made "initializers" which don't actually call malloc, but are responsible for initializing the member variables - we also really need deallocators - which don't actually free the struct - but instead free/release any "owning" references, etc. However... I'm actually going to mention something in the section below which might explain why I didn't bother with that yet.

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

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

Polymorphism

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

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

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

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

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

typedef int (*calculateMethod)(void *);

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

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

We initialize this with some initial value in the FooInitialize method (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 some way for subclasses to override this method - say, for example, a method declared in the Foo_Internal.h file called void FooSetCalculateMethod(Foo * this, calculateMethod value); - and voila! Methods which can be overridden in subclasses.

Model

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

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

You'll have to figure something out for your particular scenario (being an embedded system I'm not sure what your exact restrictions will be - and if you're worried about RAM / persistence / etc) - but I think you don't want me to 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 your "view" may be fixed buttons on a control panel - or, like you said, it could be an LCD or HTML.

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

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

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

I hope you can see now how you could easily go about creating some view classes which are relevant 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 of the application. You'll likely need more than one controller around at a given time - one for ingress/processing of sensor data, one or more for whatever UI you've got active, and possibly others.

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

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



回答2:

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 fancier than this though. If you had multiple views on one controller you'd want something like an observer pattern to manage the views. But with this, you have pluggable views. I'd probably be a bit more strict in reality and only let the model be changed through a function and the views 'display' function pointer also only callable through a function ( I directly call them ). This allows for various hooks (for starters, check to see if the model or the view / function pointer is null). I left out memory management as it isn't too difficult to add in, but makes things look messy.



回答3:

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

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

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

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

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

Edit:

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

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