Suppose we have following classes which we can't change:
interface Base {
void accept(Visitor visitor);
}
class Foo implements Base {
short getShortValue() {
return 1;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Bar implements Base {
int getIntValue() {
return 2;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
interface Visitor {
void visit(Foo foo);
void visit(Bar bar);
}
And we need to implement method:
int getValue(Base base)
There are many possibilities to do it with visitor using some storage object:
int useArray(Base base) {
int[] result = new int[1];
base.accept(new Visitor() {
@Override
public void visit(Foo foo) {
result[0] = foo.getShortValue();
}
@Override
public void visit(Bar bar) {
result[0] = bar.getIntValue();
}
});
return result[0];
}
int useAtomic(Base base) {
AtomicInteger result = new AtomicInteger();
base.accept(new Visitor() {
@Override
public void visit(Foo foo) {
result.set(foo.getShortValue());
}
@Override
public void visit(Bar bar) {
result.set(bar.getIntValue());
}
});
return result.intValue();
}
int useMutable(Base base) {
MutableInteger result = new MutableInteger(0);
base.accept(new Visitor() {
@Override
public void visit(Foo foo) {
result.setValue(foo.getShortValue());
}
@Override
public void visit(Bar bar) {
result.setValue(bar.getIntValue());
}
});
return result.getValue();
}
Or something perverted:
int useException(Base base) {
class GotResult extends RuntimeException {
private final int value;
public GotResult(int value) {
this.value = value;
}
}
try {
base.accept(new Visitor() {
@Override
public void visit(Foo foo) {
throw new GotResult(foo.getShortValue());
}
@Override
public void visit(Bar bar) {
throw new GotResult(bar.getIntValue());
}
});
} catch (GotResult result) {
return result.value;
}
throw new IllegalStateException();
}
Or don't use visitor at all:
int useCast(Base base) {
if (base instanceof Foo) {
return ((Foo) base).getShortValue();
}
if (base instanceof Bar) {
return ((Bar) base).getIntValue();
}
throw new IllegalStateException();
}
Are these our only options now? We have Java 8 (will have 9 soon enough) and still writing these ugly pieces of error-prone code. :)
I agree that not being able to return a value from the visitor is terrible from a safe usage point of view.
To avoid the burden of the gymnastic you demonstrated above, your best option is to create a wrapper type that expose a sane API (based on the corrected visitor pattern) so that the dirty work is done only once: when converting a Base value to that wrapper type.
Here is how you could do it:
interface BaseW {
interface Cases<X> {
X foo(Foo foo);
X bar(Bar bar);
}
<X> X match(Cases<X> cases);
//or alternatively, use a church encoding:
//<X> X match(Function<Foo, X> foo, Function<Bar, X> bar);
default Base asBase() {
return match(new Cases<Base>() {
@Override
public Base foo(Foo foo) {
return foo;
}
@Override
public Base bar(Bar bar) {
return bar;
}
});
}
static BaseW fromBase(Base base) {
return new Visitor() {
BaseW baseW;
{
base.accept(this);
}
@Override
public void visit(Foo foo) {
baseW = new BaseW() {
@Override
public <X> X match(Cases<X> cases) {
return cases.foo(foo);
}
};
}
@Override
public void visit(Bar bar) {
baseW = new BaseW() {
@Override
public <X> X match(Cases<X> cases) {
return cases.bar(bar);
}
};
}
}.baseW;
}
static int useCorrectedVisitor(Base base) {
return fromBase(base).match(new Cases<Integer>() {
@Override
public Integer foo(Foo foo) {
return (int) foo.getShortValue();
}
@Override
public Integer bar(Bar bar) {
return bar.getIntValue();
}
});
// or, if church encoding was used:
// return fromBase(base).match(
// foo -> (int) foo.getShortValue(),
// bar -> bar.getIntValue()
// );
}
}
Now (shameless plug), if you don't mind using derive4j (a jsr 269 code generator) the above can be simplified a fair bit, as well as improving syntax:
@org.derive4j.Data // <- generate an BaseWs classe that allows handling
// of the interface as an algebraic data type.
interface BaseW {
interface Cases<X> {
X foo(Foo foo);
X bar(Bar bar);
}
<X> X match(Cases<X> cases);
default Base asBase() {
return match(BaseWs.cases(f -> f, b -> b));
}
static BaseW fromBase(Base base) {
return new Visitor() {
BaseW baseW;
{
base.accept(this);
}
@Override
public void visit(Foo foo) {
baseW = BaseWs.foo(foo);
}
@Override
public void visit(Bar bar) {
baseW = BaseWs.bar(bar);
}
}.baseW;
}
static int useStructuralPatternMatching(Base base) {
return BaseWs.caseOf(fromBase(base))
.foo(foo -> (int) foo.getShortValue())
.bar(Bar::getIntValue);
}
}
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