java - 从Visitor返回一个值

问题描述 投票:1回答:2

假设我们有以下类,我们无法改变:

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个)并且仍在编写这些丑陋的容易出错的代码。 :)

java design-patterns visitor visitor-pattern
2个回答
3
投票

我同意,从安全使用的角度来看,无法从访问者那里返回值是非常糟糕的。

为了避免上面演示的体操负担,你最好的选择是创建一个包装类型,公开一个理智的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);
    }
}

0
投票

实际上你可以利用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()

© www.soinside.com 2019 - 2024. All rights reserved.