Visitor

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Let's talk about JDK8 changes)
Line 13: Line 13:
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.
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|Visitor18]] on the recommendations presented here?
+
[[TheAPIBook]] has been written prior to [[JDK]] 1.8 being mainstream. It is thus fair to ask [[Visitor18|does anything change with JDK8]] on the recommendations presented here?
<comments/>
<comments/>

Revision as of 13:06, 2 March 2018

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?

<comments/>

Personal tools
buy