2017-01-13 129 views
1

假設我們有一個巨大的數值笛卡爾座標列表(5; 3)(1; -9)等。爲了表示oop中的一個點,我創建了一個struct/object(c#):驗證對象/結構沒有失敗

public struct Point 
{ 
    public int X, Y { get; } 

    public Point(int x, int y) 
    { 
     // Check if x,y falls within certain boundaries (ex. -1000, 1000) 
    } 
} 

這可能是錯誤的我如何使用結構。我猜你通常不會使用構造函數,但這不是重點。

假設我想添加一個1000點的列表,並且不能保證這些座標位於邊界內。簡單地說,如果這個觀點是無效的,就轉到下一個觀點而不失敗,並告知用戶。至於對象,我認爲Point本身應該負責實例化和驗證,但我不確定在這個特定情況下如何處理它。調用者預先檢查x,y將是最簡單的方法,但它不適用,因爲調用者必須處理應駐留在Point中的邏輯。

驗證和處理不正確的座標而不失敗和違反SRP的最合適方法是什麼?

+0

[System.Drawing.Point(https://msdn.microsoft.com/en-us/ library/system.drawing.point(v = vs.110).aspx),不需要重新發明輪子。 – TheLethalCoder

+0

@TheLethalCoder'System.Drawing.Point'是一個可變結構。啊!它應該被絞死乾燥並儘可能深埋;)。就我個人而言,我避免使用'Point',除非我與API交互的API迫使我使用。 – InBetween

+0

@InBetween只是將它作爲替代選項發佈,而不是重新將其重新寫入。但是你的觀點是我評論它的原因,而沒有把它寫成答案 – TheLethalCoder

回答

4

你不能在構造函數中這樣做;構造函數要麼成功運行,要麼不運行。如果它不是因爲一個例外被提出,那麼,這麼多是因爲默默地失敗。你可能會捕獲異常,但這基本上意味着你使用異常作爲控制流機制,這是一個很大的不,不這樣做!

一個解決方案類似於你在想什麼是使用靜態工廠方法:

public struct Point 
{ 
    public static bool TryCreatePoint(int x, int y, Bounds bounds, out Point point) 
    { 
     if (x and y are inside bounds) 
     { 
      point = new Point(x, y); 
      return true; 
     } 

     point = default(Point); 
     return false; 
    } 

    //... 
} 

和代碼加分名單應該採取行動基於創建成功。

有趣的事實:如果你正在使用C#7的代碼可能看起來會更加清晰:

public static (bool Succesful, Point NewPoint) TryCreatePoint(int x, int y, Bounds bounds) 
{ 
    if (x and y are inside bounds) 
     return (true, new Point(x, y)); 

    return (false, default(Point)); 
} 
+0

C#7特性的良好用法。 – HimBromBeere

+0

@HimBromBeere我喜歡c#7中的新特性。如果你曾經用過功能語言,模式匹配和本地元組支持將會非常棒。 – InBetween

+0

只是爲了好奇:如何調用該方法? – HimBromBeere

1

你想做的事情根本不可能,一個類的實例要麼完全創建,要麼根本不創建。如果構造函數被調用的唯一方法是而不是實例化一個實例是通過拋出一個異常。

所以,你有這兩個機會做到這一點:

  1. 提取物的方法Validate返回一個布爾值,可以從你的類的調用者調用。

    public struct Point 
    { 
        public int X, Y { get; } 
    
        public Point(int x, int y) 
        { 
        } 
    } 
    public bool Validate() { return -1000 <= X && X <= 1000 && -1000 <= Y and Y <= 1000; } 
    

    當然,你可以使用屬性來做同樣的事情。

  2. 在構造

    public Point(int x, int y) 
    { 
        if(x > 1000) throw new ArgumentException("Value must be smaller 1000"); 
        // ... 
    } 
    

但是最好的解決方案恕我直言,是驗證輸入之前,你甚至想創建一個點拋出一個例外,那就是檢查傳遞給構造事先的參數:

if(...) 
    p = new Point(x, y); 
else 
    ... 
1

我能想到的三個選項:

  1. 讓構造函數拋出你捕獲的異常。如果你預計會有很多失敗,這並不是很好。

  2. 在結構上有一個IsValid屬性,您可以使用該屬性在創建後將其過濾掉。

  3. 加載數據的事情也需要負責驗證數據。這將是我的首選。你說「它感覺不對,因爲調用者必須處理應該駐留在Point中的邏輯」,但我認爲檢查加載數據的責任是正確的,是加載數據而不是數據類型。你可以有它在構造函數中拋出一個ArgumentOutOfRangeException如果輸入無效,現在你不再期待任何無效的東西作爲皮帶和護腕進行傳遞的東西。

0

說實話,點應不檢查邊界,所以調用者應該這樣做。一個點在它們的X和Y可以操作的範圍內是有效的(int.MinValue和int.MaxValue)。所以一個-1000000,2000000是一個有效的點。問題在於,這一點對您的應用程序無效,因此您的應用程序(調用者),即使用點的人應該具有該邏輯,而不是在點構造函數內部。

+1

恕我直言。那麼,Point是一個自定義對象,應該代表我想要的東西。我可以說(5000; 5000)不是一個點,因爲它不符合規則。我不會將int的技術限制與商業規則混合在一起。 – DasBoot

+2

是的,這是事實,但一點是一個點,(5000; 5000)仍然是一個有效的點。你的規則是商業規則,對我而言,這不應該影響Point(作爲一個泛型類)。如果你想重複使用同一個類(在這個例子中是Point),你會怎麼做?在另一個項目中,邊界是不同的? –

+0

它確實取決於給定的域規則。如果你有一個明確的說法,即一個點決不能超過某個值,就是這樣。這意味着沒有這樣或那樣的情況(這是我的情況)。在其他情況下,我會完全同意你的觀點,並且可以通過引入Point的子類型(例如LimitedPoint)來澄清。 – DasBoot

0

的Structs在C#是有趣的,所以我會添加一個 「有趣」 的方法來檢查:

struct Point 
{ 
    int _x; 
    public int X 
    { 
     get { return _x; } 
     set { _x = value; ForceValidate(); } 
    } // simple getter & setter for X 

    int _y; 
    public int Y 
    { 
     get { return _y; } 
     set { _y = value; ForceValidate(); } 
    } // simple getter & setter for Y 

    void ForceValidate() 
    { 
     const MAX = 1000; 
     const MIN = -1000; 

     if(this.X >= MIN && this.X <= MAX && this.Y >= MIN && this.Y <= MAX) 
     { 
      return; 
     } 
     this = default(Point); // Yes you can reasign "this" in structs using C# 
    } 
} 
+0

WTF ?!難以想象的這實際上起作用。看起來對我來說很難看,但是它的工作。 – HimBromBeere

+0

@HimBromBeere我知道這很醜陋:D但是當我看到'this'的任務時,它就會讓我大笑。 –

+0

你很明顯應該得到這個奇怪的C#特徵的+1(並且來自MS的球員值得在屁股中踢球)。 – HimBromBeere