views:

94

answers:

6

Hi everyone! I'm having an issue in OO design where I end up with duplicate code in 2 different classes. Here's what's going on:

In this example, I want to detect collision between game objects.

I have a base CollisionObject that holds common methods (such as checkForCollisionWith) and CollisionObjectBox, CollisionObjectCircle, CollisionObjectPolygon that extend the base class.

This part of design seems ok, but here's what's troubling me: calling

aCircle checkForCollisionWith: aBox

will perform a circle vs box collision check inside Circle subclass. In reverse,

aBox checkForCollisionWith: aCircle

will perform box vs circle collision check inside Box subclass.

Issue here is that Circle vs Box collision code is duplicate, since it's in both Box and Circle classes. Is there a way to avoid this, or am I approaching this problem the wrong way? For now, I'm leaning towards having a helper class with all the duplicate code and call it from the aCircle and aBox objects to avoid duplicates. I'm curious if there's more elegant OO solution to this, though.

A: 

Perhaps you could have a collision object which contains the methods for testing for different types of collisions. The methods could return other objects which contain the collision point and other necessary information.

Jani Hartikainen
+3  A: 

What you want is referred to as multi dispatch.

Multiple dispatch or multimethods is the feature of some object-oriented programming languages in which a function or method can be dynamically dispatched based on the run time (dynamic) type of more than one of its arguments.

This can be emulated in the mainline OOP languages, or you can use it directly if you use Common Lisp.

The Java example in the Wikipedia article even deals with your exact problem, collision detection.

Here's the fake in our "modern" languages:

abstract class CollisionObject {
    public abstract Collision CheckForCollisionWith(CollisionObject other);
}

class Box : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Sphere) { 
            return Collision.BetweenBoxSphere(this, (Sphere)other);
        }
    }
}

class Sphere : CollisionObject {
    public override Collision CheckForCollisionWith(CollisionObject other) {
        if (other is Box) { 
            return Collision.BetweenBoxSphere((Box)other, this);
        }
    }
}

class Collision {
    public static Collision BetweenBoxSphere(Box b, Sphere s) { ... }
}

Here's it in Common Lisp:

(defmethod check-for-collision-with ((x box) (y sphere))
   (box-sphere-collision x y))

(defmethod check-for-collision-with ((x sphere) (y box))
   (box-sphere-collision y x))

(defun box-sphere-collision (box sphere)
    ...)
Frank Krueger
This seems to be very hard to maintain, for every class deriving from Collision object you'd have to add a new `checkForCollisionWithX` in all of the other deriving classes.
chelmertz
It absolutely is hard to maintain. I never said this was the right technique to use for collision detection. Really, the code should **automatically** construct a collision dispatch matrix. Collision operations should then be handled through that matrix. You code the matrix builder once, it uses reflection, everything runs nice and fast after that.
Frank Krueger
A: 

You should use checkForCollisionWith: aCollisionObject and since all your objects are extending CollisionObject you can put all common logic there.

Alternatively you could use the delegation design pattern to share common logic between different classes.

Eugene Kuleshov
+1  A: 

You don't say what language you are using, so I presume it is something like Java or C#.

This is a situation where multimethods would be an ideal solution, but most languages do not support them. The usual way to emulate them is with some variation of the visitor pattern - see any good book on design patterns.

Alternatively have a separate CollisionDetection class that checks for collisions between pairs of objects, and if two objects collide then it calls the appropriate methods on the objects, e.g. bomb.explode() and player.die(). The class could have a big lookup table with each object type along the rows and columns, and the entries giving the methods to call on both objects.

Dave Kirby
Objective-C. I'm assuming problem is same with Java or C#, though.
Rudi
+3  A: 

This is a typical pitfall in OO development. I once also tried to solve collisions in this manner - only to fail miserably.

This is a question of ownership. Do Box class really owns the collision logic with circle? Why not the other way round? Result is code duplicity or delegating collision code from circle to Box. Both are not clean. Double dispatch doesn't solve this - same problem with ownership...

So you are right - you need third party functions/methods which solves particular collisions and a mechanism that selects the right function for two objects that are colliding (here double dispatch can be used, but if number of collision primitives is limited then probably 2D array of functors is faster solution with less code).

MaR
A: 

First option: Make the collision directional. For example, if the box is stationary, it doesn't check its own collisions with anything else; but the moving circle checks collision with the box (and other stationary objects). This is unintuitive because all our lives we're taught "equal and opposite reactions". Pitfall: moving objects would duplicate collisions with other moving objects.


Second option: Give every object a unique ID number. In the collision checking method, only check the collision if the first parameter/object has a lower ID than the second parameter.

Say the box has id=2 and circle has id=5. Then, "box collides with circle" would be executed, since box.id < circle.id; but then when the circle is checking collisions, "circle collides with box" will just return immediately without checking the collision, because the collision would have already been checked.

Ricket
Oh, hmm. Perhaps I solved a different problem than you're talking about. To solve your problem, I think I'd use Generics (java) or a template method (c++).
Ricket