MVC在纯C中实现

xxb16uws  于 2022-12-26  发布在  其他
关注(0)|答案(3)|浏览(228)

有没有人知道有什么资源提供了一个直接的例子,尝试在C环境中做模型视图控制器设计模式?特别是嵌入式系统?
澄清一下,我对C#、C++、Objective-C、Java、PHP或任何更高级的语言示例都不感兴趣。我想知道人们对如何使用纯ansi C99甚至C89来处理这种设计模式有什么想法。也许这在C中甚至没有意义,因为缺乏正式的OOP语言构造?
一些背景:我和我的同事正在开发由基于Arm的PSoC芯片供电的嵌入式系统。我们控制着硬件设计和PCB,并且必须进行软件开发以增强产品的功能集。我们的模型通常包括从产品中的模数转换器采集数据。视图可能是由嵌入式Web服务器供电的网页,或者带有电容式触摸控制的LCD屏幕。我们的控制器或多或少是管理这两个代码区域之间关系的粘合逻辑。我们有许多不同的产品和变体需要支持,因此代码重用是可取的。
不是寻找高度详细或企业级的框架。而是简单的例子,阐明分离编程关注点的好策略,但偏向于在较低级别的C中找到的习惯用法,例如结构,函数,事件驱动逻辑和一种在C中有意义的抽象消息传递。
由于硬件的特性,我们需要使用C语言,并且必须自己引导很多东西。在某些情况下,我们可以访问操作系统,而在其他情况下,我们只需直接编译到处理器,并从主函数开始。所有这些都非常原始,但我们正在寻找允许代码重用的方法,并希望加快软件工程过程。

jjjwad0x

jjjwad0x1#

首先,让我们从这句话开始:
也许这在C语言中甚至没有意义,因为缺乏正式的OOP语言结构?
我完全不同意这种说法,我将在后面展示;仅仅因为C没有像“class”这样的漂亮关键字并不意味着您不能完成同样的事情。
我将尽我所能一步一步地介绍这个问题--按照你的问题流程。

C语言中的OOP

根据您问题的措辞,我怀疑您对OOP概念有相当不错的理解(您甚至从模式的Angular 来思考,甚至对这些模式在您的特定场景中将如何发挥作用有很好的想法)-所以让我用“30秒或更少”来做一个“C语言中的OOP”教程。
一旦你掌握了窍门,你就会意识到你能做的比我在这里展示的要多得多--但我只是想给予你尝一尝。

101

首先,我们将从一个基本的“类”开始(请与我一起讨论这个问题):

燃油小时:

typedef struct Foo Foo;
Foo * FooCreate(int age, int something);
void FooSetAge(Foo * this, int age);
void FooFree(Foo * this);

Foo_内部.h:(你马上就会明白我为什么这么说)

#include "Foo.h"

struct Foo { 
     int age;
     int something;
};

void FooInitialize(Foo * this, int age, int something);

食物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中有什么,因为实现细节在“internal”头文件中,而不是“public”头文件中。
  • 我们实现“示例方法”就像OOP语言一样--除了我们必须手动传递“this”指针--其他语言会为您这样做--但这不是什么大问题。
  • 我们有“属性”。同样,其他语言会用更好的语法 Package 属性getter/settings-但它们在幕后真正做的只是为你创建一些getter/setter方法,并将对“属性”的调用转换为方法调用。

继承

那么,如果我们想要一个Foo的“子类”(它只添加了额外的功能),但可以替代Foo,该怎么办呢?

脚部子类h:

typedef struct FooSubclass FooSubclass;
FooSubclass * FooSubclassCreate(int age, int something, int somethingElse);
void FooSubclassSetSomethingElse(FooSubclass * this, int somethingElse);
void FooSubclassFree(FooSubclass * this);

脚部子类_内部.h:

#include "FooSubclass.h"
#include "Foo_Internal.h"

struct FooSubclass { 
     Foo base;
     int something;
};

void FooSubclassInitialize(FooSubclass * this, int age, int something, int somethingElse);

脚部子类.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语言中很简单--实际上只是创建一个 Package 器方法,它实际上调用一个由函数指针引用的函数。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,可能还有其他控制器。

xpszyzbs

xpszyzbs2#

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

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

e4eetjau

e4eetjau3#

我对人们可能会有什么建议很感兴趣,但我认为你已经切中要害了--它可能没有意义,因为缺乏正式的OOP结构。
然而,有可能将OOP概念引入ANSI-C;我有一个链接到这个PDF有一段时间了,虽然我从来没有真正吸收它(因为没有接触到C在我的日常工作),它肯定看起来是富有成效的:
http://www.planetpdf.com/codecuts/pdfs/ooc.pdf
这是一项艰巨的任务,但是你最终可以想出一些模板/框架工作,这将使编写进一步的MVC风格开发变得非常容易;但我想代价是--你能负担得起时间吗?嵌入式平台的局限性是否使得MVC提供的清晰性的好处被性能/内存保护/垃圾收集的缺乏,当然还有不得不重新发明轮子的纯粹努力所抵消?
我祝你好运,我会很有兴趣看看你想出了什么!
编辑:
作为一个事后的想法,也许仅仅拥有一些OOP技术而不去做一个完整的MVC实现可能会帮助解决你的问题-如果你能用接口实现一个适当的多态层次结构,你就已经在代码重用的目标上走了很长的路。
这另一个stackoverflow处理在Ansi C中实现OOP,这是一个有趣的阅读,但链接到相同的pdf:Can you write object-oriented code in C?

相关问题