8
\$\begingroup\$

I wanted to try experimenting with fluent interface design in the C programming language. That's why I wanted to ask you, Dear Code Review users, comments on the way I have implement this simple employee record interface.

Please note that for maximum simplicity I am resorting to as simple data structures as possible.

#include <stdio.h>
#include <assert.h>
/* if this weren't just an example I'd make a linked list */
#define MAX_EMPLOYEE_COUNT 10

/* global this pointer, there is no way around this */
void* this;

typedef struct Employee {
    struct Employee* (*set_salary)(int);
    struct Employee* (*set_employee_id)(int);
    char* name;
    int salary;
    int employee_id;
} Employee;

Employee* do_set_salary(int s)
{
    ((Employee*)this)->salary = s;
    return this;
}

Employee* do_set_employee_id(int id)
{
    ((Employee*)this)->employee_id = id;
    return this;
}

typedef struct Record {
    Employee* (*add_employee)(char*);
    Employee employees[MAX_EMPLOYEE_COUNT];
    int cnt;
} Record;

Employee* do_add_employee(char *n)
{
    Record* this_record = (Record*)this;
    assert(this_record->cnt < MAX_EMPLOYEE_COUNT);
    this_record->employees[this_record->cnt].set_salary = do_set_salary;
    this_record->employees[this_record->cnt].set_employee_id = 
        do_set_employee_id;
    this_record->employees[this_record->cnt].name = n;
    this = &this_record->employees[this_record->cnt];
    this_record->cnt++;
    return this;
}

void init_record(Record* r)
{
    r->cnt = 0;
    r->add_employee = do_add_employee;
}

Record* edit(Record* r)
{
    this = r;
    return r;
}

void print_record(Record* r)
{
    int i;
    for (i = 0; i < r->cnt; i++) {
        printf("%s, salary %d, id %d\n",
               r->employees[i].name,
               r->employees[i].salary,
               r->employees[i].employee_id);
    }
}

int main(void)
{
    Record record;
    init_record(&record);

    /* behold, fluent interface design! */
    edit(&record)->
        add_employee("Alice")->
        set_salary(1500)->
        set_employee_id(10201);

    edit(&record)->
        add_employee("Bob")->
        set_salary(2000)->
        set_employee_id(10202);

    print_record(&record);

    return 0;
}
\$\endgroup\$
2
  • 3
    \$\begingroup\$ The point of fluent is to make the code more readable. This fails on that account. It would be more readable just to use normal methods in this situation (rather than chaining). \$\endgroup\$ Commented Nov 24, 2011 at 14:46
  • 2
    \$\begingroup\$ I'd also add that fluency often drops the verb (add, set, etc.) , or turns it into something more "human" (with, having, etc.) One of the points is to make it sound "right". \$\endgroup\$ Commented Nov 24, 2011 at 16:26

1 Answer 1

3
\$\begingroup\$

I would drop the edit function and make add_employee the entry point to the API. I've written a number of fluent APIs in C++ and found that placing the required arguments (e.g., employee name) in the API entry point invaluable. From the usage point of view, something like:

int main(void) {
    Record db;
    init_record(&db);
    add_employee_to(&db, "Bob")
        ->having_salary(2000)
        ->with_id(10202);
    return 0;
}

The goal of the API is to create a new employee and append them, so make that the entry point. If you can't have a valid employee record with a name, then this becomes a first-class parameter of the entry point. Not sure about the requirements, so it may make sense for ID to be a required parameter as well. I also changed the names to make them More Fluent™.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.