6

我在 OO 设计中遇到问题,最终在 2 个不同的类中出现重复代码。这是发生了什么:

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

我有一个基础 CollisionObject,它包含扩展基类的常用方法(例如 checkForCollisionWith)和 CollisionObjectBox、CollisionObjectCircle、CollisionObjectPolygon。

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

aCircle checkForCollisionWith: aBox

将在 Circle 子类中执行圆形与框碰撞检查。相反,

aBox checkForCollisionWith: aCircle

将在 Box 子类中执行 box vs circle 碰撞检查。

这里的问题是 Circle vs Box 碰撞代码是重复的,因为它在 Box 和 Circle 类中。有没有办法避免这种情况,还是我以错误的方式处理这个问题?现在,我倾向于拥有一个包含所有重复代码的辅助类,并从 aCircle 和 aBox 对象中调用它以避免重复。不过,我很好奇是否有更优雅的 OO 解决方案。

4

7 回答 7

3

您想要的称为multi dispatch

多分派或多方法是一些面向对象编程语言的特性,其中一个函数或方法可以基于其多个参数的运行时(动态)类型动态分派。

这可以在主流 OOP 语言中进行模拟,或者如果您使用 Common Lisp,则可以直接使用它。

Wikipedia 文章中的 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)
    ...)
于 2009-12-06T18:23:56.530 回答
3

这是 OO 开发中的典型陷阱。我曾经也尝试过以这种方式解决碰撞 - 只是惨遭失败。

这是一个所有权问题。Box 类真的拥有与圆的碰撞逻辑吗?为什么不反过来呢?结果是代码重复或将碰撞代码从圆圈委托给盒子。两个都不干净。双重调度并不能解决这个问题 - 所有权同样的问题......

所以你是对的 - 你需要解决特定碰撞的第三方函数/方法以及为碰撞的两个对象选择正确函数的机制(这里可以使用双重调度,但如果碰撞原语的数量有限,那么可能是二维数组仿函数是更快的解决方案,代码更少)。

于 2009-12-06T18:51:59.343 回答
1

您应该使用 checkForCollisionWith: aCollisionObject 并且由于您的所有对象都在扩展 CollisionObject 您可以将所有常见逻辑放在那里。

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

于 2009-12-06T18:27:40.453 回答
1

你没有说你使用的是什么语言,所以我认为它是 Java 或 C# 之类的东西。

在这种情况下,多方法将是一个理想的解决方案,但大多数语言不支持它们。模仿它们的常用方法是使用访问者模式的一些变体——参见任何关于设计模式的好书。

或者,有一个单独的 CollisionDetection 类来检查对象对之间的碰撞,如果两个对象发生碰撞,那么它会调用对象上的适当方法,例如,bomb.explode() 和 player.die()。该类可以有一个大查找表,每个对象类型沿行和列,以及提供调用这两个对象的方法的条目。

于 2009-12-06T18:28:26.193 回答
1

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

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

@protocol Shape

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

@end

像这样为 Rectangle 定义 intersectsWithCircle,为 Circle 定义 intersectsWithRectangle

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

和 ...

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

当然它没有攻击Double Dispatch的耦合问题,但至少避免了代码重复

于 2011-01-21T17:05:28.690 回答
0

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

于 2009-12-06T18:22:33.460 回答
0

第一个选项:使碰撞有方向性。例如,如果盒子是静止的,它不会检查自己与其他任何东西的碰撞;但是移动的圆圈会检查与盒子(和其他静止物体)的碰撞。这是不直观的,因为我们一生都被教导“平等和相反的反应”。陷阱:移动物体会重复与其他移动物体的碰撞。


第二种选择:给每个对象一个唯一的 ID 号。在碰撞检查方法中,仅当第一个参数/对象的 ID 低于第二个参数时才检查碰撞。

假设盒子的 id=2,圆的 id=5。然后,将执行“box collides with circle”,因为 box.id < circle.id; 但是当圆检查碰撞时,“圆与盒子碰撞”将立即返回而不检查碰撞,因为已经检查了碰撞。

于 2009-12-07T06:21:20.047 回答