我有以下由多个其他类实现的 Shape 接口(interface),例如 Rectangle、Circle、Triangle ...
interface IShape{
bool IsColliding(IShape other);
}
IsColliding 方法应该检查一个 Shape 是否与另一个 Shape 发生碰撞,而不管它们的具体类型。 然而,每一对形状(矩形/矩形、矩形/圆形、圆形/三角形等)都有自己的碰撞检查实现。
我正在努力为这个问题找到一个好的设计解决方案。
天真的方法是切换“其他”形状的类型以调用正确的实现:
class Rectangle : IShape{
bool IsColliding(IShape other){
if(other is Rectangle){
return CollisionHandler.CheckRectangleVsRectangle(this,(Rectangle)other);
}else if(other is Circle){
return CollisionHandler.CheckRectangleVsCircle(this,(Circle)other);
} else
// etc ...
}
}
但添加新形状意味着修改每个派生类中的方法以添加新案例。
我还想像这样调用一个独特的静态方法:
static bool IsColliding(IShape shapeA, IShape shapeB);
但即使它集中了所有内容,它也会使要执行的类型测试的数量加倍,而且我仍然必须在每个第一级“if”中添加一个新案例。
if(shapeA is Rectangle){
if(shapeB is Rectangle){
// Rectangle VS Rectangle
}else if(shapeB is Circle){
// Rectangle VS Circle
}else{
// etc ...
}
}else if(shapeA is Circle){
if(shapeB is Rectangle){
// Rectangle VS Circle
}else{
// etc ...
}
} // etc ...
那么,如何才能更好地设计它呢?
最佳答案
这里有一个使用double dispatch的想法(超越访问者模式的原理):
基本事实是碰撞函数是对称的。 IE。 IsCollision(shapeA, shapeB) = IsCollision(shapeB, shapeA)
。因此,您不需要实现每个 n^2
组合(n
是形状类的数量),但只需实现其中的大约一半:
circle tri rect
circle x x x
tri x x
rec x
所以假设你有一个形状的顺序,每个形状都会与位于它们之前或相等的形状发生碰撞。
在此实现中,形状特定的碰撞处理被分派(dispatch)给一个名为 CollisionHandler
的对象。以下是界面(为简洁起见进行了简化):
interface IShape
{
int CollisionPrecedence { get; }
AbstractCollisionHandler CollisionHandler { get; }
void Collide(AbstractCollisionHandler handler);
}
class AbstractCollisionHandler
{
public virtual void Collides(Circle other) { throw new NotImplementedException(); }
public virtual void Collides(Rect other) { throw new NotImplementedException(); }
}
基于这些接口(interface),具体的形状类有:
class CircleCollisionHandler : AbstractCollisionHandler
{
public override void Collides(Circle other)
{
Console.WriteLine("Collision circle-circle");
}
}
class Circle : IShape
{
public int CollisionPrecedence { get { return 0; } }
public AbstractCollisionHandler CollisionHandler { get { return new CircleCollisionHandler(); } }
public void Collide(AbstractCollisionHandler handler) { handler.Collides(this); }
}
class TriCollisionHandler : AbstractCollisionHandler
{
public override void Collides(Circle other)
{
Console.WriteLine("Collision tri-circle");
}
public override void Collides(Tri other)
{
Console.WriteLine("Collision tri-tri");
}
}
class Tri : IShape
{
public int CollisionPrecedence { get { return 1; } }
public AbstractCollisionHandler CollisionHandler { get { return new TriCollisionHandler(); } }
public void Collide(AbstractCollisionHandler handler) { handler.Collides(this); }
}
调用具体碰撞函数的函数是:
static void Collides(IShape a, IShape b)
{
if (a.CollisionPrecedence >= b.CollisionPrecedence)
b.Collide(a.CollisionHandler);
else
a.Collide(b.CollisionHandler);
}
如果你现在想实现另一个形状 Rect
,那么你必须做三件事:
改变 AbstractCollisionHandler
以包含矩形
abstract class AbstractCollisionHandler
{
...
public virtual void Collides(Rect other) { throw new NotImplementedException(); }
}
实现碰撞处理器
class RectCollisionHandler : AbstractCollisionHandler
{
public override void Collides(Circle other)
{
Console.WriteLine("Collision rect-circle");
}
public override void Collides(Tri other)
{
Console.WriteLine("Collision rect-tri");
}
public override void Collides(Rect other)
{
Console.WriteLine("Collision rect-rect");
}
}
并在Rect
类中实现相关接口(interface)方法:
class Rect : IShape
{
public int CollisionPrecedence { get { return 2; } }
public AbstractCollisionHandler CollisionHandler { get { return new RectCollisionHandler(); } }
public void Collide(AbstractCollisionHandler handler) { handler.Collides(this); }
}
就这么简单。这是一个显示调用函数的小测试程序:
Collides(new Circle(), new Tri());
Collides(new Tri(), new Circle());
Collides(new Rect(), new Circle());
输出:
Collision tri-circle
Collision tri-circle
Collision rect-circle
关于c# - 如何为其派生类型的每种可能组合实现基类的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39317887/