推广未知节点树的渲染

时间:2011-12-03 18:28:04

标签: java

我想做的事情并不容易;我意识到这一点。但我认为有一些更好的方法,我做了什么。这是问题:我正在尝试编写一个泛型类来呈现一个存储可评估节点的树 - 例如,一个节点可以存储一个值,只是评估该值,或者它可能生成一个随机值,或者是对一些其他节点的操作 - 因此我说树,但事实是节点的内部状态是未知的(例如,节点可能有0,1,2或更多的节点字段为儿童,或一个孩子的ArrayList等)。当然,如果每个Node知道如何渲染自己,这不会是一个问题,但我试图避免这种情况。 (理想情况下,我希望能够通过更改渲染器将树渲染为字符串或OpenGL图形或其他任何东西)。哦,请不要问诸如“那会有什么好处?”之类的问题。因为我这样做是因为它看起来很有趣。

(到现在为止,我认为我至少可以让节点知道它们可以被渲染,并且能够决定渲染的逻辑结构,但这可能不会对此产生太大的影响)

这是我到目前为止所做的: 节点的接口

public interface Node<T> {
    T evaluate();
} 

值节点的类:

public class ValueNode<T> implements Node<T> {
    private T value;
    @Override
    public T evaluate() {
        return value;
    }

    public ValueNode(T value) {
        this.value = value;
    }

}

二元运算符的通用类:

public abstract class BinaryOperator<A, B, T> implements Node<T> {
    private Node<A> left;
    private Node<B> right;

    public BinaryOperator(Node<A> left, Node<B> right) {
        this.left = left;
        this.right = right;
    }

    public Node<A> getLeft() {
        return left;
    }

    public Node<B> getRight() {
        return right;
    }

}

整数加法的类:

/**
 * I couldn't figure out a generic way to add 2 numbers,
 * so I'm going with just Integer for now.
 *
 */
public class IntegerAdditionNode extends BinaryOperator<Integer, Integer, Integer> {
    public IntegerAdditionNode (Node<Integer> left, Node<Integer> right) {
        super(left,right);
    }

    public Integer evaluate() {
        return getLeft().evaluate() + getRight().evaluate();
    }
}

最后,一个示例字符串渲染器类,允许动态添加新的渲染选项。虽然它非常难看,但我真的很感激想法,或者只是在正确的方向上轻松推动我如何更好地做到这一点:

import java.util.HashMap;

public class NodeToString {
    public interface RenderMethod {
        public <T extends Node<?>> String renderNode(T node);
    }

    public static void main(String[] args) {
        //test
        NodeToString renderer = new NodeToString();
        RenderMethod addRender = new RenderMethod() {
            private NodeToString render;

            public RenderMethod addNodeToString(NodeToString render) {
                this.render = render;
                return this;
            }

            @Override
            public <T extends Node<?>> String renderNode(T node) {
                IntegerAdditionNode addNode = (IntegerAdditionNode) node;
                return render.render(addNode.getLeft()) +"+"+render.render(addNode.getRight());
            }
        }.addNodeToString(renderer);

        renderer.addRenderMethod(IntegerAdditionNode.class, addRender);

        RenderMethod valueRender = new RenderMethod () {

            @Override
            public <T extends Node<?>> String renderNode(T node) {
                return ((ValueNode<?>)node).evaluate().toString();
            }

        };

        //I don't know why I have to cast here. But it doesn't compile
        //if I don't.
        renderer.addRenderMethod((Class<? extends Node<?>>) ValueNode.class,
                                 valueRender);

        Node<Integer> node = new IntegerAdditionNode(new ValueNode<Integer>(2),
                                                    new ValueNode<Integer>(3));
        System.out.println(renderer.render(node));

    }

    private HashMap<Class<? extends Node<?>>, RenderMethod> renderMethods = new
            HashMap<Class<? extends Node<?>>, NodeToString.RenderMethod>();

    /**
     * Renders a Node
     * @param node
     * @return
     */
    public <T extends Node<?>> String render(T node) {
        Class nodeType = node.getClass();
        if(renderMethods.containsKey(nodeType)) {
            return renderMethods.get(nodeType).renderNode(node);
        } else {
            throw new RuntimeException("Unknown Node Type");
        }

    }

    /**
     * This adds a rendering Method for a specific Node type to the Renderer
     * @param nodeType
     * @param method
     */
    public void addRenderMethod(Class<? extends Node<?>> nodeType, RenderMethod method) {
        renderMethods.put(nodeType, method);
    }
}

1 个答案:

答案 0 :(得分:0)

我知道你说你“试图避免这种情况”但我真的认为Node应该有一个render()方法。如果您不希望渲染代码在每个节点类型中,那么您可以执行以下操作:

public class ValueNode<T> implements Node<T> {
    private static RenderMethod rendorMethod;
    public static void setRendorMethod(RenderMethod rendorMethod) {
        ValueNode.rendorMethod = rendorMethod;
    }
    ...
    public String render() {
        rendorMethod(this);
    }

你做这件事的方式会奏效,虽然我发现它不必要地复杂化了。一些评论:

  1. renderer.addRenderMethod(IntegerAdditionNode.class, addRender)

    使用值addRenderMethod调用addRender花了我一些时间才明白原因显而易见,因为add在这里有多重含义。也许使用与Map匹配的动词put更好。

  2. 虽然我理解需要,但在NodeToString.render()内拨打addRender()令人困惑。

  3. Method是Java中的重载术语,它使RenderMethod看起来像Java方法而不是渲染方式。 NodeRendererRenderer怎么样?

  4. NodeString分配给变量renderer只是很奇怪。它与addRendervalueRender非常不同,因为它不是RenderMethod。也许它应该是?

  5. 在所有main类中,我在main方法中做的第一件事就是执行以下操作。这至少对我来说不那么复杂了:

    public static void main(String[] args) {
        new NodeString().doMain(args);
    }
    private void doMain(String[] args) {
        ...