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,可能还有其他。
无论如何,我希望这会有所帮助......我觉得我现在正在写一本书,所以我会停下来。
如果您想要更多,或者是否有帮助,请告诉我。