2

I'm designing a configurable api and I've seen a few ways to accept options from the caller.

One is using an options object like so:

var options = new MyApiConfigurationOptions {
    Option1 = true,
    Option2 = false
};

var api1 = MyApiFactory.Create(options);

Another is using a configuration function:

var api2 = MyApiFactory.Create(o => {
    o.Option1 = true;
    o.Option2 = false;
});

Is one approach any better/worse/different than the other? Is there any real difference or would it be nice to support both so the caller can use whatever syntax they prefer?

migrated from codereview.stackexchange.com Jan 5 '17 at 18:56

This question came from our site for peer programmer code reviews.

  • Adding a lambda just to initialize some options seems a little bit overkill to me. That being said, there's nothing wrong with either one, but one piece of advice: If you chose one, stick with it throughout your API. It'll make things easier for people who will use it. – Machado Jan 5 '17 at 20:03
4

The function approach, whilst appearing less intuitive at first glance, has a couple of significant advantages:

  1. The function is supplied with the config object, ie the API has total control over the creation of the object. It could have internal only constructors for example, ensuring only the API itself gets to create the object.
  2. The API can support full-blown delayed initialisation. Not only can the API perform lazy initialisation of itself, it gets to then call back to the main code in a delayed fashion for the configuration data. Such an approach can prove really useful when, for example, plumbing together a system via IoC, allowing references to the API object to be injected into various objects and then the configuration supplied to the API afterwards.

It's all too easy to dismiss this approach as "just being fancy for fanciness' sake" or "obscure and pointless". But take the time to understand the benefits and it's easy to see why it can be a powerful (and simple to understand once the concept is understood) mechanism.

Of course, whether you need the function-approach completely depends on your API. If neither of the above points offer any obvious benefits to your code, then more devs will find your API easier to use if you adopt the simpler approach.

6

The version taking a function is obscure and pointless. The only reason to do that is if you need to calculate the options at some later point. The version taking the object is much more readable and actually makes sense.

  • Actually, no. The version taking a function allows arbitrary members to be defined on the anonymous type, allowing more flexibility. – Robert Harvey Jan 5 '17 at 19:07
  • 3
    Ah, sorry. That's a different syntax: new { o.Option1 = true; o.Option2 = false; } – Robert Harvey Jan 5 '17 at 19:09
  • 3
    You could of course simply pass in an anonymous object with arbitrary members in the first case. – DeadMG Jan 5 '17 at 19:15
4

I think it's a matter of taste. I'm a big fan of fluent interfaces so I would rather write something like:

var api1 = MyApiFactory.Create().Option1(true).Option2(true)

That beeing said, I think the configuration function approach has the advantage of keeping the user hands out of your internal types (like MyApiConfigurationOptions) and provides better encapsulation.

  • Upvoting this because fluent interfaces for configuring things are pretty cool. – Machado Jan 5 '17 at 20:04
  • 5
    @Machado I'm not upvoting this because it mixes a factory with a builder which is even more confusing then the weird second example. – t3chb0t Jan 5 '17 at 20:22
  • My point was "you should consider fluent interfaces" not "mix factory and builder" :D sorry if it seems that I'm advising someone to do that. – yorodm Jan 5 '17 at 21:12
  • 1
    @t3chb0t that makes sense. You're right. I'm a simple man, I see fluent interface, I upvote. :) – Machado Jan 6 '17 at 10:47
1

They are very different, and if you're not careful you could trip up your developers.

In the first code snip, the options are read and passed immediately, and they may be applied to the created object instance either immediately or later if there is lazy initialization. But I think it will be pretty clear what options will end up getting set.

In the second code snip, the options may be read later if the created object uses lazy initialization. This means that there might be a closure at play in some cases. Consider this code:

bool myOption = true;
var api2 = MyApiFactory.Create(o => {
    o.Option1 = myOption;
});
myOption = false;
Console.WriteLine(api2.Option1);

What do you think will be output? :) I think it is ambiguous, given only the interface. We'd actually need to look inside the factory to figure out what will happen.

Now of course there is a third option, which so obvious it just might work.

var api2 = MyApiFactory.Create();
o.Option1 = true;
o.Option2 = false;

Of course it is not so elegant. But it is completely unambiguous.

  • Did you mean Console.WriteLine(api2.Option1);? – Bent Jan 5 '17 at 23:52
  • Corrected :) TY – John Wu Jan 5 '17 at 23:54
-1

I prefer taking the object but more from a unit testing and dependency injection point of view. When writing unit test it's easier to create a bunch of mocked objects then to build a bunch of lambdas. In unit test I could write a generic assert where the options.Option1 equals api2.Settings.Option1 and if Option1 is true or false I can call this method. With lambda I need to either pass in a hard coded true or false to compare since the unit test will not have access to the values I set in the lambda.

Also for dependency injection I can register the object to send in but injecting an lambda with what options I want is more confusing to anyone coming after me. Again can do both DI and unit test with lambda but you need to work a little more at it.

Only time I would do the lambda is if inside the static factory you needed to set a property or have an internal constructor that only the factory class could provide.

A lot of this is just developer preference.

-2

Its going to be better to provide the object option in the long run. To be entirely frank, doing the function thing is just being fancy for fanciness' sake.

When you're using the object, you'll be encapsulating code and making debugging easier. A lot of things that are hard to debug (admit it, you've had trouble with it) happen in Lambda statements. I don't like them, and making a change to that underlying code that operates on them is a pain in the butt.

However, with the configuration object you're able to encapsulate code, expand easier, and reuse code later on without having to mess with that. Therefore, in the long run an object is easier to maintain and implement.

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.