对未知节点的树进行泛化渲染

  • 本文关键字:泛化 未知 节点 java
  • 更新时间 :
  • 英文 :


我想做的并不容易;我确实意识到了。但我觉得有比我编的更好的办法。问题是:我想写一个泛型类呈现树存储节点可评价的——例如,一个节点可以存储一个值,评估价值,或者它可以生成一个随机值,或者在其他节点上的操作,因此我说树,但事实是,未知节点的内部状态(例如,一个节点可以有0,1,2个或更多孩子的节点字段,或一个ArrayList的儿童等)。当然,如果每个节点都知道如何渲染自己,这不会是一个问题,但我试图避免这种情况。(理想情况下,我希望能够渲染树的字符串或作为一个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);
    }
}

我知道你说你是"试图避免这种情况",但我真的认为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)

    addRender的值调用addRenderMethod花了我一段时间来理解,原因很明显,因为add在这里有多种含义。也许使用与Map匹配的动词put会更好。

  2. 从您的addRender()中调用NodeToString.render()是令人困惑的,尽管我理解需要。

  3. 方法在Java中是一个重载的术语,这使得RenderMethod看起来像一个Java方法而不是一种呈现方式。那NodeRenderer或者Renderer呢?

  4. NodeString赋值给变量renderer真是奇怪。它与addRendervalueRender非常不同,因为它不是RenderMethod。也许应该是这样?

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

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

最新更新