我在OO设计中遇到问题,我最终在2个不同的类中重复了代码。这是正在发生的事情:
在这个例子中,我想检测游戏对象之间的碰撞。
我有一个基本CollisionObject,它包含扩展基类的常用方法(如checkForCollisionWith)和CollisionObjectBox,CollisionObjectCircle,CollisionObjectPolygon。
这部分设计似乎没问题,但这就是令我不安的问题:打电话
aCircle checkForCollisionWith: aBox
将在Circle子类内执行圆形与盒子碰撞检查。相反,
aBox checkForCollisionWith: aCircle
将在Box子类中执行box vs circle collision check。
这里的问题是Circle vs Box碰撞代码是重复的,因为它在Box和Circle类中都有。有没有办法避免这种情况,或者我是否以错误的方式处理这个问题?现在,我倾向于使用包含所有重复代码的辅助类,并从aCircle和aBox对象调用它以避免重复。我很好奇是否有更优雅的OO解决方案。
答案 0 :(得分:3)
您想要的是multi dispatch。
多个调度或多方法是一些面向对象编程语言的特性,其中可以根据多个参数的运行时(动态)类型动态调度函数或方法。
这可以在主线OOP语言中进行模拟,或者如果使用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)
...)
答案 1 :(得分:3)
这是OO开发中的典型陷阱。我曾经试图以这种方式解决冲突 - 只是悲惨地失败。
这是一个所有权问题。 Box类真的拥有带圆的碰撞逻辑吗?为什么不反过来呢?结果是代码重复或从圈到委托的委托碰撞代码。两者都不干净。双重调度无法解决这个问题 - 与所有权相同的问题......
所以你是对的 - 你需要第三方函数/方法来解决特定的碰撞和为两个碰撞对象选择正确函数的机制(这里可以使用双重调度,但如果碰撞原语的数量有限则那么可能是2D数组函子是更快的解决方案,代码更少)。
答案 2 :(得分:1)
你没有说你正在使用什么语言,所以我认为它类似于Java或C#。
这种情况下多方法将是一种理想的解决方案,但大多数语言都不支持它们。模仿它们的通常方法是访问者模式的一些变化 - 请参阅任何关于设计模式的好书。
或者有一个单独的CollisionDetection类来检查对象对之间的碰撞,如果两个对象发生碰撞,那么它会调用对象上的相应方法,例如: bomb.explode()和player.die()。该类可以有一个大的查找表,每个对象类型沿着行和列,以及条目赋予方法调用这两个对象。
答案 3 :(得分: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交换WithWithRectangle
-(BOOL) intersectsWithCircle:(Circle*) aCircle
{
return CircleAndRectangleCollision(aCircle, self);
}
和......
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle
{
return CircleAndRectangleCollision(self, aRectangle);
}
当然它不会攻击Double Dispatch的耦合问题,但至少它避免了代码重复
答案 4 :(得分:0)
也许你可以有一个碰撞对象,其中包含测试不同类型碰撞的方法。这些方法可以返回包含碰撞点和其他必要信息的其他对象。
答案 5 :(得分:0)
你应该使用checkForCollisionWith:aCollisionObject,因为所有对象都在扩展CollisionObject,你可以将所有常用逻辑放在那里。
或者,您可以使用delegation design pattern在不同类之间共享通用逻辑。
答案 6 :(得分:0)
第一个选项:使碰撞定向。例如,如果盒子是静止的,它不会检查自己与其他任何物体的碰撞;但是移动的圆圈检查与盒子(和其他静止物体)的碰撞。这是不直观的,因为我们所有的生活都被教导为“平等和相反的反应”。陷阱:移动物体会与其他移动物体发生碰撞。
第二个选项:为每个对象提供唯一的ID号。在碰撞检查方法中,如果第一个参数/对象的ID低于第二个参数,则仅检查碰撞。
假设该框具有id = 2且圆圈具有id = 5。然后,由于box.id&lt;“,因此将执行”框与圆碰撞“。 circle.id;但是当圆圈检查碰撞时,“圆圈与框碰撞”将立即返回而不检查碰撞,因为碰撞已经被检查过。