OO设计和镜像/重复的方法

6

我在面向对象设计中遇到了一个问题,即在两个不同的类中出现了重复的代码。以下是具体情况:

在这个例子中,我想要检测游戏对象之间的碰撞。

我有一个基本的CollisionObject类,其中包含一些通用方法(例如checkForCollisionWith),以及CollisionObjectBox、CollisionObjectCircle和CollisionObjectPolygon等继承自该基类的类。

这部分设计看起来还不错,但是让我感到困扰的是:

aCircle checkForCollisionWith: aBox

在 Circle 子类中执行圆形和矩形之间的碰撞检查。反过来,在 Box 子类中执行矩形和圆形之间的碰撞检查。

aBox checkForCollisionWith: aCircle

将在Box子类中执行盒子与圆形的碰撞检查。

问题在于,圆形与盒子的碰撞代码是重复的,因为它们都在Box和Circle类中。是否有避免这种情况的方法,或者我处理这个问题的方式有误?目前,我倾向于使用带有所有重复代码的辅助类,并从aCircle和aBox对象调用它以避免重复。不过,我很好奇是否有更优雅的面向对象的解决方案。

7个回答

3
你需要的是多重分派。多重分派或者多方法是一些面向对象编程语言的特性,它可以根据一个函数或方法的运行时类型来动态分派多个参数的类型。这可以在主流的面向对象编程语言中模拟实现,或者如果使用Common Lisp,则可以直接使用。维基百科文章中的Java示例甚至涉及到了你的确切问题——碰撞检测。以下是我们“现代”语言中的伪代码:
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) { ... }
}

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

这似乎很难维护,因为每个从Collision对象派生的类都必须在所有其他派生类中添加一个新的checkForCollisionWithX - chelmertz
它绝对很难维护。我从未说过这是用于碰撞检测的正确技术。实际上,代码应该自动构建一个碰撞分派矩阵。然后通过该矩阵处理碰撞操作。你只需编写一次矩阵生成器,它使用反射,之后所有内容都会运行得非常顺畅。 - Frank Krueger

3

这是面向对象开发中的一个典型陷阱。我曾经尝试过用这种方式解决碰撞问题,结果失败而彻底。

这是一个所有权的问题。Box类真的拥有与圆形的碰撞逻辑吗?为什么不是反过来呢?结果会导致代码重复或将碰撞代码从圆形委托给Box。两者都不干净。双重分派无法解决这个问题——所有权问题仍然存在……

所以你是正确的——你需要解决特定碰撞的第三方函数/方法,以及选择在发生碰撞时为两个对象选择正确函数的机制(可以使用双重分派,但如果碰撞原语的数量有限,则可能使用二维数组的函数更快且代码更少)。


1

我曾经遇到过同样的问题(在Objective C中工作),我找到了一个解决方案,就是定义一个外部函数来解决当我已经知道两个对象的类型时的碰撞问题。

例如,如果我有Rectangle和Circle,它们都实现了一个协议(这种语言的接口)Shape..

@protocol Shape

-(BOOL) intersects:(id<Shape>) anotherShape;
-(BOOL) intersectsWithCircle:(Circle*) aCircle;
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle;

@end

为矩形定义intersectsWithCircle函数,为圆形定义intersectsWithRectangle函数,如下所示

-(BOOL) intersectsWithCircle:(Circle*) aCircle
{
    return CircleAndRectangleCollision(aCircle, self);
}

并且...

-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle
{
    return CircleAndRectangleCollision(self, aRectangle);
}

当然,它并没有解决双重分派的耦合问题,但至少避免了代码重复。


1

你应该使用 checkForCollisionWith: aCollisionObject 方法,由于你的所有对象都是扩展 CollisionObject,因此可以将所有通用逻辑放在那里。

或者,你可以使用委托设计模式来共享不同类之间的通用逻辑。


1
您没有说明使用的编程语言,所以我假设它是类似Java或C#之类的语言。
这是一个多方法将是理想解决方案的情况,但大多数语言不支持它们。通常模拟多方法的方法是使用某种访问者模式的变体 - 请参阅任何有关设计模式的好书。
另一种选择是拥有一个单独的CollisionDetection类,它检测对象对之间的碰撞,如果两个对象发生碰撞,则调用对象的相应方法,例如bomb.explode()和player.die()。该类可以具有大型查找表,其中每个对象类型沿行和列,并且条目给出在两个对象上调用的方法。

Objective-C。虽然我认为问题在Java或C#中也是相同的。 - Rudi

0
也许你可以创建一个碰撞对象,其中包含测试不同类型碰撞的方法。这些方法可以返回其他对象,其中包含碰撞点和其他必要信息。

0

第一种选择:使碰撞具有方向性。例如,如果盒子静止不动,则不会检查自身与其他任何物体的碰撞;但是移动的圆形将检查与盒子(和其他静止物体)的碰撞。这是不直观的,因为我们一生都被教导“作用力与反作用力相等”。陷阱:移动的物体会重复与其他移动的物体的碰撞。


第二个选项:给每个对象分配一个唯一的ID号码。在碰撞检测方法中,只有在第一个参数/对象具有比第二个参数更低的ID时才检查碰撞。

假设盒子的ID为2,圆形的ID为5。那么,“盒子与圆形相撞”将被执行,因为box.id < circle.id;但是当圆形检查碰撞时,“圆形与盒子相撞”将立即返回而不检查碰撞,因为碰撞已经被检查过了。


哦,嗯。也许我解决的是不同于你所说的问题。为了解决你的问题,我认为我会使用泛型(Java)或模板方法(C ++)。 - Ricket

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接