c - 如何使 C 中的方法链流畅?

标签 c macros api-design fluent-interface method-chaining

现有的 C API 如下所示:

//data
typedef struct {int properties;} Widget;

//interface
Widget* SetWidth(Widget *const w, int width){
    // ...
    return w;
}
Widget* SetHeight(Widget *const w, int height){
    // ...
    return w;
}
Widget* SetTitle(Widget *const w, char* title){
    // ...
    return w;
}
Widget* SetPosition(Widget *const w, int x, int y){
    // ...
    return w;
}

第一个参数始终是指向实例的指针,并且转换实例的函数始终将其作为指针返回。

我认为这样做是为了支持某种Method Chaining

当函数作为方法存在于对象范围内时,方法链接在语言中才有意义。考虑到 API 的当前状态,我只能这样使用它:

int main(void) {
    Widget w;
    SetPosition(SetTitle(SetHeight(SetWidth(&w,400),600),"title"),0,0);
}

我可以在 C 语言中使用任何技术来获得与其他语言相同的流畅性吗?

最佳答案

C 中没有像其他语言中可能使用的语法技巧来实现方法链接。在 C 中,您可以编写单独的函数调用,将对象指针传递给每个函数:

Widget *w = getWidget();
widgetSetWidth(w, 640);
widgetSetHeight(w, 480);
widgetSetTitle(w, "Sample widget");
widgetSetPosition(w, 0, 0);

对于 C++ 和其他 OOP 语言中的方法调用也可以完成同样的操作:

Widget *w = getWidget();
w->SetWidth(640);
w->SetHeight(480);
w->SetTitle("Sample widget");
w->SetPosition(0, 0);

使用上述 API,并假设每个方法返回 this 对象,方法链接语法如下所示:

getWidget()->SetWidth(640)->SetHeight(480)->SetTitle("Sample widget")->SetPosition(0, 0);

这是否比单独的语句更具可读性取决于品味和本地编码约定。我个人觉得它很麻烦,更难读。在代码生成方面有一个小优点:下次调用时不需要从局部变量重新加载对象指针。这种微小的优化很难证明链接语法是合理的。

一些程序员尝试通过这种方式让它变得更容易接受:

getWidget()
 -> SetWidth(640)
 -> SetHeight(480)
 -> SetTitle("Sample widget")
 -> SetPosition(0, 0);

同样,这是一个品味和编码约定的问题......但是 C 语言的等效版本肯定看起来很尴尬:

Widget *w = widgetSetPosition(widgetSetTitle(widgetSetHeight(widgetSetWidth(getWidget(), 640), 480), "Sample widget"), 0, 0);

并且没有简单的方法可以将这个链重新组织成更具可读性。

请注意,一些最古老的 C 库函数也可以链接:

const char *hello = "Hello";
const char *world = "World";
char buf[200];
strcpy(buf, hello);
strcat(buf, " ");
strcat(buf, world);
strcat(buf, "\n");

可以重组为:

strcat(strcat(strcat(strcpy(buf, hello), " "), world), "\n");

但更安全且更受青睐的方法是:

snprintf(buf, sizeof buf, "%s %s\n", hello, world);

有关更多信息,您可能需要阅读以下内容:

Marco Pivetta (Ocramius): Fluent Interfaces are Evil

另请注意,如果 C 对象具有用于这些调用的函数指针成员,则可以使用上述所有语法,但仍必须将对象指针作为参数传递。函数指针通常被分组在一个结构体中,指针存储在对象中,模仿C++虚拟方法的实现,使得语法稍微重一些:

Widget *w = getWidget();
w->m->SetWidth(w, 640);
w->m->SetHeight(w, 480);
w->m->SetTitle(w, "Sample widget");
w->m->SetPosition(w, 0, 0);

将它们链接起来也是可能的,但没有真正的好处。

最后,应该注意的是,方法链接不允许显式错误传播。在 OOP 语言中,链接是惯用的,可以抛出异常以或多或少令人满意的方式发出错误信号。在 C 中,处理错误的惯用方法是返回错误状态,这与返回指向对象的指针相冲突。

因此,除非保证方法成功,否则建议不要使用方法链并执行迭代测试:

Widget *w = getWidget();
if (SetWidth(w, 640)
||  SetHeight(w, 480)
||  SetTitle(w, "Sample widget")
||  SetPosition(w, 0, 0)) {
    /* there was an error, handle it gracefully */
}

关于c - 如何使 C 中的方法链流畅?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41968044/

相关文章:

c - 代码结果说明

java - 为什么某些 API(如 JCE、JSSE 等)通过单例映射提供可配置属性?

api-design - 命名异步方法的最佳方式

macros - Racket with-hash 宏和重命名转换器

c - C 宏的作用域规则

javascript - 使用 POST 数据的数组键或数组值的 RESTful api

c - 程序堆内存的初始大小

c - 为什么 getline() 仅返回 1 而不是返回它读取的字符 no

c - 不确定堆分配创建的未初始化值在哪里

C++11 自动转换