【问题标题】:How can I make method chaining fluent in C?如何使方法链接在 C 中流利?
【发布时间】:2017-06-17 12:10:32
【问题描述】:

现有的 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 中使用任何技术来获得与其他语言相同的流动性吗?

【问题讨论】:

  • 并非如此。流体接口一般只存在于 OO 语言中。
  • 它在 C 中用处不大,原因有二:一,没有例外。返回值通常用于指示成功或失败。二、手动内存管理和无RAII。
  • C 没有方法。它有功能。你的问题没有意义。
  • @Barmar C can be OOP。种。
  • @Barmar:流利,不流畅。除非你想通过管道泵送你的接口。

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


【解决方案1】:

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 中获得类似的东西。这一切似乎只是一种冗长的说法,你不能。
  • @Barmar:fluid 是 OOP 语言中选择的术语,但它具有超出此范围的通用含义。我在暗示方法链接语法并没有恕我直言,流动性很大。这只是我的意见,所以我可能跑题了。
  • @Barmar:OP 可能知道,但普通读者、C 程序员可能不知道。
  • 流畅还是流畅?我在上面的评论中打错了,你是说这是真的吗?标签 wiki 和问题中的链接显示了流畅的编码在支持它的语言中的样子。
  • @Barmar:不,我是在推断……流利是这个词,我想我的英语没有我应该说的那么流利。
【解决方案2】:

据我所知,在 c 中实现 OOP 风格的函数链没有很好的方法。您可以使用函数指针实现类似链接的行为。无论如何,这使 C 代码更加复杂。

这是一个例子:

#define SCAN_STR(buf)        str(&SCANER_NAME, buf)
#define SCAN_STR_L(buf, len) strL(&SCANER_NAME, buf,len)
#define SCAN_NUM(number)        num(&SCANER_NAME, number)

typedef struct StrScanner
{
    const char* curPos;
    bool isError;
    uint32_t parsedCnt;

    StrScanner* (*chr)(StrScanner* scan, int*);
    StrScanner* (*str)(StrScanner* scan, char* buf);
    StrScanner* (*strL)(StrScanner* scan, char* buf, uint32_t len);
    StrScanner* (*num)(StrScanner* scan, int*);
} StrScanner;

像这样使用链接

char myBuf[30];
char my2Buf[30];
int in;

#define SCANER_NAME scan
if(SCANER_NAME.SCAN_STR(myBuf)->SCAN_STR_L(my2Buf, 5)->SCAN_NUM(&in)->isError)
    printf("error\n");
#undef SCANER_NAME

通常你会简单地用 c 函数风格编写类似的东西

char myBuf[30];
char my2Buf[30];
int in;

bool res = str(&scan, myBuf);
res = res && strL(&scan, myBuf2, 5);
res = res && num(&scan, num);
if(!res)
        printf("error\n");

如果您可以确保一次只有一个扫描仪处于活动状态,您可以使用全局来存储扫描仪对象引用。现在您不必将扫描对象传递给每个链函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 2020-11-01
    • 1970-01-01
    相关资源
    最近更新 更多