设计一个抽象基类。使用什么类型,抽象或具体?

时间:2012-11-08 15:26:08

标签: c# java

我只是在叔叔的推荐下从Java跳进C#。 Java几何lib看起来比C#的Drawing lib更完整,所以我正在进行一些简单的移植,只需要一些额外的功能即可开始使用C#。

然而,我遇到了一个设计问题,无法辨别哪个是更好的选择。要在抽象基类中使用具体数据类型的多个方法,或者使用较少的方法将抽象基数作为其参数?

public abstract bool isOverlapping(GeometricObject2D o) {}

OR

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

我脑子里的论据告诉我具体的论据可以防止逻辑错误,但是,如果使用的话,我已经被教导要总是使用抽象数据类型。

4 个答案:

答案 0 :(得分:8)

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

另一种方法是使用类似visitor pattern的内容,并将每个具体的类调度依次发送给访问者。然后,交集访问者将包含有关如何计算每对对象类型的交集的所有知识。

以下是访客模式如何用于此目的。 (我将使用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 . . .;
    }
}

答案 1 :(得分:5)

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

您的第二个代码段(具有多个具体类)是双重调度问题的一个常见解决方案的开始,称为visitor pattern。它比if - then - else链更好用,但它有一些缺点:

  • 每次添加新形状时,必须使用方法扩展所有形状,以检查新添加的形状的重叠
  • 目前尚不清楚在Rectangle2D的{​​{1}}或Circle2D中查找Rectangle2D重叠IsOverlapping(Circle2D)的确定算法的位置}'Circle2D

一种常见的解决方案是引入类型ID,并创建处理几何形状重叠的代表的2D数组。这会引起访问者的第一个问题,但通过集中决策来解决第二个问题。

答案 2 :(得分: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
    }
}

上帝,我的回答是愚蠢的。在这种情况下,您无需再调用其他对象,您可以直接将该对发送到静态类。无论如何......我的猜测是没有一种令人印象深刻的简单方法。

答案 3 :(得分:1)

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

如果你确实使用抽象类型作为参数,那么你仍然需要检查有问题的具体类型并执行相关的数学运算。这里的问题是解决方案不太明确 - 您可以传递一个在GeometricObject2D中没有实现的具体isOverlapping类型。那又怎样?抛出异常并不是很好,因为您的isOverlapping(GeometricObject2D o)调用在技术上受到定义的欢迎。它打败了OOP,说“我们接受几乎所有的GeometricObject2D类型!”。