在不使用 instanceof 的情况下使用重载方法进行动态调度(运行时多态性)

Dynamic dispatch (runtime-polymorphism) with overloaded methods without using instanceof

我想把ArcLine的对象保存在一个ArrayList中,然后求两者的交集。问题是如何将 ij 转换为原来的 class。我知道 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)
    }
}

感谢您的帮助!

首先考虑:是否需要将ijCurve转换(upcast)为ArcLine

例如,请看这里:

What's the need to use Upcasting in java?

如果您决定确实需要向上转型,不幸的是没有魔法蛋 - 您无法避免使用 instanceof 来决定要向上转型的 class。

你可以把责任委托给另一个人class,但基本上你无法避免。

对不起!

另一种方法是使用 Class ClassisAssignableFrom 方法。下面是一个例子:

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));

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

Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter. It returns true if so; otherwise it returns false. If this Class object represents a primitive type, this method returns true if the specified Class parameter is exactly this Class object; otherwise it returns false.

因为每个子 class 已经知道其他子 class(例如,Arc 必须知道 Line class 为了实现 ArcLine 交集),使用 instanceof.

没有错

在每个子 class 中,您可以覆盖基础 class 的 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) 方法中的任何内容。

Curve更改为界面。保持 ArrayList<Curve> 不变,而是将 intersection 方法提取到单独的 class,并让它在 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
    }
}

好的,所以我发现的一个解决方案是在 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)
    }
}

有两种方法可以解决这种用例:


1.实施 Multiple dispatch :

首先让 Curve 成为一个接口,然后将 intersect 的两个重载版本添加到这个接口中,从而使它们成为契约的一部分。接下来,让每个子 class 中的 intersection(Curve c) 方法将调用委托给适当的重载形式。 (a.k.a Visitor pattern)

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 class 中调用您的 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 :查看 this 实现 Visitor 模式的替代方法。


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

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

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

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

一旦您不再需要 subclass 中的专门方法,而 superclass 中不存在,您的代码将自动消除 instanceof 或类型检查的需要。这符合 Liskov substitution principle.


如果您不想使用 instanceof,那么替代方法是使用组合来获取类型。以下方法不会使用 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[] {};
        }
    }