简体   繁体   English

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

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

I am just jumping into C# from Java on the recommendation of my uncle. 我只是根据叔叔的建议从Java跳进C#。 The Java geometry lib seems more complete than C#'s Drawing lib, so I am working on a simple bit of porting with a small bit of added functionality to begin into C#. Java几何lib看起来比C#的Drawing lib更完整,所以我正在进行一些简单的移植,只需要一些额外的功能即可开始使用C#。

However, I have run into a design issue and cannot discern which would be the better choice. 但是,我遇到了一个设计问题,无法辨别哪个是更好的选择。 To have multiple methods in the abstract base class that take concrete datatypes OR to have less methods that take the abstract base as its argument? 要在抽象基类中使用具体数据类型的多个方法,或者使用较少的方法将抽象基数作为其参数?

public abstract bool isOverlapping(GeometricObject2D o) {}

OR 要么

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

The argument I am having in my head tells me concrete arguments prevent logic errors, BUT, I have been taught to always use abstract datatypes if the use fits. 我脑子里的论点告诉我具体的论据可以防止逻辑错误,但是,如果使用的话,我会被教导总是使用抽象数据类型。

If you want to put the operation in the base class, use the abstract type. 如果要将操作放在基类中,请使用抽象类型。 You don't want to have to modify the base class every time you decide to add a new subclass. 每次决定添加新子类时,您都不希望必须修改基类。

An alternative is to use something like the visitor pattern and have each concrete class dispatch in turn to the visitor. 另一种方法是使用访问者模式之类的东西,并将每个具体的类调度依次发送给访问者。 An intersection visitor would then contain all the knowledge of how to compute the intersection of each pair of object types. 然后,交集访问者将包含有关如何计算每对对象类型的交集的所有知识。

Here's how the visitor pattern can be used for this. 以下是访问者模式可用于此的方式。 (I'll use Java syntax since I'm not a C# programmer). (我将使用Java语法,因为我不是C#程序员)。 First, using the visitor pattern here is more complicated than the usual case because you have to modify the operation based on the types of two arguments. 首先,在这里使用访问者模式比通常情况更复杂,因为您必须根据两个参数的类型修改操作。 In effect, you need triple dispatch. 实际上,您需要三次调度。 Languages like Clojure support this directly, but in Java (and probably C#) you need to simulate triple dispatch by using two levels of visitor. 像Clojure这样的语言直接支持这种语言,但在Java(可能还有C#)中,你需要通过使用两个级别的访问者来模拟三重调度。 It's ugly, but the great benefits are that it keeps your geometry hierarchy clean and maintainable, and it centralizes all intersection logic in one compilation unit. 这很丑陋,但最大的好处是它可以保持几何层次结构的清洁和可维护性,并且它将所有交叉逻辑集中在一个编译单元中。

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

Welcome to the double dispatch land! 欢迎来到双重派遣土地! The issue that you are seeing is a classic illustration of the shortcomings of languages with virtual dispatch. 您所看到的问题是虚拟调度语言缺点的经典例证。 Ideally, you are looking for a function that is virtual with respect to more than one object, because the algorithm to determine if two shapes overlap or not depends on both shapes. 理想情况下,您正在寻找一个相对于多个对象是虚拟的函数,因为确定两个形状是否重叠的算法取决于两个形状。

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 - else s, but it has a couple of shortcomings: 它比if - then - else链更好用,但它有一些缺点:

  • 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) 目前还不清楚在Rectangle2DIsOverlapping(Circle2D)Circle2DIsOverlapping(Rectangle2D)Circle2D Rectangle2D重叠Circle2D的确定算法的位置

One common solution is to introduce type IDs, and make a 2D array of delegates that process overlaps of geometric shapes. 一种常见的解决方案是引入类型ID,并创建处理几何形状重叠的代表的2D数组。 This suffers from the first problem of the visitor, but fixes the second by centralizing the decision making. 这会受到访问者的第一个问题的影响,但通过集中决策来解决第二个问题。

What I would do: 我会怎么做:

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

God, my answer is stupid. 上帝,我的答案是愚蠢的。 In this case, you wouldn't need to call the other object anyway, you could just send the pair directly to the static class. 在这种情况下,您无需再调用其他对象,您可以直接将该对发送到静态类。 Anyway... My guess is that there isn't an impressively easy way to do it. 无论如何......我的猜测是没有一种令人印象深刻的简单方法。

I don't think that you can implemented generic logic to determine if two shapes are overlapping, so I would suggest overloading isOverlapping with all the types. 我不认为你可以实现通用逻辑来确定两个形状是否重叠,所以我建议重载isOverlapping与所有类型。

If you do use the abstract type as an argument then you will still need to check the concrete type in question and perform the relevant maths. 如果你确实使用抽象类型作为参数,那么你仍然需要检查有问题的具体类型并执行相关的数学运算。 The problem here is that the solution is less explicit - you could pass in a concrete GeometricObject2D type which has no implementation in isOverlapping . 这里的问题是解决方案不太明确 - 你可以传入一个具体的GeometricObject2D类型,它在isOverlapping没有实现。 Then what? 那又怎样? Throwing an exception is not great here because your isOverlapping(GeometricObject2D o) call is technically welcomed by definition. 抛出异常并不是很好,因为你的isOverlapping(GeometricObject2D o)调用在技术上受到定义的欢迎。 It defeats the point of OOP to say "We accept almost allGeometricObject2D types!". 它违背了OOP的观点,说“我们接受几乎所有的GeometricObject2D类型!”。

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

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