[英]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
链更好用,但它有一些缺点:
Rectangle2D
的IsOverlapping(Circle2D)
或Circle2D
的IsOverlapping(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.