4

我只是在我叔叔的推荐下从 Java 跳到 C#。Java 几何库似乎比 C# 的绘图库更完整,所以我正在做一些简单的移植,并添加一些附加功能以开始进入 C#。

但是,我遇到了一个设计问题,无法辨别哪个是更好的选择。在抽象基类中有多个采用具体数据类型的方法,还是有更少的方法采用抽象基作为其参数?

public abstract bool isOverlapping(GeometricObject2D o) {}

或者

public abstract bool isOverlapping(Rectangle2D rect) {}
public abstract bool isOverlapping(Circle2D circ) {}
etc...

我脑海中的论点告诉我具体的论点可以防止逻辑错误,但是,我被教导要在使用合适的情况下始终使用抽象数据类型。

4

4 回答 4

8

如果要将操作放在基类中,请使用抽象类型。您不希望每次决定添加新子类时都必须修改基类。

另一种方法是使用访问者模式之类的东西,并让每个具体类依次分派给访问者。然后,一个交集访问者将包含有关如何计算每对对象类型的交集的所有知识。

以下是访问者模式如何用于此目的。(我将使用 Java 语法,因为我不是 C# 程序员)。首先,这里使用访问者模式比通常情况更复杂,因为您必须根据两个参数的类型修改操作。实际上,您需要三重调度。Clojure 等语言直接支持这一点,但在 Java(可能还有 C#)中,您需要通过使用两个访问者级别来模拟三重调度。它很丑,但最大的好处是它可以保持几何层次结构的清洁和可维护性,并将所有交集逻辑集中在一个编译单元中。

public interface IGeometry {
    void accept(IGeometryVisitor visitor);
}

public interface IGeometryVisitor {
    void visitCircle2D(Circle2D circle);
    void visitBox2D(Box2D box);
    // a method for each concrete type
}

public class Circle2D implements IGeometry {
    public void accept(IGeometryVisitor visitor) {
        visitor.visitCircle2D(this);
    }
}

public class Box2D implements IGeometry {
    public void accept(IGeometryVisitor visitor) {
        visitor.visitBox2D(this);
    }
}

public class IntersectionVisitor implements IGeometryVisitor {
    private boolean mResult;
    private IGeometry mGeometry2;

    public static boolean isOverlapping(IGeometry geometry1, IGeometry geometry2) {
        return new IntersectionVisitor(geometry1, geometry2).mResult;
    }

    private IntersectionVisitor(IGeometry geometry1, IGeometry geometry2) {
        mGeometry2 = geometry2;
        // now start the process
        mGeometry1.accept(this);
    }

    public void visitCircle2D(Circle2D circle) {
        mGeometry2.accept(new Circle2DIntersector(circle));
    }

    private class Circle2DIntersector implements IGeometryVisitor {
        private Circle2D mCircle;
        Circle2DIntersector(Circle2D circle) {
            mCircle = circle;
        }
        public void visitCircle2D(Circle2D circle) {
            mResult = isOverlapping(mCircle, circle);
        }
        public void visitBox2D(Box2D box) {
            mResult = isOverlapping(mCircle, box);
        }
    }

    private class Box2DIntersector implements IGeometryVisitor {
        private Box2D mBox;
        Box2DIntersector(Box2D box) {
            mBox = box;
        }
        public void visitCircle2D(Circle2D circle) {
            mResult = isOverlapping(circle, mBox);
        }
        public void visitBox2D(Box2D box) {
            mResult = isOverlapping(mBox, box);
        }
    }

    // static methods to compute overlap of concrete types
    // For N concrete types there will be N*(N+1)/2 methods
    public static boolean isOverlapping(Circle2D circle1, Circle2D circle2) {
        return /* intersection of 2 circles */;
    }

    public static boolean isOverlapping(Circle2D circle, Box2D box) {
        return . . .;
    }

    public static boolean isOverlapping(Box2D box1, Box2D box2) {
        return . . .;
    }
}
于 2012-11-08T15:27:49.793 回答
5

欢迎来到双发地!您看到的问题是具有虚拟调度的语言缺点的经典例证。理想情况下,您正在寻找一个关于多个对象的虚拟函数,因为确定两个形状是否重叠的算法取决于两个形状。

Your second code snippet (with multiple concrete classes) is a start toward one common solution to the double dispatch problem, known as the visitor pattern. It works better than a chain of if-then-elses, but it has a couple of shortcomings:

  • Every time you add a new shape, all shapes must be extended with a method to check the overlap with the newly added shape
  • It is not clear where to look for the definitive algorithm of, say, Rectangle2D overlapping Circle2D - in Rectangle2D's IsOverlapping(Circle2D), or in Circle2D's IsOverlapping(Rectangle2D)

One common solution is to introduce type IDs, and make a 2D array of delegates that process overlaps of geometric shapes. This suffers from the first problem of the visitor, but fixes the second by centralizing the decision making.

于 2012-11-08T15:38:24.203 回答
2

我会做什么:

public interface IGeometry
{
    bool IsOverlapping(IGeometry geometry);
}

public class Circle2D : IGeometry
{
    public bool IsOverlapping(IGeometry geometry)
    {
        dynamic dyn = geometry;
        return Overlapper.Overlap(this, dyn);
    }
}

public class Box2D : IGeometry
{
    public bool IsOverlapping(IGeometry geometry)
    {
        dynamic dyn = geometry;
        return Overlapper.Overlap(this, dyn);
    }
}

public static class Overlapper
{
    public static bool Overlap(Box2D box1, Box2D box2)
    {
        // logic goes here
    }

    public static bool Overlap(Box2D box1, Circle2D circle1)
    {
        // logic goes here
    }

    public static bool Overlap(Circle2D circle1, Box2D box1)
    {
        return Overlap(box1, circle1); // No need to rewrite it twice
    }

    public static bool Overlap(Circle2D circle1, Circle2D circle2)
    { 
        // logic goes here
    }
}

天哪,我的回答是愚蠢的。在这种情况下,您无论如何都不需要调用另一个对象,您可以直接将这对发送到静态类。无论如何......我的猜测是没有一种非常简单的方法可以做到这一点。

于 2012-11-08T15:52:56.833 回答
1

我不认为你可以实现通用逻辑来确定两个形状是否重叠,所以我建议重载isOverlapping所有类型。

如果您确实使用抽象类型作为参数,那么您仍然需要检查有问题的具体类型并执行相关的数学运算。这里的问题是解决方案不太明确 - 您可以传入一个GeometricObject2DisOverlapping. 然后呢?在这里抛出异常并不是很好,因为isOverlapping(GeometricObject2D o)从技术上讲,您的调用在定义上是受欢迎的。说“我们接受几乎所有GeometricObject2D 类型!”,这违背了 OOP 的观点。

于 2012-11-08T15:32:32.303 回答