Trying to understand how to create fluent interfaces, and when to use them
How would one create a fluent interface instead of a more tradition approach? Here is a traditional approach:
Interface:
interface IXmlDocumentFactory<T>
{
XmlDocument CreateXml() //serializes just the data
XmlDocument CreateXml(XmlSchema schema) //serializes data and includes schema
}
interface IXmlSchemaFactory<T>
{
XmlSchema CreateXmlSchema() //generates schema dynamically from typ开发者_C百科e
}
Usage:
var xmlDocFactory = new XmlDocumentFactory<Foo>(foo);
var xmlDocument = xmlDocFactory.CreateXml();
//or...
var xmlDocFactory = new XmlDocumentFactory<Foo>(foo);
var xmlSchemaFactory = new XmlSchemaFactory<Foo>();
var xmlDocument = xmlDocFactory.CreateXml(xmlSchemaFactory.CreateXmlSchema());
I'd like to be able to say:
var xmlDocument = new XmlDocumentFactory<Foo>(foo).CreateXml().IncludeSchema();
//or...
var xmlDocument = new XmlDocumentFacotry<Foo>(foo).CreateXml();
Lastly, is this situation a good fit for fluent interfaces? Or would a more traditional approach make more sense?
The key to making an interface fluent is to ensure the methods all return instances of the interface itself, or some other object that also implements the interface that can continue the processing.
So in your case each of your IXmlDocumentFactory methods has to return an IXmlDocumentFactory so you can continue to call. The final method, if there is one, returns the type you really want?
It makes for very readable code but one thing that still gives me a bit of the willies is return-checking. You have to make very sure that null's can't be returned or else the next 'fluent call' will fail.
Three things are important in my mind:
1.) There is an initial method that returns the fluent interface you are going to work with
2.) Each method in the class that implements your fluent interface returns itself so you can continue chaining - these are the real fluent methods.
3.) There is a final method that returns the type that you really want to build.
In your example since you only have two methods, its borderline useful - usually you would have more methods in a fluent interface. Alternatively (which I personally prefer) you can offer both options in your API: A fluent API and a more traditional API (i.e. with optional parameters).
In your case would do something like this:
Edited to respond to comment.
public interface IXmlDocumentFactory<T>
{
XmlDocument Create(); //serializes just the data
IXmlDocumentFactory<T> WithSchema(XmlSchema schema); //serializes data and includes schema
}
public class DocumentFactory<T> : IXmlDocumentFactory<T>
{
private XmlSchema _schema;
private T _data;
public DocumentFactory(T data)
{
_data = data;
}
public IXmlDocumentFactory<T> WithSchema(XmlSchema schema)
{
_schema = schema;
return this;
}
public XmlDocument Create()
{
if (_schema != null)
{
//..use schema/data
return new XmlDocument();
}
else //use data
return new XmlDocument();
}
public static IXmlDocumentFactory<T> From(T data)
{
return new DocumentFactory<T>(data);
}
}
Then you can use it like this:
var xmlDoc = DocumentFactory<int>.From(42)
.WithSchema(new XmlSchema())
.Create();
var xmlDoc = DocumentFactory<int>.From(42)
.Create();
IMO, fluent APIs do have their value. Fluently configuring a component feels more like an English sentence, easily read from start to finish. The intent of a developer is more easily comprehended.
For examples of implementation, please refer to such projects as Autofac, Moq and Fluent NHibernate
精彩评论