How can I make method chaining fluent in C?

Deadly 提交于 2021-02-05 05:46:06

问题


There is an existing C API that looks like this:

//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;
}

The first parameter is always a pointer to the instance, and the functions that transform the instance always return it as a pointer.

I assume this was done to support some kind of Method Chaining?

Method Chaining makes sense in languages when the functions exist as methods inside the scope of the object. Given the API in its current state, I'm left using it like this:

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

Are there any techniques I can use in C to get the same fluidity as in other languages?


回答1:


There is no syntax trick in C to achieve method chaining as may be used in some other languages. In C, you would write separate function calls, passing the object pointer to each function:

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

The same can be done with method calls in C++ and other OOP languages:

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

With the above APIs, and assuming each method returns the this object, the method chaining syntax looks like this:

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

Whether this is more readable than the separate statements is a matter of taste and local coding conventions. I personally find it cumbersome and harder to read. There is a small advantage in terms of code generation: the object pointer does not need to be reloaded from a local variable for the next call. This minuscule optimisation hardly justifies the chaining syntax.

Some programmers try and make it more palatable this way:

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

Again, a matter of taste and coding conventions... But the C equivalent definitely looks awkward:

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

And there is no easy way to reorganise this chain into some more readable.

Note that some of the most ancien C library functions can be chained too:

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

Can be reorganised into:

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

But a safer and much preferred approach is this:

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

For more information, you might want to read this:

Marco Pivetta (Ocramius): Fluent Interfaces are Evil

Note also that if the C object has function pointer members for these calls, all of the above syntaxes could be used, but the object pointer must still be passed as an argument. The function pointers are usually grouped in a structure to which a pointer is stored in the object, mimicking the implementation of C++ virtual methods, making the syntax slightly heavier:

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);

Chaining these is possible too, but for no real gain.

Finally, it should be noted that method chaining does not allow for explicit error propagation. In OOP languages where chaining is idiomatic, exceptions can be thrown to signal errors in a more or less palatable way. In C the idiomatic way to handle errors is to return an error status, which conflicts with returning a pointer to the object.

As a consequence, unless the methods are guaranteed to succeed, it is advisable to not use method chaining and perform iterative tests instead:

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 */
}



回答2:


As far as I know there is no nice way to implement OOP style function chaining in c. You can implement chaining like behaviour using function pointers. Anyway this makes C code more complex.

Here is an example:

#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;

Use chaining like this

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

Normally you would simply write in c function style something like

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");

If you can ensure that there is only one scanner active at a time you can use a global to store scanner object reference. Now you don't have to pass scan object to every chain function.



来源:https://stackoverflow.com/questions/41968044/how-can-i-make-method-chaining-fluent-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!