2

What concrete objective advantages do chained functions have vs an options parameter for object initialization?

What do I mean? Well as one example there's a library called dat.GUI that uses the chained function style of setting things up. For example a simple range slider has 4 or 5 parameters.

/**
 * @param {object} object The object with a property you want a GUI for
 * @param {string} property The name of the property to make a GUI for
 * @param {number} min The minimum value
 * @param {number} max The maximum value
 * @param {number} [step] An optional step
 */
gui.add(object, property, min, max, opt_step)

But then there are a several optional settings. You set them by calling chained functions. For example name(someName) sets the label shown on the GUI. It defaults to the property name. onChange(fn) sets a callback when the value changes. listen() tells the GUI to watch for changes to the property.

So, if you want to set all of those you might write

gui.add(someObject, 'someProp', 10, 20).name('foo').onChange(someFunc).listen();

Where as if gui.add took an options object as input you could write something like

gui.add({
  target: someObject,
  property: 'someProp',
  min: 10,
  max: 20,
  name: 'foo',
  onChange: someFunc,
  listen: true,
});

Is this a 6 of one half dozen of another type of situation as in either style is fine or does one style have concrete benefits over the other.

Off the top of my head

  • chained functions are more terse but the options parameter is more readable. I see min: 10 and know that 10 is min and not width or size or numItems etc. Conversely I have to spell out each parameter.

  • options parameters seem more flexible. Can more easily do something like

    gui.add(Object.assign({}, globalOptions, localOptions));
    
  • Code completion seems like a toss up.

    If there are definitions for both the functions and the parameter types including the options parameter then it seems either is similar? Seeing all the options might be useful but completion after gui.add(...). it should show the all the chainable functions so that's probably the same?

  • Are options parameter more forward proof?

    By that I mean if giu.add takes 5 params in a specific order, if it turns out later that parameter #3 is not important but #4 is then it's too late, you can't easily swap the order and make what was parameter #3 optional since all users would have to change their code whereas with options parameters you can?

  • In a strongly typed language if there many options then the options object itself might get large and complicated (lots of initialization for all the fields) whereas with the chained functions you mostly only create what's needed. But, that doesn't fit JavaScript so not an advantage there?

  • The chained functions can be called later individually

    In other words

    const control = gui.add(...);
    ...20 seconds later...
    control.name('foo');
    

    For the options style you'd need something like setOptions(options) method which might not be bad but it might have strange limitations like certain options can only be specified at creation time.

Anything else I'm missing? Is there an objective reason to choose one over the other as in "if situation X use chained functions, if situation Y use an options object" or is it mostly just a style issue?

  • Will the chained method listen() return an object? If not then the methods to initialize the object can also be used to change object state and you don't need additional methods for that. – Kwebble Aug 29 '18 at 21:38
  • An options parameter is a lot less work. Zero or one methods in the public API vs. many setters. – user949300 Oct 29 '18 at 5:48
2

When all the chainable functions of the object are - in essence - just setters for individual parameters (like in your example), then the semantic differences between the two approaches are almost negligible. If that's the case, using optional parameters is more simple, more idiomatic and therefore preferrable.

However, function chaining (or method chaining) makes more sense in the context of builder objects where the methods have more complex semantics. For example, think about a ControlBuilder with a fluent interface, where you can add label text subsequently by calling a method like AppendLine more than once. Or, think about methods where the specific order of calls makes a difference. There are plenty of examples for this in the Wikipedia article about fluent interfaces. That is something you cannot achieve with optional parameters.

0

I have mostly seen the chained-function pattern (usually called the builder pattern in languages like Java where anonymous objects, like the kind you are proposing as the options parameter, are more rare/not idiomatic/impossible to create.

In JavaScript, I've the options parameter more often, but that doesn't mean it's better.

Your comparison of the two is pretty thorough. One thing I would like to add: with the builder pattern, consider that it can make option validation trickier in some scenarios. For example, if you want to allow the specification of enumerated OptionB only if toggleable OptionA has been toggled on.

How do you stop the users of your API from doing something like,

builder.disableOptionA().setOptionB(OptionB.SOME_VALUE).build();

It is possible to do, but doing so seems to involve a multi-state builder which can get rather complicated. Otherwise, the error handling there has to be performed in the build method (which might feel less elegant to the user). As an API user, I like to feel safe when chaining together methods like this.

Having said that, validating the options is a problem for the option parameter pattern as well. At least with the builder pattern, it is possible to enforce rules on the options as you go.

Have you considered a hybrid approach? Have the gui.add() method accept and Options object which can be constructed 'manually', but also provide an OptionsBuilder object for convenience (or inconvenience, depending on who you ask :P).

So both of the following would be allowed:

gui.add(new Options(optionAVal, optionBVal));
gui.add(
    OptionsBuilder.options()
    .optionA(optionAVal)
    .optionB(optionBVal)
    .build()
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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