Fluent programming interfaces allow the code-writer to do a lot of functions in a small amount of code space. There are a lot of nuances to the concept, but one basic concept is that mutator methods should return a reference to the parent object instead of returning null. A mutator is a method which changes the object in some way. The result is that the programmer can daisy-chain mutators in order to set a large number of variables with one logical statement.
In my opinion, fluent interfaces make perfect sense when generating XML. In a certain way, it seems logical that the XML-generating code would bear some resemblance to the XML it generates. That is to say, since XML reads from top to bottom, in a hierarchical fashion, with inner tags typically indented, it seems logical that the code should read from top to bottom, with inner-tag-generating statements indented. To go further, it makes so much sense to me, that frankly to design an XML API in any other way seems foolish and myopic.
The first of Microsoft's XML APIs is not fluent. Nevertheless, if you try hard enough, you can fake it. Here is a first example, from namespace System.XML; notice that in order to use this API in a fluent way I have to use the .ParentNode member over and over and over in order to get back to the XML element that I want:
rootElement
.AppendChild(doc.CreateElement("Services"))
.AppendChild(doc.CreateElement("Name"))
.AppendChild(doc.CreateTextNode("TestWMS"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Title"))
.AppendChild(doc.CreateTextNode("Company Test WMS"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Abstract"))
.AppendChild(doc.CreateTextNode("WMS Interface To Other Data Service"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("KeywordList"))
.AppendChild(doc.CreateElement("Keyword"))
.AppendChild(doc.CreateTextNode("Map Company"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Keyword"))
.AppendChild(doc.CreateTextNode("Forecast"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Keyword"))
.AppendChild(doc.CreateTextNode("MapPoint Server"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Keyword"))
.AppendChild(doc.CreateTextNode("MapTile Server"))
.ParentNode
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("ContactInformation"))
.AppendChild(doc.CreateElement("ContactPersonPrimary"))
.AppendChild(doc.CreateElement("ContactPerson"))
.AppendChild(doc.CreateTextNode("Nicholas Rinard"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("ContactOrganization"))
.AppendChild(doc.CreateTextNode("Company Name, LLC"))
.ParentNode
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("Fees"))
.AppendChild(doc.CreateTextNode("$.02 per request"))
.ParentNode
.ParentNode
.AppendChild(doc.CreateElement("AccessConstraints"))
.AppendChild(doc.CreateTextNode("Server is loan-limited"))/**/
;
Also, after all that work to make it fluent-like, it still fails, because this strategy does not allow you to set attributes, or some other XML features. This interface left me frustrated and wanting something better. This interface was starting to force me to write ugly, non-fluent, barely readable code such as can be found so many places on the intertubes.
The second interface, from System.Xml.Linq, is a lot better. It could be described as fluent or almost fluent. I choose not only to use this API, but to use it in a fluent way. Compare the above code to this code; this code is shorter, easier to read and understand, and can do all the tricks such as setting XML attributes.
XDocument doc = new XDocument(
new XDeclaration("1.0", "UTF-8", "yes"),
new XElement("WMS_Capabilities",
new XElement("Services",
new XElement("Name", new XText("TestWMS"))
),
new XElement("Title", new XText("Company Test WMS")),
new XElement("Abstract", new XText("WMS Interface To Dataserver")),
new XElement("KeywordList",
new XElement("Keyword", new XText("Test Company")),
new XElement("Keyword", new XText("Forecast")),
new XElement("Keyword", new XText("MapPoint Server")),
new XElement("Keyword", new XText("MapTile Server"))
),
new XElement("ContactInformation",
new XElement("ContactPersonPrimary",
new XElement("ContactPerson", new XText("Nicholas Rinard")),
new XElement("ContactOrganization", new XText("Company Name"))
)
),
new XElement("Fees", new XText("$.02 per request")),
new XElement("AccessConstraints", new XText("The server is load balanced and subject to pricing"))
)
);
Using the API in this way makes the code resemble the eventual XML: it starts with a WMS_Capabilities tag, which contains the tags at one-level indentation; and so on with deeper indentation. I'm not sure whether this counts technically as a fluent interface, because the daisy-chaning here is done in a constructor instead of with mutator methods. Nevertheless, it serves the same purpose: it is clean and clear, fully funtional, pretty, and easy to understand. It is elegant, and that's how code should be, when it can be.
No comments:
Post a Comment