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.
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)
...)
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).
I had the same problem (working in Objective C), and a workaround I found for this is to define an external function to solve the collision when I already know types for both objects.
For example, if I have Rectangle and Circle, both implementing a protocol (kind of interface for this language) Shape..
@protocol Shape
-(BOOL) intersects:(id<Shape>) anotherShape;
-(BOOL) intersectsWithCircle:(Circle*) aCircle;
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle;
@end
define intersectsWithCircle for Rectangle, and intersectsWithRectangle for Circle like this
-(BOOL) intersectsWithCircle:(Circle*) aCircle
{
return CircleAndRectangleCollision(aCircle, self);
}
and ...
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle
{
return CircleAndRectangleCollision(self, aRectangle);
}
Of course it doesn't attack the coupling problem of Double Dispatch, but at least it avoids code duplication
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With