繁体   English   中英

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

[英]Designing an abstract base class. What types to use, abstract or concrete?

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

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

public abstract bool isOverlapping(GeometricObject2D o) {}

要么

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

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

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

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

以下是访问者模式可用于此的方式。 (我将使用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 . . .;
    }
}

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

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

  • 每次添加新形状时,必须使用方法扩展所有形状,以检查与新添加的形状的重叠
  • 目前还不清楚在Rectangle2DIsOverlapping(Circle2D)Circle2DIsOverlapping(Rectangle2D)Circle2D Rectangle2D重叠Circle2D的确定算法的位置

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

我会怎么做:

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
    }
}

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM