Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
193 views
in Technique[技术] by (71.8m points)

model view controller - MVC implemented in pure C

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.

question from:https://stackoverflow.com/questions/9355021/mvc-implemented-in-pure-c

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...