C# 如何为其派生类型的每个可能组合实现基类的方法

C# 如何为其派生类型的每个可能组合实现基类的方法,c#,oop,inheritance,C#,Oop,Inheritance,我有下面的图形接口,它由多个其他类实现,如矩形、圆形、三角形 interface IShape{ bool IsColliding(IShape other); } IsColliding方法应该检查一个形状是否与另一个碰撞,而不管其具体类型如何。 但是,每对形状(矩形/矩形、矩形/圆、圆/三角形等)都有自己的碰撞检查实现 我正试图为这个问题找到一个好的设计解决方案 简单的方法是切换“其他”形状的类型以调用正确的实现: class Rectangle : IShape{ boo

我有下面的图形接口,它由多个其他类实现,如矩形、圆形、三角形

interface IShape{
    bool IsColliding(IShape other);
}
IsColliding方法应该检查一个形状是否与另一个碰撞,而不管其具体类型如何。 但是,每对形状(矩形/矩形、矩形/圆、圆/三角形等)都有自己的碰撞检查实现

我正试图为这个问题找到一个好的设计解决方案

简单的方法是切换“其他”形状的类型以调用正确的实现:

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”中添加一个新的案例


那么,怎样才能设计得更好呢?

是的,你说得对。在你目前的方法中,你正在打破原则

任务的第一部分已正确完成。您通过为每个形状添加碰撞处理程序来决定如何处理碰撞,例如,您正在使用
IsColliding
方法创建类
Rectangle

然后你需要做另一个决定,如何应对这次碰撞。回应方需要注意这一点。因此,
other
shape的工作就是响应这个碰撞

我建议在合同中添加一种新方法
RespondToCollision(IShape)

在这种情况下,您可以创建以下(伪)场景

Collide(IShape其他){
//对其他.Properties执行smth
其他。对集合的响应(本);
}
集合响应(IShape其他){
//使用this.Propertiesother.Properties执行smth
}
如果形状对于这两个函数都不够参数,您可以使用
OneToAnotherCollisionMethod
将静态类更改为策略类(请查看),并将这些策略作为参数传递


考虑到形状是通过它们的坐标来检查碰撞的事实,通过将目标侧传递给源侧来建立公式并不难,反之亦然。

也许这不是最漂亮的解决方案,但您可以编写接受各种形状的方法

CollisionHandler.Check(Rectangle r = null, Circle c = null, Triangle t = null)
{
   if(r != null && c != null
   {
      return CollisionHandler.CheckRectangleVsCircle(r,c);
   }
}

下面是一个使用双重分派(visitor模式之外的原则)的想法:

基本事实是碰撞函数是对称的。也就是说,
IsCollision(shapeA,shapeB)=IsCollision(shapeB,shapeA)
。因此,您不需要实现每个
n^2
组合(
n
是形状类的数量),而只需要实现其中的大约一半:

         circle  tri rect
circle      x     x    x
tri               x    x
rec                    x
所以假设你们有一个形状的顺序,每个形状都会和位于它们前面或相等的形状发生碰撞

在这个实现中,特定于形状的冲突处理被分派到名为
碰撞处理程序
的对象。以下是接口(为简洁起见简化):

基于这些接口,特定的形状类为:

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);
}
如果现在要实现另一个shape
Rect
,则必须执行三件事:

更改
AbstractCollisionHandler
以包含rect

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
类中实现相关的接口方法:

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

我真的认为你在这里做工程太过分了

所有形状本质上都是顶点和边的集合,甚至是圆(只需选择满足精度要求的顶点数量)

一旦您的所有形状都是点和边的集合,您只需要在一个位置处理碰撞,它将对涉及的任何形状有效


如果你的形状是凸面的,你的碰撞算法可以像检查一个形状是否包含另一个形状的至少一个顶点一样简单,并且
包含(点p)
可以是一个虚拟方法,被每个形状覆盖。

想想看:你需要的是一个根据两个参数变化的行为(
其他

换句话说,你需要的是(或者更具体地说)。起初,像C++中的许多其他“OOP”语言一样,C语言被设计成只支持(如java,而不像普通的Lisp、Culjule、Lua这样的语言,它们被设计用来支持多个调度)。 有一种在单一调度语言上模拟多个调度的经典方法,称为。如果您想遵循该路径,这里已经存在堆栈溢出(使用C#和访问者模式,并且针对与您非常类似的问题),因此我不再重复

我可以补充的是,与Java不同,C#4.0+确实支持多个分派…通过使用
dynamic
关键字,再加上常用的方法重载

所以我们可以有这样的东西:

public abstract class Shape
{
    private CollisionDetector detector = new CollisionDetector();

    public bool IsColliding(Shape that)
    {
        return detector.IsColliding((dynamic) this, (dynamic) that);
    }
}

public class CollisionDetector
{
    public bool IsColliding(Circle circle1, Circle circle2)
    {
        Console.WriteLine("circle x circle");
        return true;
    }

    public bool IsColliding(Circle circle, Rectangle rectangle)
    {
        Console.WriteLine("circle x rectangle");
        return true;
    }

    public bool IsColliding(Rectangle rectangle, Circle circle)
    {
        // Just reuse the previous method, it is the same logic:
        return IsColliding(circle, rectangle);
    }

    public bool IsColliding(Rectangle rectangle1, Rectangle rectangle2)
    {
        Console.WriteLine("rectangle x rectangle");
        return true;
    }
}

public class Circle : Shape { }

public class Rectangle : Shape { }

是的,这将按预期工作。使用
dynamic
将强制延迟绑定,因此实际的方法调用将在运行时选择。当然,这将带来性能成本:动态类型解析比静态类型解析慢得多。如果这不可接受,请使用我上面提到的答案。

它仍然需要更改如果您添加新代码,请更改现有代码shape@RoyalBg是的,但只是在一个地方是的,这与OP给出的最后一个代码相同:)他说他可以集中它,但仍然需要修改它以获得新的形状。不,他的意思是他将代码加倍,因为形状可以作为不同的参数传递,例如第一个=矩形,第二个=圆和虎钳
Collides(new Circle(), new Tri());
Collides(new Tri(), new Circle());
Collides(new Rect(), new Circle());
Collision tri-circle
Collision tri-circle
Collision rect-circle
public abstract class Shape
{
    private CollisionDetector detector = new CollisionDetector();

    public bool IsColliding(Shape that)
    {
        return detector.IsColliding((dynamic) this, (dynamic) that);
    }
}

public class CollisionDetector
{
    public bool IsColliding(Circle circle1, Circle circle2)
    {
        Console.WriteLine("circle x circle");
        return true;
    }

    public bool IsColliding(Circle circle, Rectangle rectangle)
    {
        Console.WriteLine("circle x rectangle");
        return true;
    }

    public bool IsColliding(Rectangle rectangle, Circle circle)
    {
        // Just reuse the previous method, it is the same logic:
        return IsColliding(circle, rectangle);
    }

    public bool IsColliding(Rectangle rectangle1, Rectangle rectangle2)
    {
        Console.WriteLine("rectangle x rectangle");
        return true;
    }
}

public class Circle : Shape { }

public class Rectangle : Shape { }