5

I am sure this must have been asked before but I cannot seem to find a similar example. I understand well polymorphism and method overloading, but here is a seemingly simple scenario with a solution that escapes me:

let's say I have a base class with several derived classes. I will use shapes for this example

base Shape
derived Circle extends Shape
derived LineSeg extends Shape

etc.

Now, shape has a method called intersect(other) that tests against another shape to see if they intersect. With polymorphism it is easy to see how Circle, LineSeg, etc., can implement their own "intersect" methods, and, with method overloading, I can easily implement all needed combinations. e.g.,

Circle.intersect(LineSeg)
Circle.intersect(Circle)
LineSeg.intersect(Circle)

etc.

so far so good.

the problem is, if I keep a central list of shapes, I want to do this:

for some shape s
Foreach shape in Shapes
  if (s.intersect(shape)) - do something

Currently, I am not sure how this is possible, since the method overloading selects the "intersect" method to match the base type Shape, and not the appropriate parameter type. How can I make this without an if-else chain checking the types and down-casting?

BTW, I am using Java, but I am not sure that the language is entirely relevant since it seems to be a basic design question. Seems so simple, what am I missing?

Thanks!


Solved below (Thanks!), see details there. Basically, by having a callback in the derived classes that then calls the appropriate method (a visitor pattern?), you can use the "this" keyword to invoke the proper intersect method, as it has the proper type needed.

  • 1
    Some programming languages support overloading on the arguments' runtime types more directly. For example, LISP supports *multimethods*, and I suppose some of the functional programming languages support the same. In Java, this is usually called *double dispatch*, and the GoF Visitor pattern is the way to go. – Eric Jablow May 20 '13 at 00:42
  • 1
    Not to nitpick your design but your inheritance is a little wrong. Line segments aren't really shapes. They make up shapes. You would check intersections of any one of many, depending the shape, line segments that made up a the two shapes in question:) – ChiefTwoPencils May 20 '13 at 02:02
  • @C.Lang, good catch, but what if I rename lineSeg to "wall"? thats basically how its used, but calling it lineSeg makes it easier to conceptualize using it in other more complex shapes – user1922401 May 25 '13 at 21:36
  • You can do what makes the most sense. To me it's sort of misleading because shapes have physical dimensions lines don't. If you're thinking of an example where they do, they would probably be better described as a filled rectangle. A wall would be even a more concrete definition a rectangle in most situations. I'd probably say: shape=>quadrilateral=>rectangle=>wall -where shape *has* lines. – ChiefTwoPencils May 26 '13 at 09:36

3 Answers3

4

My first thought was the visitor pattern, pretty much give every Shape two methods, one I'll call intersect(Shape) and one doIntersect() method per Shape type.

It would look about like this:

interface Shape {
    public abstract Intersection intersect(Shape other);

    public abstract Intersection doIntersect(Circle circle);

    public abstract Intersection doIntersect(LineSeg line);
}
class LineSeg implements Shape {
    @Override
    public Intersection intersect(Shape other) {
        return other.doIntersect(this);
    }

    Intersection doIntersect(Circle circle) {
        // Code to intersect with Circle
    }

    Intersection doIntersect(LineSeg other) {
       // Code to intersect with another Lineseg
    }
}

class Circle implements Shape {
    @Override
    public Intersection intersect(Shape other) {
        return other.doIntersect(this);
    }

    public Intersection doIntersect(Circle other) {
        // Code to intersect with another Circle
    }

    public Intersection doIntersect(LineSeg segment) {
        // Code to intersect with LineSeg
    }
}

You might want the doIntersect methods to be package private or chose different names than these though.

confusopoly
  • 1,245
  • 7
  • 19
  • This is fantastic, thanks. Fixed my problem quickly and easily. I need to add all the parameter type possibilities as prototypes in the base class, forcing children to handle any possibility. – user1922401 May 20 '13 at 00:19
  • One additional refinement: You should be able to name all the methods `intersect()` instead of naming the specific ones `doIntersect()`. As far as I know the compiler picks the most specific overload that fits first. – confusopoly May 20 '13 at 00:29
  • Thanks - I already did that, but decided to leave your suggestion as it may be less confusing. I also tried putting it in the base class so that only one is required, but using "this" in a base class method has the base class type, unfortunately (I see why, though) – user1922401 May 20 '13 at 00:33
0

See Generics Jdk5

For each shape in Shapes <>

0

Your shape class must be an abstract class, meaning it does not get to be instantiated, only the derived classes circle and lineseg have instances. The intersect method should be virtual in shape so when you loop over all shapes, the intersect method of each shape is called

public abstract class Shape {

    boolean intersect(Shape s);
}

public class Circle extends Shape {

    boolean intersect(Shape s) {
        ...
        if( s instanceOf Circle ) { 
           .... // Circle intersects cicrcle
        } else if( s instanceOf Lineseg ) {
           .... // Circle intersects Lneseg
        } else {
          throw RuntimeException("Unrecognized shape");
        }

    }

}

public class Lineseg extends Shape {

    boolean intersect(Shape s) {
        ...
        if( s instanceOf Circle ) { 
           .... // Lineseg intersects circle
        } else if( s instanceOf Lineseg ) {
           .... // Lineseg intersects lineseg
        } else {
          throw RuntimeException("Unrecognized shape");
        }
    }

}
ilomambo
  • 8,290
  • 12
  • 57
  • 106
  • Sorry, I was asking for an answer without that if-else type-check chain – user1922401 May 20 '13 at 00:21
  • @user1922401 But you are willing to write a method for each derived class in each one of the derived classes, which is the same. Only that this way is the classic OO way to handle polymorphism. – ilomambo May 20 '13 at 00:23
  • it is not the same, as via the visitor pattern you have only one extra indirection. With the if-else chain, you have at worst n checks each time you call the method. Further, the proposed solution is slightly safer. If I add a new type I must add the new typed intersect header to the base class or it wont compile, and, it forces all the other shape types to handle it - in your solution, I could forget it accidentally. Although you do propose to have an if-else in each class, this could be centralized – user1922401 May 20 '13 at 00:32
  • @user1922401 You know, the if-else chain is done anyway, by the compiled code, to choose the right method to call. There is no magic in programming. Either solution will not compile if you add a new class, because of the `abstract` quality of the method, when a method is abstract you **must** implement it in any new derived class. In any case, the if-else can finish with `else { throw exception "unrecognized shape" } – ilomambo May 20 '13 at 00:39
  • @ilomambo: Everything else being equal this becomes a question of readability. This large if-else chain is less readable than having different methods for different types in my opinion. And I agree with you that performance differences are going to be completely negligible unless we are somehow dealing with 1000s of types. – confusopoly May 20 '13 at 00:55