MVC在纯C中实现

时间:2012-02-20 01:08:59

标签: c model-view-controller design-patterns

是否有人知道任何提供在C上下文中尝试执行模型视图控制器设计模式的直接示例的资源?特别是嵌入式系统?

为了澄清,我对C#,C ++,Objective-C,Java,PHP或任何更高级别的语言示例不感兴趣。我想知道人们如何用纯ansi C99甚至C89来解决这个设计模式的问题。也许由于缺乏正式的OOP语言结构,这在C中甚至没有意义?

某些背景信息:我的同事和我正在研究基于Arm的PSoC芯片驱动的嵌入式系统。我们可以控制硬件设计和PCB,并且必须进行软件开发以增强我们产品的功能集。我们的模型通常包括从产品中的模拟到数字转换器的数据采集。视图可以是由嵌入式Web服务器供电的网页,或者是具有电容式触摸控制的LCD屏幕。我们的控制器或多或少是管理这两个代码区域之间关系的胶合逻辑。我们有许多不同的产品和变体来支持,因此需要重用代码。

不寻找高度详细或企业级的框架。但是相当简单的例子说明了分离编程问题的好策略,但偏向于在较低级别C中找到的习语,例如结构,函数,事件驱动逻辑和一种在C中有意义的抽象消息传递。

由于硬件的性质,我们需要使用C并且必须自己引导很多东西。在某些情况下,我们可以访问操作系统,在其他情况下,只需直接编译到处理器并从主函数开始。所有非常原始的,但寻找允许代码重用的方法,并希望加快软件工程过程。

3 个答案:

答案 0 :(得分:61)

Pshew ......这可能是一个很长的答案......但是这里......

首先,让我们从这句话开始:

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

该声明无法不同意。正如我稍后会展示的那样;仅仅因为C没有像#34; class"这样的漂亮关键词。并不意味着你无法完成同样的事情。

我会尽可能地逐步完成这一步骤 - 按照您的问题流程进行操作。

C中的OOP

我怀疑,基于你的问题的措词,你对OOP概念的掌握非常好(你甚至在模式方面思考,甚至​​对这些模式如何发挥作用有很好的认识你的特殊情况) - 让我在C"中做一个" OOP教程在" 30秒或更短时间"。

一旦掌握了一些东西,你就会意识到你可以做的事情远远超过我在这里展示的东西 - 但我只想给你一个品味。

101

首先,我们将从一个基本的"类"开始。 (和我一起去):

<强> 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 :(你会明白为什么我会在一秒钟之内解决这个问题)

#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);
}

要注意事项:

  • 我们将Foo的实现细节隐藏在不透明指针后面。其他人不知道Foo中的内容,因为实施细节位于&#34;内部&#34;头文件,而不是&#34; public&#34;头。
  • 我们实施&#34;实例方法&#34;就像OOP语言一样 - 除了我们必须手动传递&#34;这个&#34;指针 - 其他语言只是为你做这件事 - 但这不是什么大问题。
  • 我们有&#34;属性&#34;。同样,其他语言将以更好的语法包含属性getter / settings - 但他们真正在幕后做的就是为你创建一些getter / setter方法并将调用转换为&#34;属性&#34;进入方法调用。

继承

那么如果我们想要一个&#34;子类&#34;那该怎么办? Foo - 仅添加其他功能 - 但可替代Foo?简单:

<强> 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);
}

现在,我应该提一下,就像我们制作&#34;初始化者&#34;它实际上并没有调用malloc,而是负责初始化成员变量 - 我们也真的需要解除分配器 - 它实际上不会释放结构 - 而是免费/释放任何&#34;拥有&#34;但是......我实际上会在下面的部分提到一些内容,这可能解释了为什么我还没有为此烦恼。

您现在应该注意 - 因为我们FooSubclass的第一个成员实际上是Foo结构 - 所以对FooSubclass的任何引用也是有效的引用Foo - 意味着它几乎可以在任何地方使用。

然而,这有一些小问题 - 就像我之前在段落中提到的那样 - 这种技术实际上并没有让你改变基类的行为。 (例如,我们要为解除分配我们的实例而做的事情)。

多态性

让我们说我们有一些方法 - 我们会提出一个随机的BS示例 - 名为calculate

我们希望在calculate上调用Foo来返回一个值 - 但如果在FooSubclass上调用它,则会返回不同的值。

这在C中很简单 - 它实际上只是创建一个实际调用函数指针引用的函数的包装器方法。 OOP语言在幕后为您执行此操作,通常通过所谓的VTable来实现。

以下是一个例子(我将停止提供完整的示例,而是专注于相关部分):

首先我们定义方法的签名。我们在这里说&#34; calculateMethod&#34;是:指向一个方法的指针,该方法接受一个参数(一个指针)并返回一个int。

typedef int (*calculateMethod)(void *);

接下来,我们在基类中添加一个成员变量,它将指向一些函数:

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

我们使用FooInitialize方法中的一些初始值初始化它(对于我们的基础实现):

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;
    // ...
}

现在我们为子类创建一些覆盖此方法的方法 - 例如,在名为Foo_Internal.h的{​​{1}}文件中声明的方法 - 瞧!可以在子类中重写的方法。

模型

void FooSetCalculateMethod(Foo * this, calculateMethod value);

好的 - 所以,Model可能是最容易实现的东西 - 简单&#34;类&#34;它们被用作数据存储机制。

你必须为你的特定场景找出一些东西(作为一个嵌入式系统,我不确定你的确切限制是什么 - 以及你是否担心RAM /持久性/等等) - 但我想你不要让我潜入那里。

视图

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

对于物质事物,您可以查看&#34;可能是控制面板上的固定按钮 - 或者,就像你说的,它可能是一个LCD或HTML。

这里的底线是你只需要能够向你的系统的其余部分展示简单&#34;用于在视图中显示/更改内容的界面 - 并将IO的详细信息封装到用户。

通常&#34; I&#34; &#34; IO&#34;的一部分在视图中至少需要一些小的代码。

我不认为这是理想的 - 但是,在大多数情况下,没有一个好方法可以让你的观点&#34;代理用户输入回控制器。也许你的系统有一个很好的方法 - 鉴于你有完全控制。

我希望您现在可以看到如何轻松创建一些与您的需求相关的视图类。

控制器

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

这通常是应用程序的内容。在给定时间,您可能需要多个控制器 - 一个用于传感器数据的入口/处理,一个或多个用于您已激活的UI,以及可能的其他UI。

无论如何,我希望有帮助...我觉得我现在正在写一本书,所以我会停下来。

如果您想要更多,或者根本不需要,请告诉我。

答案 1 :(得分:6)

我的MVC框架!

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);
}

你可能会比这更有趣。如果在一个控制器上有多个视图,则需要像观察者模式这样的视图来管理视图。但有了这个,你就有了可插拔的视图。实际上我可能会更加严格,只允许通过函数更改模型,并且视图'display'函数指针也只能通过函数调用(我直接调用它们)。这允许各种钩子(对于初学者,检查模型或视图/函数指针是否为空)。我遗漏了内存管理,因为它不是很难添加,但让事情看起来很乱。

答案 2 :(得分:4)

我对人们可能提出的建议感兴趣,但我认为你已经敲定了头 - 由于缺乏正式的OOP结构,它可能没有意义。

然而; 可以将OOP概念引入ANSI-C;我已经有一段时间链接到这个PDF了,虽然我从来没有真正吸收它(因为我的日常工作中没有接触过C),它看起来确实很有成效:

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

这是一项艰巨的任务,但你最终可以提出某种模板/框架工作,这样可以很容易地编写MVC风格的进一步开发;但我认为权衡是 - 你能负担得起时间吗?嵌入式平台的局限性是由于缺乏性能/内存保护/垃圾收集以及必须重新发明轮子的绝对努力而超过了MVC提供的清晰度的好处吗?

祝你好运,我很想知道你的想法!

编辑:

作为一个后来的想法,或许只是拥有OOP的一些技术而不进行完整的MVC实现可能有助于解决您的问题 - 如果您可以使用Interfaces实现适当的多态层次结构,那么您可以使用它。 d对代码重用的目标已经走了很长的路。

这个其他的stackoverflow涉及在Ansi C中实现OOP,它是一个有趣的读物,但链接到相同的pdf:Can you write object-oriented code in C?