用纯 C 实现的 MVC

Posted

技术标签:

【中文标题】用纯 C 实现的 MVC【英文标题】:MVC implemented in pure C 【发布时间】:2012-03-10 10:23:35 【问题描述】:

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

澄清一下,我对 C#、C++、Objective-C、Java、php 或任何更高级别的语言示例不感兴趣。我想知道人们如何看待如何使用纯 ansi C99 甚至 C89 来处理这种设计模式。由于缺乏正式的 OOP 语言结构,这可能在 C 中甚至没有意义?

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

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

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

【问题讨论】:

我在嵌入式环境中使用 OO C 和 MVP(这对你来说可能比 MVC 更有用)。 【参考方案1】:

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

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

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

不能不同意该声明。正如我稍后将展示的那样;仅仅因为 C 没有像“class”这样漂亮的关键字并不意味着你不能完成同样的事情。

我会尽量按照您的问题流程逐步完成此操作。

C 中的 OOP

我怀疑,根据您问题的措辞,您对 OOP 概念有相当不错的掌握(您甚至在模式方面进行思考,甚至​​对这些模式将如何针对您的特定场景)——所以让我在“30 秒或更短的时间内”做一个“C 语言中的 OOP”教程。

一旦你掌握了窍门,你就会意识到你可以做的比我在这里展示的要多得多——但我只是想让你尝尝。

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 中有什么,因为该实现细节在“内部”头文件中,而不是“公共”头文件中。 我们实现“实例方法”就像 OOP 语言一样 - 除了我们必须手动传递“this”指针 - 其他语言只是为您执行此操作 - 但这没什么大不了的。 我们有“属性”。同样,其他语言将以更好的语法封装属性 getter/settings - 但他们在幕后所做的只是为您创建一些 getter/setter 方法并将对“属性”的调用转换为方法调用。

继承

那么,如果我们想要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);

现在,我应该提一下,就像我们制作的“初始化程序”实际上并不调用malloc,而是负责初始化成员变量——我们也确实需要释放器——它实际上并不释放结构——而是免费/释放任何“拥有”引用等。但是...实际上我将在下面的部分中提到一些内容,这可能会解释为什么我还没有为此烦恼。

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

但是,这有一些小问题 - 就像我在上一段中提到的那样 - 这种技术实际上并不能让您更改基类的行为。 (例如,我们想做一些事情来释放我们的实例)。

多态性

假设我们有一些方法 - 我们会想出一个随机的 BS 示例 - 称为 calculate

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

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

这是一个例子(我将不再给出完整的例子,而是专注于相关部分):

首先我们定义方法的签名。这里我们说“calculateMethod”是:一个指向方法的指针,它接受一个参数(一个指针)并返回一个 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 文件中声明的一个名为 void FooSetCalculateMethod(Foo * this, calculateMethod value); 的方法 - 瞧!可以在子类中覆盖的方法。

型号

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

好的 - 所以,模型可能是最容易实现的东西 - 用作数据存储机制的简单“类”。

您必须为您的特定场景找出一些东西(作为嵌入式系统,我不确定您的确切限制是什么 - 如果您担心 RAM/持久性等) - 但我认为您反正我不希望我深入研究。

查看

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

对于物理事物,您的“视图”可能是控制面板上的固定按钮 - 或者,如您所说,它可能是 LCD 或 html

这里的底线是您只需要能够通过“简单”界面呈现系统其余部分的类,以便在视图中显示/更改内容 - 并将 IO 的详细信息封装给用户。

通常,“IO”的“I”部分在视图中至少需要一小段代码。

我认为这并不理想 - 但是,大多数时候,将“视图”代理用户输入返回到控制器并没有什么好的方法。也许你的系统有一个很好的方法来解决这个问题——因为你可以完全控制。

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

控制器

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

这通常是应用程序的核心。在给定时间,您可能需要多个控制器 - 一个用于传感器数据的输入/处理,一个或多个用于您激活的任何 UI,可能还有其他。

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

如果您想要更多,或者是否有帮助,请告诉我。

【讨论】:

谢谢史蒂夫。这是一个非常详细的答案。它确实说明了 OOP 语言中有很多语法糖。 Steve 我正在尝试完成这个示例,但是一些 C 语法和不透明的指针让我感到困惑。您能否提供一个基本示例,说明如何初始化 Foo 的新实例并绑定到主函数内的局部变量?也许还显示在 Foo 实例上调用一些方法。我尝试了以下方法: Foo myFoo = Foo * FooCreate(30, 10); printf("Foo 年龄[%i] \n", myFoo->age); // 期望 30 printf("Foo something[%i] \n", myFoo->something); // 期望 10 @GordonPotter - 当然 - 但让我知道这一点首先澄清:myFoo->age 不起作用,因为 Foo 是不透明的 - 这意味着这段代码不知道 Foo 的内部结构。这是很好的信息隐藏 - 但意味着您需要公开方法来访问该信息,例如 FooGetAge(Foo *) ,这将返回年龄成员。让我知道是否可以澄清或者您是否仍需要示例。 谢谢史蒂夫。这就是我试图通过方法掌握对不透明指针的访问的部分。 C 语法让我大吃一惊。对 C 来说还是比较新的。也许是一个基本的 main 函数,其中包含调用 Foo 对象和使用 setter 和 getter 的示例。我只需要查看代码以更好地理解语法。还传递“this”参数。需要确保我完全掌握代码中的内容。这可能超出了范围,但是如果您有一个子类,您将如何处理“超级”的概念,例如调用链上更高的初始化程序。外部呢? @Steve 看到你在 C 中实现 MVC 会很有趣。你能在你的帖子中添加它吗?【参考方案2】:

我的 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);

不过,您可能会比这更花哨。如果你在一个控制器上有多个视图,你会想要像观察者模式这样的东西来管理视图。但是有了这个,你就有了可插入的视图。在现实中我可能会更严格一些,只让模型通过函数进行更改,并且视图“显示”函数指针也只能通过函数调用(我直接调用它们)。这允许各种钩子(对于初学者,检查模型或视图/函数指针是否为空)。我省略了内存管理,因为添加起来并不难,但会让事情看起来很乱。

【讨论】:

谢谢基思。简洁明了。【参考方案3】:

我对人们可能提出的建议很感兴趣,但我认为你已经一针见血了 - 由于缺乏正式的 OOP 结构,这可能没有意义。

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

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

这是一项艰巨的任务,但您最终可以想出某种模板/框架,这将使编写进一步的 MVC 样式开发变得非常容易;但我想权衡的是——你能负担得起时间吗?嵌入式平台的局限性是否使得 MVC 提供的清晰度的好处被缺乏性能/内存保护/垃圾收集以及必须重新发明***的巨大努力所抵消?

祝你好运,我很想看看你的想法!

编辑:

事后考虑,也许仅仅拥有一些 OOP 技术而不去执行完整的 MVC 实现可能有助于解决您的问题 - 如果您可以使用接口实现适当的多态层次结构,那么您将拥有实现代码重用的目标还有很长的路要走。

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

【讨论】:

Russ,这是您分享的精彩 PDF。我只是浏览了一下,它看起来有很多具体的例子。肯定会仔细看看。谢谢! 链接的 PDF 在我看来已损坏。

以上是关于用纯 C 实现的 MVC的主要内容,如果未能解决你的问题,请参考以下文章

在没有其他库的情况下用纯 c/c++ 编写 BMP 图像

怎么用纯css实现点击出现下拉框,抽屉效果那种

用纯css实现双边框效果

用纯XMLHttpRequest实现AJAX

能否给我一个用纯C编写的UDP发送和接收的程序

用纯css实现Tab切换