OO设计和镜像/重复方法
我在面向对象设计中遇到了一个问题,最终在两个不同的类中出现了重复的代码。事情是这样的:
在这个例子中,我想检测游戏对象之间的碰撞。
我有一个基 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您想要的称为多调度。
这可以在主线 OOP 语言中模拟,或者如果您使用 Common Lisp,也可以直接使用它。
维基百科文章中的 Java 示例甚至处理了您的确切问题:碰撞检测。
这是我们“现代”语言中的假冒:
这是 Common Lisp 中的:
What you want is referred to as multi dispatch.
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:
Here's it in Common Lisp:
这是面向对象开发中的一个典型陷阱。我也曾经尝试过用这种方式解决冲突——结果却惨遭失败。
这是一个所有权问题。 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).
我遇到了同样的问题(在 Objective C 中工作),我找到的解决方法是在我已经知道两个对象的类型时定义一个外部函数来解决冲突。
例如,如果我有 Rectangle 和 Circle,两者都实现协议(该语言的一种接口)Shape..
为 Rectangle 定义 intersectsWithCircle,并为 Circle 定义 intersectsWithRectangle 像这样
......
当然它不会解决耦合问题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..
define intersectsWithCircle for Rectangle, and intersectsWithRectangle for Circle like this
and ...
Of course it doesn't attack the coupling problem of Double Dispatch, but at least it avoids code duplication
您应该使用 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.
你没有说你使用什么语言,所以我猜它是 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.
也许您可以有一个碰撞对象,其中包含测试不同类型碰撞的方法。这些方法可以返回包含碰撞点和其他必要信息的其他对象。
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.
第一个选项:使碰撞具有方向性。例如,如果盒子是静止的,它不会检查自己与其他任何物体的碰撞;但移动的圆圈会检查与盒子(和其他静止物体)的碰撞。这是不直观的,因为我们一生都被教导“相等和相反的反应”。陷阱:移动物体会与其他移动物体重复碰撞。
第二个选项:为每个对象指定一个唯一的 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.