Tips for writing fluent interfaces in C# 3

爷,独闯天下 提交于 2019-11-28 03:06:23
Mendelt

On your 4th point;

Yes I think that a complex fluent interface can still be fluent.

I think fluent interfaces are somewhat of a compromise. (although a good one!) There has been much research into using natural language for programming and generally natural language isn't precise enough to express programs.

Fluent interfaces are constructed so that they write like a programming language, only a small subset of what you can express in a natural language is allowed, but they read like a natural language.

If you look at rhino mocks for example the writing part has been complicated compared to a normal library. I took me longer to learn mostly due to the fluent interface but it makes code a lot easier to read. Because programs are usually written once and read a lot more than once this is a good tradeoff.

So to qualify my point a bit. A fluent interface that's complex to write but easy to read can still be fluent.

sbohlen

The single biggest challenge I have experienced as a consumer of fluent interfaces is that most of them aren't really fluent intefaces -- instead they are really instances of what I tend to refer to as 'legible interfaces'.

A fluent interface implies that its primary goal is to make it easy to SPEAK it whereas a legible interface implies that its primary goal is to be easy to READ it. Most fluent interfaces only tend to be ridiculously difficult to code with but conversely incredibly easy to READ later by others.

Assert().That().This(actual).Is().Equal().To(expected).
    Except().If(x => x.GreaterThan(10));

...is alot easier to read later than it is to actually compose in code!

You'll hit a brick when using inheritance along with fluent interfaces because using polymorphic methods break your call chains and you definitely don't want to make your interfaces non-fluent by using ugly casting and paranthesis where they are not needed. I've written an article about a pattern that provides you with a workaround using generic builders and generic extension methods with generic constraints: http://liviutrifoi.wordpress.com/2009/02/16/fluent-interfaces-constraints-at-compile-time/

Moq hides unreleated methods such as equals, ToString and so on to make their fluent interface even easier to use.

Hiding System Object is an article explaining the benefit of doing this.

And on your 2nd and 3rd question;

Three fluent patterns i've noticed

The first uses the using statement (C# 2.0) to run code in a certain context for example:

using(var transaction = new Transaction())
{
  // ..
  // ..
}

This uses the constructor and disposer of Transaction to set up a transaction and then runs the code in this context.

The second does almost the same but with lambda's, this is used a lot in Rhino Mocks for example.

(new Transaction()).Run( () => mycode(); );

The best known fluent interface is to use return types to chain method calls. Mostly methods return this so you can chain calls on the same object. But you can also return different objects to change the context depending on the method called. If you've got an object that can only run in a transaction (sorry can't think of a different example) you can give it a StartTransaction method that returns an initialized transaction where you can run call run and stoptransaction, in pseudocode:

class Runner
{
  Transaction StartTransaction()
  {
    return new Transaction(this);
  }
}

class Transaction
{
  Transaction Run()
  Transaction StopTransaction()
}

where the call looks like

var runner = new Runner();
runner
  .StartTransaction()
  .Run()
  .StopTransaction();

Of course you need to add all kinds of error handling etc.

sam

I too am just jumping on learning how to write a fluent interface for a small app at work. I've asked around and researched a little and found that a good approach for writing a fluent interface is using the "Builder pattern", read more about it here.

In essence, this is how I started mine:

public class Coffee
{
    private bool _cream;
    private int _ounces;

    public Coffee Make { get new Coffee(); }

    public Coffee WithCream()
    {
        _cream = true;
        return this;
    }

    public Coffee WithOuncesToServe(int ounces)
    {
        _ounces = ounces;
        return this;
    }
}

Here's a cross post to a similar question I have for implementing a closure in a fluent interface.

One thing is that you have to account for the morphology of English syntax and ensure that you have not introduced undocumented sequential coupling underneath.

// Snarky employees get a raise.
employees.WhereSnarky().GiveRaise();

vs.

// Depending on implementation, everyone may get a raise.
employees.GiveRaise().WhereSnarky();
Andre Vianna

Sometime ago I had the same doubts you are having now. I've done some research and now I'm writing some posts to help in those topics.

Check it at my blog:

Guidelines to Fluent Interface design in C# part 1

And in the following posts I will cover every one of the points you mentioned.

Best regards André Vianna

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