Sorry for this long question, it is flagged wiki since I'm asking for something that might not have a very concrete answer. If it is closed, so be it.
My main question is this:
How would you write a fluent interface that isn't fully defined in the base classes, so that programs that uses the fluent interfaces can tack on new words inside the existing structure, and still maintain a guiding interface so that after a dot, the intellisense only lists the keywords that actually apply at this point.
I'm on my 3rd iteration of rewriting my IoC container. The 2nd iteration was to improve performance, this third iteration will be to solve some extensibility problems, and separation-problems.
Basically, the problem with extensibility is that there is none. I recently wanted to use a service that had a lifetime, and after the lifetime had expired, resolve a fresh copy. For instance, read a config file every minute, but not more often. This was not supported by my current IoC solution, but the only way to add it was to go into the base class library and add support for it there. This means to me that I've failed to build an extensible class library. In all fairness, I didn't intend to build extensibility into it, but then I didn't fully appreciate how much pain it would be to go in and add something like this later.
I'm looking at my fluent interface for configuration, and since I want to build full extensibility into the interface as well (or get rid of it, which I'm loath to do) I need to do things differently.
As such, I need your opinion. I have very little experience actually using fluent interfaces, but I've seen quite a bit of code that uses them, and as such there is one obvious benefit right out of the box:
- Code that uses fluent interfaces are usually very easy to read
In other words, this:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.For.Policy("DEBUG")
.With.Scope.Container()
.And.With.Parameters
.Add<String>("connectionString", "Provider=....")
.Add<Boolean>("optimizeSql", true);
is easier to read than this:
ServiceContainer.Register(typeof(ISomeService), typeof(SomeService),
"DEBUG", ServiceScope.Container, new Object[] { "Provider=...", true });
So readability is one issue.
However, programmer guidance is another, something which isn't easily understood by reading existing code, on the web or in an editor.
Basically, when I type this:
ServiceContainer.Register<ISomeService>()
.From.|
^-cursor here
and then intellisense will show the available resolution types. After I've picked that one, and write:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.For.|
then I only get things available after the "For" keyword, like "Policy" and such.
However, is this a big issue? Have fluent interfaces you've used been like this? The obvious cop-out to define the interface is to make a class, or an interface, with all the keywords, and everything, so that intellisense after each comma contains everything, but this could also lead to this being legal (as in, it compiles) code:
ServiceContainer.Register<ISomeService>()
.From.ConcreteType<SomeService>()
.From.Delegate(() => new SomeService())
.From.With.For.Policy("Test");
so I'd like to structure the fluent interfaces such that after you've specified how to resolve a service, you cannot do that again.
- In other words, fluent interfaces are very easy to use, since they guide you towards what you can do.
But is this typical? Since I want to be able to add a bunch of these keywords, like the type of resolver (ConcreteType, Delegate, etc.), the type of scope (Factory, Container, Singleton, Cache, etc.) as extension methods, so that programs can define their own ways to do this without having to go in and change the base classes, it means I'll need to provide interfaces for all the intermediate stops, and let the actual important keywords be. The implementation for those keywords then have to pick one intermediate-stop-interface to return, as appropriate.
So it looks like I need to define an interface for:
- xyz.From.
xyz.From.<Resolver here>.
<Resolver here>.With.
<Resolver here>.For.
etc. but that looks fragmented to me.
Can anyone with experience with fluent interfaces go back and read my quoted answer near the top and try to give me a short answer?