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

I want to store Arc and Line objects in one ArrayList array, and then get the intersection of both. The question is how can I apply i and j to its original class. I know instanceof works, but it will be the dirtiest method.

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

Thank you for your help!

+5
source share
7 answers

Since each subclass should already know about other subclasses (for example, Arc must know the Line class to implement the intersection of Arc and Line ), there is nothing wrong with using instanceof .

In each subclass, you can override the base class method public Point[] intersection(Curve c) and send the implementation to one of the overloaded methods.

For instance:

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

Thus, you do not need to change anything in the public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) method public static boolean intersect(ArrayList<Curve> list1, ArrayList<Curve> list2) .

+1
source

There are two approaches to solving such a precedent:


1. Implement Multiple Dispatch :

Start by creating a Curve interface and add two overloaded intersect versions to this interface, making them part of the contract. Then, if the intersection(Curve c) method in each of the subclasses delegates the call to the corresponding overloaded form. (aka visitor template )

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

Then you can call your intersection method in the intersection class without any explicit tricks:

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

Additional features . Check out this alternative approach to visitor pattern implementation.


2. Make a line and a curve to stick to the same interface (contract) :

If Line and Arc adhere to the Curve interface, your code will no longer need overloaded versions of the intersect method. If we say that a Line is Curve and Arc also Curve , both of these classes should have the same interface as Curve (by interface I mean the list of supported operations). If these classes do not have the same interface as Curve , the problem is here. The methods presented in Curve should be the only methods that should be required by the Line and Arc classes.

There are several strategies to eliminate the need for subclasses to have methods that are not present in the superclass:

  • If a subclass requires additional inputs compared to a superclass, provide these inputs through the constructor, rather than creating separate methods that work on these inputs.
  • If a subclass requires additional behavior that is not supported by the superclass, support this behavior using composition (read the strategy template) rather than adding methods to support additional behavior.

Once you eliminate the need for specialized methods in a subclass that are not present in the superclass, your code automatically eliminates the need for instanceof or type of validation. This is consistent with the Liskov substitution principle .


+2
source

An alternative would be to use the isAssignableFrom method of the Class class. The following is an example:

 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 is the javadoc of the isAssignableFrom method, and here is what it says:

Determines whether the class or interface represented by this class object is the same as or is the superclass or superinterface, the class or interface represented by the specified class parameter. This 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 this class object; otherwise he will return a lie.

+1
source

First consideration: do you need to convert (upcast) i and j from Curve to Arc or Line ?

Please see, for example, here:

What is the need to use upcasting in java?

If you decide that you really need to heave, unfortunately, there is no magic egg - you cannot avoid using instanceof to decide what the class should raise.

You can delegate responsibility to another class, but in principle you cannot avoid it.

Unfortunately!

0
source

Change Curve to interface. Keep the ArrayList<Curve> the same and instead extract your intersection method into a separate class and use its Curves .

You will need to use instanceof validation, but your design will be a little cleaner due to the use of inheritance.

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

OK, so one solution that I found out would be to use an abstract method in the Curve chain and if-else in subclasses. However, I am not very pleased with this complicity.

 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) } } 
0
source

If you do not want to use instanceof , then an alternative is to use composition to get the type. The following approach will not use instanceof and will only use the preferred Class.cast operation:

 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[] {}; } } 
0
source

Source: https://habr.com/ru/post/1273024/


All Articles