MVC在纯C中实现

Gor*_*ter 36 c model-view-controller design-patterns

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

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

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

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

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

Ste*_*eve 61

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

首先,让我们从这个声明开始:

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

不能不同意这种说法.正如我稍后将要展示的那样; 仅仅因为C没有像"类"这样的漂亮关键词并不意味着你无法完成同样的事情.

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

O中的OOP

我怀疑,基于你的问题的措辞,你对OOP概念有了相当不错的把握(你甚至在模式方面进行思考,甚至​​对这些模式如何在你的特定场景中发挥作用) - 所以,让我在"30秒或更短时间内"进行"OOP in C"教程.

一旦你掌握了一些事情,你会发现你可以做的事情比我将在这里展示的要多得多 - 但我只是想给你一个品味.

101

首先,我们将从一个基本的"课程"开始(与我同行):

foo.h中:

typedef struct Foo Foo;
Foo * FooCreate(int age, int something);
void FooSetAge(Foo * this, int age);
void FooFree(Foo * this);
Run Code Online (Sandbox Code Playgroud)

Foo_Internal.h :(你会明白为什么我会在一秒钟内解决这个问题)

#include "Foo.h"

struct Foo { 
     int age;
     int something;
};

void FooInitialize(Foo * this, int age, int something);
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

要注意的事情:

  • 我们隐藏了Foo不透明指针背后的实现细节.其他人不知道a中的内容是Foo因为实现细节位于"内部"头文件中,而不是"公共"头文件中.
  • 我们实现"实例方法"就像OOP语言一样 - 除了我们必须手动传递"this"指针 - 其他语言只是为你做这个 - 但这不是什么大问题.
  • 我们有"属性".同样,其他语言将以更好的语法包含属性获取器/设置 - 但他们真正在幕后做的就是为您创建一些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);
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

现在,我应该提一下,就像我们制作的"初始化器"实际上没有调用malloc,但负责初始化成员变量 - 我们也真的需要解除分配器 - 实际上不释放结构 - 而是免费/释放任何"拥有"引用等等.但是......我实际上会在下面的部分中提到一些可能解释为什么我还没有打扰它的原因.

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

但是,这有一些小问题 - 就像我在前一段中提到的那样 - 这种技术实际上并没有让你改变基类的行为.(例如,我们想要取消分配我们的实例).

多态性

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

我们希望调用calculatea Foo来返回一个值 - 但是如果在a上调用它则是一个不同的值FooSubclass.

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

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

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

typedef int (*calculateMethod)(void *);
Run Code Online (Sandbox Code Playgroud)

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

struct Foo { 
    // ...
    calculateMethod calc;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我们用方法中的一些初始值初始化它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;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

现在我们为子类创建一些覆盖此方法的方法 - 例如,在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.

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

你必须为你的特定场景找出一些东西(作为一个嵌入式系统,我不确定你的确切限制是什么 - 如果你担心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,以及可能的其他UI.

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

如果你想要更多,或者如果有所帮助,请告诉我.

  • 谢谢史蒂夫。这是一个非常详细的答案。它确实说明了 OOP 语言中有很多语法糖。 (2认同)

Kei*_*las 8

我的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);
}
Run Code Online (Sandbox Code Playgroud)

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


Rus*_*rke 4

我对人们可能提出的建议感兴趣,但我认为你已经击中要害 - 由于缺乏正式的 OOP 结构,这可能没有意义。

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

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

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

我祝你好运,我很想看看你能想出什么!

编辑:

事后想想,也许只掌握一些 OOP 技术而不进行完整的 MVC 实现可能有助于解决您的问题 - 如果您可以使用接口实现适当的多态层次结构,那么您就已经走了很长一段路了。达到代码重用目标的方法。

另一个 stackoverflow 涉及在 Ansi C 中实现 OOP,这是一本有趣的读物,但链接到相同的 pdf: 你能用 C 编写面向对象的代码吗?