假设我们有以下类,我们无法改变:
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);
}
我们需要实现方法:
int getValue(Base base)
访问者使用一些存储对象有很多可能性:
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();
}
或者变态的东西:
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();
}
或者根本不使用访客:
int useCast(Base base) {
if (base instanceof Foo) {
return ((Foo) base).getShortValue();
}
if (base instanceof Bar) {
return ((Bar) base).getIntValue();
}
throw new IllegalStateException();
}
这些是我们现在唯一的选择吗?我们有Java 8(很快就会有9个)并且仍在编写这些丑陋的容易出错的代码。 :)
我同意,从安全使用的角度来看,无法从访问者那里返回值是非常糟糕的。
为了避免上面演示的体操负担,你最好的选择是创建一个包装类型,公开一个理智的API(基于corrected visitor pattern),这样脏工作只进行一次:将Base
值转换为该包装类型时。
您可以这样做:
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()
// );
}
}
现在(无耻的插件),如果你不介意使用derive4j(一个jsr 269代码生成器),上面的内容可以简化,以及改进语法:
@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);
}
}
实际上你可以利用Java泛型:
interface Visitor<T> {
T visit(Foo foo);
T visit(Bar bar);
}
interface Base {
<T> T accept(Visitor<T> visitor);
}
interface Foo extends Base {
}
interface Bar extends Base {
}
public final class VisitorExample {
static class ConcreteFoo implements Foo {
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
}
static class ConcreteBar implements Bar {
@Override
public <T> T accept(Visitor<T> visitor) {
return visitor.visit(this);
}
}
static class ClassNameExtractor implements Visitor<String> {
@Override
public String visit(Foo foo) {
return foo.getClass().getName();
}
@Override
public String visit(Bar bar) {
return bar.getClass().getName();
}
}
public static void main(String[] args) {
Visitor<String> visitor = new ClassNameExtractor();
Foo foo = new ConcreteFoo();
Bar bar = new ConcreteBar();
final String stringResultFromFoo = foo.accept(visitor);
System.out.println(stringResultFromFoo);
final String stringResultFromBar = bar.accept(visitor);
System.out.println(stringResultFromBar);
}
}
通过声明accept()
方法返回<T> T
,您可以使用通用的vistor Visitor<T>
来控制返回类型。所以你可以返回任何原始类型。
这种方法的缺点是,如果你不想返回任何东西,你的具体访问者将必须是Visitor<Void>
类型,并且它必须在return null
方法上有令人烦恼的visit()
。