我只是在叔叔的推荐下从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...
我脑子里的论据告诉我具体的论据可以防止逻辑错误,但是,如果使用的话,我已经被教导要总是使用抽象数据类型。
答案 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类型!”。