Java最佳实践:子类中更详细的类变量

时间:2016-01-19 10:52:24

标签: java graph drawable

我在java中为一些算法建模可绘制的平面图。 我的基本课程是:

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)

有没有人有意识到这一点?

3 个答案:

答案 0 :(得分:2)

您可以使用泛型并使用Node类参数化Edge

public class Edge<N extends Node> {
    private N node0;
    private N node1;
    public Edge(N n0, N n1) { this.node0 = n0; this.node1 = n1; }
}

GraphicalEdge然后变成

public class GraphicalEdge extends Edge<GraphicalNode> {
    public GraphicalEdge (GraphicalNode n0, GraphicalNode n1) { super(n0, n1); }
}

注意:这些类层次结构通常难以处理。 您还可以简单地决定将xy成员放入Node基类,而不需要层次结构。

答案 1 :(得分:1)

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

这听起来像是使用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();
}

或者,使用您提供的类,使用您提到的构造函数和一些getter进行扩展,组合成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 { ... }

使用泛型,wero suggested in his answer,可以添加更多自由度,并且考虑到更多Node类型,可能会提供更清晰,更灵活的设计。例如,您可能稍后想要引入类似ColoredGraphicalNode的内容,这可以很好地被泛型类型参数覆盖。但它的代价是更隐秘的方法签名:以通用形式编写方法,允许&#34;路由通过&#34;所需的类型信息可能会变得有点麻烦,具体取决于你想去那里的距离。

答案 2 :(得分:0)

我认为最简单的方法是让Node班级实施Point

public interface Point {
    double getX();
    double getY();
}
像这样:

public class Node implements Point {
    private String label;
    // ...
}

然后你可以拥有一个像你原来的Edge类:

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

    public List<Node> getNodes() {
    // ...
}

和负责绘图的对象:

public class PointsDrawer {

    public void drawPoints(List<? extends PointPoint> pointsToDraw) {
        // draw logic here
    }
}

你可以这样使用:

Edge edge = new Edge();
// initialization here ...
PointsDrawer pd = new PointsDrawer();
pd.drawPoints(edge.getNodes());

使用此方法可以从数据结构中取消绘图逻辑,只需在Node类中保存 x y 坐标。您可以通过使用装饰器或Point存储附加的Map数据来进一步解耦。