Case Study of Writing the Extensible Visitor Pattern

From APIDesign

(Difference between revisions)
Jump to: navigation, search
Current revision (06:46, 4 November 2009) (edit) (undo)
(Have You Ever Wondered...?)
 
(7 intermediate revisions not shown.)
Line 1: Line 1:
-
I liked this section, too. Practical, example-based ways of applying the concept learned in the book. Awesome.
+
== [[Have You Ever Wondered]]...? ==
-
I'd almost recommend that this and the previous section should be in their own part of the book. "Part IV: Experiences" or something.
+
Do you wonder whether knowledge of proper design patterns makes you good API designer? Yes and no. Of course knowing regular design patterns simplifies communication, understanding, etc. However there are hidden catches. Not every design pattern belongs among [[APIDesignPatterns]] - it may not be ready for evolution. For example the well known [[wikipedia::Visitor_pattern|Visitor pattern]] is really not evolvable easily as analysed by [[Case Study of Writing the Extensible Visitor Pattern|Chapter 18]].
-
Paragraph 2: "in the tool apt" doesn't make sense. If "apt" is the name of the tool, it needs to be set apart in quotes or in typeface.
+
== [[Visitor|Tripple Dispatch]] ==
-
"traversing hierarchies of heterogeneous data structures" (note the spelling of heterogeneous) is quite a chunk of text. Any way to make that a little less haughty-sounding?
+
{{:Visitor}}
-
 
+
-
--[[User:Dmkoelle|Dmkoelle]] 02:47, 23 April 2008 (UTC)
+
-
 
+
-
"would keep all kinds of compatibility, see expr, based on generics": what is "expr"? Looks like a link is missing.
+
-
 
+
-
Why are most of the classes in the visitor examples static?
+
-
 
+
-
In the visitor pattern, it is common for the method in the element classes (<tt>Plus</tt>, etc.) which takes a visitor instance to be named <tt>accept</tt>, not <tt>visit</tt>.
+
-
 
+
-
The "Old visitor used on new exceptions" message in the abstract <tt>Visitor</tt> doesn't make sense to me, perhaps "new elements" is what you meant.
+
-
 
+
-
Common Java code conventions advice against underscores in identifiers. Moreover, you talk about "Valid10" and "Visitor70" in the text. Please make this consistent, ideally by removing the underscores.
+
-
 
+
-
"exception subtype" should probably be "element subtype".
+
-
 
+
-
Perhaps I'm just missing something, but we fact that <tt>visitMinus</tt> visits the children elements by default looks wrong to me. The visitor pattern describes the visitor as an interface with no default behavior. The actual behavior is implemented by the visitor implementation. But here, you have made an arbitrary choice for the behavior (what if I want to visit the numbers in postorder?), and worse, only for a part of the tree.
+
-
 
+
-
"the client visitor that just called the method" looks unclear. How about "the <tt>Visitor</tt> which was passed to <tt>Expression.visit</tt>"?
+
-
 
+
-
--[[User:AndreiBadea|AndreiBadea]] 12:44, 23 April 2008 (UTC)
+

Current revision

Have You Ever Wondered...?

Do you wonder whether knowledge of proper design patterns makes you good API designer? Yes and no. Of course knowing regular design patterns simplifies communication, understanding, etc. However there are hidden catches. Not every design pattern belongs among APIDesignPatterns - it may not be ready for evolution. For example the well known Visitor pattern is really not evolvable easily as analysed by Chapter 18.

Tripple Dispatch

The Chapter 18 discusses various approaches to implement visitor in an evolvable API. The learning path itself is important, but to stress the important point, here is the code for the final solution of the expression problem:

Code from Language.java:
See the whole file.

public interface Expression {
    public abstract void visit(Visitor v);
}
public interface Plus extends Expression {
    public Expression getFirst();
    public Expression getSecond();
}
public interface Number extends Expression {
    public int getValue();
}
 
public static abstract class Visitor {
    Visitor() {}
 
    public static Visitor create(Version10 v) {
        return create10(v);
    }
 
    public interface Version10 {
        public boolean visitUnknown(Expression e);
        public void visitPlus(Plus s);
        public void visitNumber(Number n);
    }
 
    public abstract void dispatchPlus(Plus p);
    public abstract void dispatchNumber(Number n);
}
 

The solution is using Java interfaces to represent expression elements and yet it is fully evolvable (one can always define new expression element interface). Visitor is not just a single class, but one Java interface and one Java final class. Visitors written using this style are easily extensible. For example when adding support for Minus operation in version 2.0 one just adds:

Code from Language.java:
See the whole file.

/** @since 2.0 */
public interface Minus extends Expression {
    public Expression getFirst();
    public Expression getSecond();
}
 
public static abstract class Visitor {
    Visitor() {}
    /** @since 2.0 */
    public static Visitor create(Version20 v) {
        return create20(v);
    }
 
    /** @since 2.0 */
    public interface Version20 extends Version10 {
        public void visitMinus(Minus m);
    }
 
 
    /** @since 2.0 */
    public abstract void dispatchNumber(Number n);
}
 

This is the most flexible solution. It uses a form of tripple dispatch - e.g. the actual visit method called (on some implementation of the visitor interface) is determined by the expression type, version of the expression language and implementation of the visitor interface:

Code from Language.java:
See the whole file.

static Visitor create20(final Visitor.Version20 v) {
    return new Visitor() {
        @Override
        public void dispatchPlus(Plus p) {
            v.visitPlus(p);
        }
 
        @Override
        public void dispatchNumber(Number n) {
            v.visitNumber(n);
        }
 
        @Override
        public void dispatchMinus(Minus m) {
            v.visitMinus(m);
        }
 
        @Override
        public void dispatchReal(Real r) {
            v.visitUnknown(r);
        }
    };
}
 

This solution to the expression problem is another realization of the general principle to separate ClientAPI from ProviderAPI. Client part of the visitor can be enriched by new dispatch methods with every version. The interface part of visitor is immutable, a fixed point, which stays the same for those who implement it. Each version defines its own unique interface (according to the list of expression types it supports). The internals of the API then bridge the dispatch methods to appropriate interface visit methods.

TheAPIBook has been written prior to JDK 1.8 being mainstream. It is thus fair to ask does anything change with JDK8 on the recommendations presented here?

JDK8 and Default methods

Can the extensible visitor pattern be simplified by usage of default methods? No, not really. Usage of default methods only increases fuzziness and goes against cluelessness of the users of the API. A general example can be found at the default methods page, here is its application to the visitor case:

When introducing the version 2.0 of the interface, one might be tempted to add the visitMinus method into an existing interface:

public interface Version10 {
    public boolean visitUnknown(Expression e);
    public void visitPlus(Plus s);
    public void visitNumber(Number n);
 
    public default void visitMinus(Minus s) {
      visitUnknown(s);
    }
}

however that hurts the clarity of the specified version. When I see an implementation of the interface without the visitMinus method being overriden, what does that mean? Does that mean that the implementation was written against version 1.0? Or that it has been written against version 2.0, but the default method implementation is fine? This makes a difference as the arithmetica example demonstrates!

As such rather create new pure interface for each version of your language/expressions rather than relying on default methods. Don't be lazy, don't increase fuzziness of your API!

<comments/>

Personal tools
buy