As part of the API, Mark implementing method chaining. This allows for very compact code that is still quite easy to read. For example, you could produce this XML:
<?xml version="1.0" encoding="UTF-8"?>
<car year="2008">
<model>Prius</model>
</car>
with just this code::
WAX wax = new WAX(WAX.Version.V1_0);
wax.start("car").attr("year", 2008)
.start("model" ).text( "Prius").close();
Much of the compactness comes from the fact that each method called returns the WAX object, allowing the developer to chain method calls together.
As we were discussing the code, the topic of incorrect ordering of calls came up. For example, what happens if you try to call the attr method to create an attribute after already creating some text inside the element with the text method?
Because WAX is a streaming API, you can't 'go back' and add an attribute in the start element tag after having already 'moved on' to putting text in the body of the element. For example, what if I tried to create an attribute to the model of the car after putting text in it, like this:
WAX wax = new WAX(WAX.Version.V1_0);
wax.start("car").attr("year", 2008)
.start("model" ).text( "Prius").attr( "trim", "GT" ).close();
WAX detects this situation and throws an exception at runtime, which it should do. But I'm a fan of static type checking. I love having tools do as much checking for me as possible. So I suggested to Mark that he create a set of interfaces. Each interface would contain only the methods that make sense at the current point in the output stream. This would allow the compiler to catch invalid method invocations. Even better, modern IDEs like IDEA would flag the method invocation visually even before compilation.
For example, if we arrange for the text method to return an interface which does not have an attr method, then the compiler will catch it and the IDE will flag the error visually. For example, in IDEA, the error is flagged like this:
Now we get the compactness of runtime exceptions (which Mark really likes) and static checking (which I really like). I did some looking around and couldn't find a design pattern that really fits. A number of people suggested that this was an example of a domain-specific language (DSL). But to my thinking, something that deserves the moniker of 'language' should be more involved than simply constructing some interfaces so that static checking is applied.
But whatever you call it, I think it's a useful design pattern than can be applied in other situations to make for clean, compact, yet statically checked APIs.
4 comments:
hi there,
nice tip. will try to remember when I get a chance.
BR,
~A
See Fowler's "Fluent Interface" Bliki entry, although he isn't quite as explicit about returning different interface types. Guice's Binder API does exactly what you describe. Depending on which method you call in the binder, it returns different interfaces that restrict how you can chain things together legally. Guice calls it "embedded domain-specific language" or EDSL.
This technique is discussed in the 2006 OOPSLA paper "Evolving an Embedded Domain-Specific Language in Java" by Steve Freeman and Nat Pryce.
What a great web log. I spend hours on the net reading blogs, about tons of various subjects. I have to first of all give praise to whoever created your theme and second of all to you for writing what i can only describe as an fabulous article. I honestly believe there is a skill to writing articles that only very few posses and honestly you got it. The combining of demonstrative and upper-class content is by all odds super rare with the astronomic amount of blogs on the cyberspace.
Post a Comment