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

时间:2017-10-31 14:46:43

标签: java oop design-patterns polymorphism multiple-dispatch

我想在一个ArrayList中保存ArcLine的对象,然后获取两者的交集。问题是如何将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)
    }
}

感谢您的帮助!

7 个答案:

答案 0 :(得分:2)

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

<强> 1。实施Multiple dispatch

首先使Curve成为一个接口,并将intersect的两个重载版本添加到此接口,从而使它们成为合同的一部分。接下来,让每个子类中的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类中调用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);

    }
}

其他内容:请参阅this替代方法来实施访客模式。

<强> 2。使线条和曲线遵循相同的界面(合同):

如果LineArc符合Curve的界面,则您的代码将不再需要intersect方法的重载版本。如果我们说LineCurveArc也是Curve,则这两个类应该具有与{{>> 相同的接口 {1}}(通过界面我指的是他们支持的操作列表)。如果这些类与Curve没有相同的接口,那么这就是问题所在的地方。 Curve中提供的方法应该是CurveLine类应该要求的唯一方法。

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

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

一旦消除了在超类中不存在的子类中使用专门方法的需要,您的代码就会自动消除对Arc或类型检查的需要。这与Liskov substitution principle一致。

答案 1 :(得分:1)

另一种方法是使用类isAssignableFrom的{​​{1}}方法。以下是一个例子:

Class

Here's 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)); 方法的javadoc,这就是它所说的:

  

确定此Class对象所表示的类或接口   与...相同或者是超类或超级接口   由指定的Class参数表示的类或接口。它   如果是,则返回true;否则返回false。如果是这个Class对象   表示基本类型,如果指定,此方法返回true   Class参数正是这个Class对象;否则它会返回   假的。

答案 2 :(得分:1)

由于每个子类都必须知道其他子类(例如,Arc必须知道Line类才能实现Arc和{{ 1}}交集),使用Line没有错。

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

答案 3 :(得分:0)

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

请看一下,例如:

What's the need to use Upcasting in java?

如果你确定你确实需要向上播种,不幸的是没有神奇的蛋 - 你无法避免使用instanceof来决定将该课改为上课。

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

抱歉!

答案 4 :(得分:0)

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
    }
}

答案 5 :(得分:0)

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

答案 6 :(得分:0)

如果您不想使用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[] {};
        }
    }