Closed. This question is opinion-based。它当前不接受答案。












想改善这个问题吗?更新问题,以便editing this post用事实和引用来回答。

2年前关闭。



Improve this question




我在Java中为某些算法建模可绘制平面图。
我的基本 class 是:
public class Node {
    private String label;
}


public class Edge {
    private Node node0;
    private Node node1;
}

这对于算法来说效果很好。为了绘制图形,我用位置扩展了节点类:
public class GraphicalNode extends Node {
    private int x;
    private int y;
}

我的问题是可绘制边缘的类。我想写这样的东西:
public class GraphicalEdge extends Edge {
    private GraphicalNode node0;
    private GraphicalNode node1;
}

但是我以前从未见过这样的设计。最重要的是,编译器需要为超类构造一个public GraphicalNode(Node node0, Node node1)构造函数。

有没有人意识到这一点?

最佳答案

也许我在这里误会了一些东西-投票将显示-但是...

这听起来像是使用Covariance的教科书示例。

如果您的Edge类具有方法Node getNode(),则可以在extends Edge类中为此方法定义更特定的返回类型。例如:

class Node {}
class Edge {
    Node getNode();
}

class GraphicalNode extends Node {}
class GraphicalEdge extends Edge {
    // This really overrides the method, with a more specific return type!
    @Override
    GraphicalNode getNode();
}

或者,使用您提供的类,用您提到的构造函数和一些吸气剂扩展,组装成MCVE:
public class WhatIsCovariance
{
    public static void main(String[] args)
    {
        Node n0 = new Node();
        Node n1 = new Node();
        Edge e0 = new Edge(n0, n1);

        Node n = e0.getNode0(); // Works


        GraphicalNode gn0 = new GraphicalNode();
        GraphicalNode gn1 = new GraphicalNode();
        GraphicalEdge ge0 = new GraphicalEdge(gn0, gn1);

        GraphicalNode gn = ge0.getNode0(); // Works
    }
}

class Node
{
    private String label;
}

class Edge
{
    private Node node0;
    private Node node1;

    Edge(Node node0, Node node1)
    {
        this.node0 = node0;
        this.node1 = node1;
    }

    public Node getNode0()
    {
        return node0;
    }

    public Node getNode1()
    {
        return node1;
    }
}

class GraphicalNode extends Node
{
    private int x;
    private int y;
}

class GraphicalEdge extends Edge
{
    private GraphicalNode node0;
    private GraphicalNode node1;

    GraphicalEdge(GraphicalNode node0, GraphicalNode node1)
    {
        super(node0, node1);

        this.node0 = node0;
        this.node1 = node1;
    }

    @Override
    public GraphicalNode getNode0()
    {
        return node0;
    }

    @Override
    public GraphicalNode getNode1()
    {
        return node1;
    }

}

这里的关键点是:当您拥有Edge类型的引用时,则只能从中获取Node(即使该引用实际上指向的对象是GraphicalEdge)。仅当引用的类型为GraphicalEdge时,您才能从中获取GraphicalNode

这在很多情况下都很方便,并且通常可以将关注点清晰地分开:当方法仅需对EdgeNode对象进行操作,而对它们的图形表示不感兴趣时​​,则可以使用基类编写其签名:
void computeSomething(Edge edge) {
    Node n0 = edge.getNode();
    Node n1 = edge.getNode();
    ...
}

void run() {
    GraphicialEdge e = new GraphicalEdge(...);

    computeSomething(e);
}

当方法确实需要图形表示时,可以让它采用图形的优势:
void drawSomething(GraphicalEdge edge) {
    GraphicalNode n0 = edge.getNode();
    GraphicalNode n1 = edge.getNode();
    ...
}

void run() {
    GraphicialEdge e = new GraphicalEdge(...);

    computeSomething(e); // Works
    drawSomething(e); // Works as well

    Edge edge = e;
    drawSomething(edge); // Does not work. A GraphicalEdge is required.
}

考虑到您的问题特别是关于...的旁注或关键点

类变量:

使用您在此处绘制的当前设计,每个GraphicalEdge都会将其节点存储两次-一次作为GraphicalNode,一次作为简单的Node。例如,可以通过将事物定义为接口来避免这种情况:
interface Node {}
interface Edge {
    Node getNode();
}

interface GraphicalNode extends Node {}
interface GraphicalEdge extends Edge {
    @Override
    GraphicalNode getNode();
}

class DefaultEdge implements GraphicalEdge { ... }

考虑到其他Node类型,将泛型用作wero suggested in his answer可以增加更多的自由度,甚至可以提供更简洁,更灵活的设计。例如,您稍后可能要引入类似ColoredGraphicalNode的内容,而泛型类型参数可以很好地覆盖它。但这要付出更多方法方法签名的代价:以通用形式编写方法,该方法允许“路由”所需的类型信息可能会变得有些麻烦,具体取决于您要走多远。

09-13 13:48