English 中文(简体)
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 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.

最佳回答

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

问题回答

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).

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.





相关问题
Template Classes in C++ ... a required skill set?

I m new to C++ and am wondering how much time I should invest in learning how to implement template classes. Are they widely used in industry, or is this something I should move through quickly?

JSON with classes?

Is there a standardized way to store classes in JSON, and then converting them back into classes again from a string? For example, I might have an array of objects of type Questions. I d like to ...

Object-Oriented Perl constructor syntax and named parameters

I m a little confused about what is going on in Perl constructors. I found these two examples perldoc perlbot. package Foo; #In Perl, the constructor is just a subroutine called new. sub new { #I ...

Passing another class amongst instances

I was wondering what is the best practice re. passing (another class) amongst two instances of the same class (lets call this Primary ). So, essentially in the constructor for the first, i can ...

Where can I find object-oriented Perl tutorials? [closed]

A Google search yields a number of results - but which ones are the best? The Perl site appears to contain two - perlboot and perltoot. I m reading these now, but what else is out there? Note: I ve ...

热门标签