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 type
}
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?
Fluent Ribbon User Interface The Fluent interface makes it easier to find powerful features by replacing menus and toolbars with a Ribbon that organizes and presents capabilities in a way that corresponds more directly to how people work.
A fluent interface is an object-oriented API that depends largely on method chaining. The goal of a fluent interface is to reduce code complexity, make the code readable, and create a domain specific language (DSL). It is a type of method chaining in which the context is maintained using a chain.
Fluent interface, first coined as a term by Martin Fowler, is a very convenient way of communicating with objects in OOP. It makes their facades easier to use and understand. However, it ruins their internal design, making them more difficult to maintain.
A fluent interface provides an easy-readable, flowing interface, that often mimics a domain specific language. Using this pattern results in code that can be read nearly as human language.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With