2012-11-08 28 views
4

我只是在我叔叔的推薦下從Java跳入C#。 Java幾何庫看起來比C#的Drawing庫更完整,所以我正在進行一點簡單的移植操作,並增加一些功能以便開始使用C#。設計一個抽象基類。什麼類型可以使用,抽象或具體?

但是,我遇到了一個設計問題,無法辨別哪個會是更好的選擇。要在抽象基類中使用多個方法來獲取具體的數據類型或使用抽象基類作爲其參數的方法較少?

public abstract bool isOverlapping(GeometricObject2D o) {} 

OR

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

我在我的頭,具有該參數告訴我具體的參數防止邏輯錯誤,但我一直在教導如果採用適合於經常使用的抽象數據類型。

+1

就像比較一下,Java的API是如何選擇解決這個問題的? – Servy

+1

我從來沒有見過這樣的抽象方法。像接口這樣的抽象類的地獄​​需要實現。 –

+0

@Servy偉大的問題。它看起來像具體類型。 [鏈接](http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/geom/RectangularShape.html)但是,我想添加更多的功能。 – kicks

回答

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

但是,數學上可能的是,任何類型都知道它是否與任何泛型重疊? 'GeometricObject2D'可以提供足夠的信息來實現該方法嗎? – Servy

+0

@Servy - 不,這就是爲什麼操作本身在基類級抽象的原因。每個具體類都需要一個知道如何用一系列其他幾何類型來測試重疊的實現。當它遇到一個不知道如何處理的類型時,代碼將需要某種回退機制。 (有許多類型的回退機制,例如,每個具體類都可以創建一個自己的近似模型作爲多邊形,然後這些模型可以用來提供一個近似的答案,其他方法,比如拋出異常,也是可能的。) –

+0

如果這個泛型方法的內部只是在類型上進行切換,則使用不同的實現,所以最好每種類型都有一個方法。 – Servy

1

我不認爲你可以實現通用的邏輯來確定是否兩個形狀是重疊的,所以我建議超載isOverlapping的所有類型。

如果您確實使用抽象類型作爲參數,那麼您仍然需要檢查相關的具體類型並執行相關的數學運算。這裏的問題是解決方案不那麼明確 - 您可以傳入一個具體的GeometricObject2D類型,該類型在isOverlapping中沒有實現。那又怎麼樣?拋出異常並不是很好,因爲你的isOverlapping(GeometricObject2D o)調用在技術上受到定義的歡迎。它打破了OOP的觀點:「我們接受幾乎所有GeometricObject2D類型!」。

+0

您提出了一個有趣的觀點。我的目的是能夠使用「is」(java中的「instof」)在形狀之間做到這一點,並從那裏應用邏輯。 – kicks

+0

我認爲這是一個設計缺陷,因爲可能沒有所有形狀的實現方法,但方法簽名提供了其他方法。 – davenewza

5

歡迎來到double dispatch土地!您所看到的問題是虛擬調度語言缺點的典型例證。理想情況下,您正在尋找一個關於多個對象的虛擬函數,因爲確定兩個形狀是否重疊的算法取決於兩個形狀。

您的第二個代碼片段(具有多個具體類)是開始朝向雙調度問題的一個常見解決方案,即visitor pattern。它的工作原理比if鏈更好 - then - else S,但它有幾個缺點:

  • 每次添加一個新的形狀,所有形狀必須與方法擴展到檢查與重疊新增加的形狀
  • 目前尚不清楚到哪裏找的,比如明確的算法,Rectangle2D重疊Circle2D - 在Rectangle2DIsOverlapping(Circle2D),或Circle2DIsOverlapping(Rectangle2D)

Ø常見的解決方案是引入類型ID,並製作處理幾何形狀重疊的代表的二維數組。這遭遇訪問者的第一個問題,但通過集中決策來解決第二個問題。

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

上帝,我的回答是愚蠢的。在這種情況下,無論如何您都不需要調用其他對象,您可以直接將對發送給靜態類。無論如何...我的猜測是,沒有一個令人印象深刻的簡單方法來做到這一點。

+0

這是IMO的最佳解決方案。避免訪問者模式的複雜性。我會實現一個重疊(IGeometry box1,IGeometry box2)作爲一個「全部捕獲」任何未實現的變體 - 也許拋出一個異常? – davenewza