我在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)
。
有没有人有意识到这一点?
答案 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); }
}
注意:这些类层次结构通常难以处理。
您还可以简单地决定将x
和y
成员放入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
。
这在许多情况下都很方便,并且通常可以清晰地分离关注点:当方法只需对Edge
和Node
个对象进行操作,并且对它们的图形不感兴趣时表示,然后您可以使用基类编写其签名:
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
数据来进一步解耦。