heraphim
heraphim

Reputation: 346

Accept superclass as parameter but use subclass in method

thanks in advance for your answers. I might not have all the necessary knowledge to form the question correctly but here it goes: I have a bunch of classes that extend the Shape super class (Circle, Rectangle, Point). I have a static class Geometry with a method that checks intersection between different shapes (circles, rectangles, triangles). In "Geometry" static class I implemented the "intersects" method to accept any combination of shapes

  public static boolean intersects(Rectangle rect, Point point) {
    return rectIntersectsPoint(rect, point);
  }
  public static boolean intersects(Circle circle, Point point) {
    return circleIntersectsPoint(circle, point);
  }
  public static boolean intersects(Circle circle, Rectangle rect) {
    return circleIntersectsRect(circle, rect);
  }

And a bunch more for each case. Now, in my program I have a method that has to check intersection and has to accept any subclass of Shape:

  List<Point> query(Shape shape) {
    if (Geo.intersects(this.b, shape)) {
      return this.found;
    }
   ......
  }

And I use it like this:

Rectangle rect = new Rectangle(10, 10, 200, 300);
found = qt.query(rect );

But i get an error saying "The function intersects expects parameters like (Rectangle, Circle)".

So I'm passing a Rectangle object to the query() method, and it goes well because I can accept any subclass of "Shape". But when I use it in the method it doesn't keep it's "Rectangle" type and becomes a "Shape" object.

I thought about having the query method for each subclass but that way I'll have the same code over and over.

Upvotes: 2

Views: 1311

Answers (2)

Andy Thomas
Andy Thomas

Reputation: 86411

Polymorphism allows you to call the appropriate method for the actual type of the object, regardless of the declared type of reference.

And double dispatch allows you to call the appropriate method for two object types.

Here's a sketch (not compiled or tested):

public abstract class Shape {
    public abstract boolean intersects( Shape that );

    public abstract boolean intersects(Rectangle that);
    public abstract boolean intersects(Circle that);
}

public class Circle extends Shape {
    public boolean intersects( Shape that ) {
        return that.intersects( this );   // 'this' is of type Circle
    }

    public boolean intersects( Rectangle that ) { ... }
    public boolean intersects( Circle that ) { ... }
}

public class Rectangle extends Shape {
    public boolean intersects( Shape that ) {
        return that.intersects( this );   // 'this' is of type Rectangle
    }

    public boolean intersects( Rectangle that ) { ... }
    public boolean intersects( Circle that ) { ... }
}

You can avoid redundancy by either defining one of a pair to call the other -- or having both methods call a common implementation. An example of the former is:

public class Rectangle {
    ...
    public boolean intersects( Circle that ) {
       return that.intersects( this ); 
    }

Now, say you pass a Circle and a Rectangle to the method below.

public boolean intersects( Shape shape1, Shape shape2 ) {
    return shape1.intersects( shape2 );
}

At compile-time, the compiler doesn't know what method will be called. But at run-time, the Circle.intersects(Shape) method will be called. It will call Rectangle.intersects( Circle ).

Upvotes: 3

shmosel
shmosel

Reputation: 50716

You can use a modified visitor pattern with nested visitors. I'll demonstrate with 2 shapes for brevity:

abstract class Shape {
    abstract <V extends Visitor> V accept(V v);
}

class Circle extends Shape {
    @Override
    <V extends Visitor> V accept(V v) {
        v.visit(this);
        return v;
    }
}

class Rectangle extends Shape {
    @Override
    <V extends Visitor> V accept(V v) {
        v.visit(this);
        return v;
    }
}

interface Visitor {
    void visit(Circle c);
    void visit(Rectangle r);
}

static boolean intersects(Shape s1, Shape s2) {
    return s1.accept(new Visitor() {
        boolean intersects;

        @Override
        public void visit(Circle left) {
            s2.accept(new Visitor() {
                @Override
                public void visit(Circle right) {
                    intersects = circleIntersectsCircle(left, right);
                }

                @Override
                public void visit(Rectangle right) {
                    intersects = circleIntersectsRect(left, right);
                }
            });
        }

        @Override
        public void visit(Rectangle left) {
            s2.accept(new Visitor() {
                @Override
                public void visit(Circle right) {
                    intersects = circleIntersectsRect(right, left);
                }

                @Override
                public void visit(Rectangle right) {
                    intersects = rectIntersectsRect(left, right);
                }
            });
        }
    }).intersects;
}

Upvotes: -1

Related Questions