具有重载方法的动态调度(运行时多态性),而不使用instanceof



我想把ArcLine的对象保存在一个ArrayList中,然后得到两者的交集。问题是如何将ij强制转换为其原始类。我知道instanceof有效,但那将是最肮脏的方法。

public class Intersection {
public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) {
for (Curve i : list1) {
for (Curve j : list2) {
if (i.intersection(j).length > 0) 
return true;
}
}
return false;
}
}
public abstract class Curve {
public Point[] intersection(Curve c) {
return new Point[] {};
}
}
public class Line extends Curve {
public Point[] intersection(Line l) {
// returns intersection Point of this and l
}
public Point[] intersection(Arc a) {
// returns intersection Point(s)
}
}
public class Arc extends Curve {
public Point[] intersection(Line l) {
// return intersection Point(s) of this and l
}
public Point[] intersection(Arc a) {
// returns intersection Point(s)
}
}

谢谢你的帮助!

有两种方法可以处理这样的用例:


1.实现多重调度:

首先将Curve作为一个接口,并将intersect的两个重载版本添加到此接口,从而使它们成为合同的一部分。接下来,让每个子类中的intersection(Curve c)方法将调用委托给适当的重载表单

interface class Curve {
public Point[] intersection(Curve c);
public Point[] intersection(Line l);
public Point[] intersection(Arc c);
}
class Line extends Curve {

public Point[] intersection(Curve c) {
return c.intersection(this);
}

@Override
public Point[] intersection(Line l) {
System.out.println("line interesection with line");
return new Point[0];
}
@Override
public Point[] intersection(Arc c) {
System.out.println("line intersection with arc");
return new Point[0];
}
}
class Arc extends Curve {

public Point[] intersection(Curve c) {
return c.intersection(this);
}
@Override
public Point[] intersection(Line l) {
System.out.println("arc interesection with line");
return new Point[0];
}
@Override
public Point[] intersection(Arc c) {
System.out.println("arc interesection with arc");
return new Point[0];
}
}

然后,您可以在Intersection类中调用intersection方法,而不需要任何显式强制转换:

public class Intersection {
public static boolean intersect(ArrayList<Curve> list1,
ArrayList<Curve> list2) {
for (Curve i : list1) {
for (Curve j : list2) {
if (i.intersection(j).length > 0)
return true;
}
}
return false;
}

public static void main(String[] args) {
Curve line1 = new Line();
Curve arc1 = new Arc();
Curve line2 = new Line();
Curve arc2 = new Arc();

ArrayList<Curve> list1 = new ArrayList<>();
ArrayList<Curve> list2 = new ArrayList<>();
list1.add(line1);
list1.add(arc1);
list2.add(line2);
list2.add(arc2);

Intersection.intersect(list1, list2);

}
}

Extras:看看这个实现Visitor模式的替代方法。


2.使直线和曲线遵守同一界面(合同):

如果LineArc坚持Curve的接口,那么您的代码将不再需要intersect方法的重载版本。如果我们说LineCurveArc也是Curve,那么这两个类都应该具有与Curve相同的接口(我所说的接口是指它们支持的操作列表)。如果这些类没有与Curve相同的接口,那么这就是问题所在。Curve中存在的方法应该是LineArc类所需的唯一方法。

有几种策略可以消除子类具有超类中不存在的方法的需要:

  • 如果与超类相比,子类需要额外的输入,请通过构造函数提供这些输入,而不是创建单独的方法来操作这些输入
  • 如果子类需要超类不支持的额外行为,请通过组合(读取策略模式)来支持此行为,而不是添加方法来支持额外行为

一旦您消除了在子类中使用超类中不存在的专用方法的需要,您的代码就会自动消除instanceof或类型检查的需要。这符合利斯科夫替代原理。


由于每个子类都必须知道其他子类(例如,Arc必须知道Line类才能实现ArcLine的交集),因此使用instanceof没有错。

在每个子类中,您可以重写基类的public Point[] intersection(Curve c)方法,并将实现分派到重载的方法之一。

例如:

public class Arc extends Curve {    
@Override
public Point[] intersection(Curve c) {
if (c instanceof Line)
return instersection ((Line) c);
else if (c instanceof Arc)
return intersection ((Arc) c);
else
return an empty array or null, or throw some exception
}
public Point[] intersection(Line l) {
// return intersection Point(s) of this and l
}
public Point[] intersection(Arc a) {
// returns intersection Point(s)
}
}

这样,您就不必更改public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2)方法中的任何内容。

另一种选择是使用Class类的isAssignableFrom方法。下面是一个例子:

Exception e = new Exception();
RuntimeException rte = new RuntimeException();
System.out.println(e.getClass().isAssignableFrom(RuntimeException.class));
System.out.println(rte.getClass().isAssignableFrom(Exception.class));
System.out.println(rte.getClass().isAssignableFrom(RuntimeException.class));

这是isAssignableFrom方法的javadoc,它是这样说的:

确定该class对象表示的类或接口与类或由指定的class参数表示的接口。它如果是,则返回true;否则返回false。如果此Class对象表示基元类型,如果指定Class参数正是此Class对象;否则返回false。

首先要考虑的是:是否需要将ijCurve转换为ArcLine

例如,请看一下这里:

什么';是否需要在java中使用Upcasting?

如果你决定你真的需要升级,不幸的是没有魔法蛋——你无法避免使用instanceof来决定要升级到的类。

你可以将责任委托给另一个类,但基本上你无法避免

对不起!

好的,所以我发现的一个解决方案是在Curve中使用抽象方法,在子类中使用if-else链。然而,我对这种做法并不满意。

public abstract class Curve {
public abstract Point[] intersection(Curve c);
}
public class Line extends Curve {
public Point[] intersection(Curve c) {
if (c instanceof Line) {
return this.intersection((Line) c);
} else if (c instanceof Arc) {
return this.intersection((Arc) c);
}
}
private Point[] intersection(Line l) {
// returns intersection Point of this and l
}
private Point[] intersection(Arc a) {
// returns intersection Point(s)
}
}

Curve更改为接口。保持ArrayList<Curve>不变,而是将intersection方法提取到一个单独的类中,并在Curves上进行操作。

您需要在那里使用instanceof检查,但由于使用了继承,您的设计会更干净一些。

public interface Curve {
...
}
public class Line extends Curve {
...
}
public class Arc extends Curve {
...
}
public class IntersectionUtility {
public static boolean intersects(ArrayList<Curve> list1, ArrayList<Curve> list2) {
for (Curve i : list1) {
for (Curve j : list2) {
if (i.intersection(j).length > 0) 
return true;
}
}
return false;
}
public Point[] intersection(Curve a, Curve b) {
if (a.instanceof(Line.class)) {
if (b.instanceof(Line.class)) {
return findIntersection((Line) a, (Line) b); // two Lines
} else {
return findIntersection((Line) a, (Arc) b); // a Line and an Arc
}
} else {
if (b.instanceof(Line.class)) {
return findIntersection((Line) b, (Arc) a); // a Line and an Arc
} else {
return findIntersection((Arc) a, (Arc) b); // two Arcs
}
}
}
public Point[] findIntersection(Line a, Line b) {
// returns intersection Point of two Lines
}
public Point[] findIntersection(Arc a, Arc b) {
// returns intersection Point(s) of two Arcs
}
public Point[] findIntersection(Line a, Arc b) {
// returns intersection Point(s) of an Line and an Arc
}
}

如果您不想使用instanceof,那么另一种选择是使用composition来获取类型。以下方法不会使用instanceof,只会使用首选的Class.cast操作:

public static class Intersection {
public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) {            
for (Curve i : list1) {                
Optional<Line> il = i.get(Line.class);
Optional<Arc> ia = i.get(Arc.class);
for (Curve j : list2) {
Optional<Line> jl = j.get(Line.class);
Optional<Arc> ja = j.get(Arc.class);
Point[] intersection = null;
if ( il.isPresent() ){
if ( jl.isPresent() ) intersection = il.get().intersection( jl.get() );
else if ( ja.isPresent() ) intersection = il.get().intersection( ja.get() );
}else if ( ia.isPresent() ){
if ( jl.isPresent() ) intersection = ia.get().intersection( jl.get() );
else if ( ja.isPresent() ) intersection = ia.get().intersection( ja.get() );
}    
if ( intersection != null && intersection.length > 0 ) return true;
}
}
return false;
}
}
public static abstract class Curve {
public abstract <T extends Curve> Optional<T> get(Class<T> clazz);
}
public static class Line extends Curve {
public <T extends Curve> Optional<T> get(Class<T> clazz){
return clazz.equals(Line.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
}
public Point[] intersection(Line l) {
return new Point[] {};
}
public Point[] intersection(Arc a) {
return new Point[] {};
}
}
public static class Arc extends Curve {
public <T extends Curve> Optional<T> get(Class<T> clazz){
return clazz.equals(Arc.class) ? Optional.of( clazz.cast(this) ) : Optional.empty();
}
public Point[] intersection(Line l) {
return new Point[] {};
}
public Point[] intersection(Arc a) {
return new Point[] {};
}
} 

最新更新