OO设计和镜像/重复方法

发布于 2024-08-13 21:05:06 字数 605 浏览 6 评论 0原文

我在面向对象设计中遇到了一个问题,最终在两个不同的类中出现了重复的代码。事情是这样的:

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

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

这部分设计看起来没问题,但让我困扰的是:调用

aCircle checkForCollisionWith: aBox

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

aBox checkForCollisionWith: aCircle

将在 Box 子类内执行框与圆的碰撞检查。

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

云朵有点甜 2024-08-20 21:05:06

您想要的称为多调度

<块引用>

多重分派或多方法是某些面向对象编程语言的功能,其中可以根据多个参数的运行时(动态)类型来动态分派函数或方法。

这可以在主线 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)
    ...)

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)
    ...)
樱花落人离去 2024-08-20 21:05:06

这是面向对象开发中的一个典型陷阱。我也曾经尝试过用这种方式解决冲突——结果却惨遭失败。

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

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

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

神经暖 2024-08-20 21:05:06

我遇到了同样的问题(在 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,但至少避免了代码重复

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

等风来 2024-08-20 21:05:06

您应该使用 checkForCollisionWith: aCollisionObject,并且由于所有对象都扩展了 CollisionObject,因此您可以将所有通用逻辑放在那里。

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

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.

故事未完 2024-08-20 21:05:06

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

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

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

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.

忘羡 2024-08-20 21:05:06

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

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.

二手情话 2024-08-20 21:05:06

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


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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文