OO design and mirrored/duplicated methods
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 cla开发者_如何学JAVAsses. 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
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.
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.
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.
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.
精彩评论