2012-11-06 40 views
3

我目前正在嘗試學習SOLID設計原則以及行爲驅動開發,但是我很難將我的頭腦置於單一責任原則的範圍內。我試着爲使用測試驅動開發的c#找到一個很好的教程,但一直沒能找到有價值的東西。無論如何,花了幾天的時間閱讀後,我決定最好的學習方式是經驗,所以我開始創建一個小應用程序,盡我所能使用這些原則。瞭解SOLID設計的單一責任原則

這是一個簡單的保齡球比分計算器。我認爲最好的辦法是從最簡單的部分開始工作,所以我開始在球(或投)水平。現在我已經創建了測試和一個接口和類來處理球(投)分數,這確保它們不是無效的,即。 <10>0。在實現這個之後,我意識到球類基本上只是一個可空的整數,所以也許我甚至不需要它......但現在它在那裏。

接下來的球,我決定接下來要添加的邏輯是一個幀接口和類。這是我陷入困境的地方。這裏是我所在的地方:

namespace BowlingCalc.Frames 
{ 
    public interface IFrame : BowlingCalc.Generic.ICheckValid 
    { 
     void AddThrow(int? score); 
     int? GetThrow(int throw_number); 
    } 
} 

namespace BowlingCalc.Frames 
{ 
    public class Frame : IFrame 
    { 
     private List<Balls.IBall> balls; 
     private Balls.IBall ball; 
     private int frame_number; 

     public Frame(Balls.IBall ball, int frame_number) 
     { 
      this.ball = ball; 
      this.frame_number = frame_number; 
      balls = new List<Balls.IBall>(); 
      check_valid(); 
     } 

     public void AddThrow(int? score) 
     { 
      var current_ball = ball; 
      current_ball.Score = score; 
      balls.Add(current_ball); 
     } 

     public int? GetThrow(int throw_number) 
     { 
      return balls[throw_number].Score; 
     } 

     public void check_valid() 
     { 
      if (frame_number < 0 || frame_number > 10) 
      { 
       throw (new Exception("InvalidFrameNumberException")); 
      } 
     } 

    } 
} 

該框架使用我以前實施的球類通過依賴注入來添加球得分到框架。它還實現了一種方法來返回幀中任何給定球的得分。

我想要做什麼,以及我卡在哪裏,我想添加一種方法來確保幀分數是有效的。 (現在只是1-9幀的簡單情況,其中兩個球的組合得分必須小於等於10,我將繼續討論更復雜的幀10)。

問題是我不知道如何以SOLID的方式實現這一點。我正在考慮在課堂上增加邏輯,但這似乎違背了單一責任原則。然後我想將它作爲一個單獨的接口/類添加,然後在每個幀的分數更新時調用該類。我也想過創建一個單獨的界面,或者類似的東西,並在Frame類中實現它。不幸的是,我不知道這些中的哪一個是最好/最可靠的做事方式。

那麼,實現一種驗證幀分數的方法是一種很好的SOLID方法嗎?


注:任何批評我的代碼是非常受歡迎的。學習創建更好的代碼讓我非常興奮,並樂於接受任何幫助。

回答

2

ICheckValid接口的用途是什麼?你在別處叫check_valid嗎?在我看來,因爲frame_number似乎實際上是Frame的只讀屬性,爲什麼在構造函數中驗證其一致性而沒有任何附加接口會是錯誤的? (構造者應該生產一致的對象,因此可以自由地驗證傳入的參數,但是他們喜歡。)

但是,不要問如何正確驗證此字段,最好問問爲什麼您確實需要Frame中的frame_number屬性?看起來這是某個數組中這個項目的索引 - 您可能只是使用索引,爲什麼將它存儲在Frame?你可能想在以後寫一些if/else邏輯,如:

if (frame_number == 10) { 
    // some rules 
} else { 
    // other rules 
} 

然而,這是不太可能了堅實的方法,因爲你可能會寫出來這一點,如果在Frame許多地方/ else語句。相反,您可以創建一個基類FrameBase,定義大部分邏輯,並在OrdinaryFrameTenthFrame中實施一些抽象方法,您可以在其中定義不同的規則。這將使您完全避免frame_number - 您只需創建九個OrdinaryFrames和一個TenthFrame

至於批評:你的代碼似乎抽象球和框架,但忽略'拋出',或由於某種原因'滾動'。考慮需要添加每個卷的軌跡信息,您需要更改IFrame界面,添加諸如void SetThrowTrajectory(int throwNumber, IThrowTrajectory trajectory)之類的內容。然而,如果你在一個例如IBallRoll,與軌跡相關的功能將容易適合於此(以及一些布爾計算屬性,例如IsStrike,IsSpare)。

+0

哇,謝謝!這真的有很多好的建議,特別是關於使用基本框架類並從中繼承來創建正常框架和框架類。這是很好的建議。 –

+0

相關的,對於框架驗證,你是完全正確的,根本不需要幀數,我絕對是在想它。 –

+0

至於抽象抽象。我很抱歉,我意識到我的球類是不恰當的命名。它應該被稱爲投擲或滾球,因爲它的目的是跟蹤投擲信息,而不是球信息。所以它實際上就是你正在談論的抽象。 –

3

當我認爲SRP時,我傾向於將重點放在責任方面。反過來,班級的名字應理想地描述其責任。對於某些類而言,這是關於類應該成爲什麼的一種類型(如果一個框架缺乏行爲並且僅僅代表狀態),但是當你有行爲責任時,這個名字就是關於類應該是什麼的去做'。

計算分數本身是一個相當小的責任,所以讓我們考慮一些稍微大一些,更自然的可分解的東西。這裏是一個保齡球遊戲簡單的責任,並與合適的偏執封裝的一個可能的崩潰(我們所有的朋友在這裏,但沒有人希望任何人錯誤地欺騙。)

//My job is to keep score; I don't interpret the scores 
public interface IScoreKeeper : IScoreReporter 
{ 
    void SetScore(int bowlerIndex, int frameIndex, int throwIndex, int score); 
} 

//My job is to report scores to those who want to know the score, but shouldn't be allowed to change it 
public interface IScoreReporter 
{ 
    int? GetScore(int bowlerIndex, int frameIndex, int throwIndex);   
} 

//My job is to play the game when told that it's my turn 
public interface IBowler 
{ 
    //I'm given access to the ScoreReporter at some point, so I can use that to strategize 
    //(more realisically, to either gloat or despair as applicable) 

    //Throw one ball in the lane, however I choose to do so 
    void Bowl(IBowlingLane lane); 
} 

//My job is to keep track of the pins and provide score feedback when they are knocked down 
//I can be reset to allow bowling to continue 
public interface IBowlingLane 
{ 
    int? GetLastScore(); 

    void ResetLane(); 
} 

//My job is to coordinate a game of bowling with multiple players 
//I tell the Bowlers to Bowl, retrieve scores from the BowlingLane and keep 
//the scores with the ScoreKeeper. 
public interface IBowlingGameCoordinator 
{ 
    //In reality, I would probably have other service dependencies, like a way to send feedback to a monitor 
    //Basically anything that gets too complicated and can be encapsulated, I offload to some other service to deal with it 
    //I'm lazy, so all I want to do is tell everybody else what to do. 

    void PlayGame(IScoreKeeper gameScore, IEnumerable<IBowler> bowlers, IBowlingLane lane); 
} 

請注意,如果你想使用此模型來簡單地計算分數(不用玩真正的遊戲),你可以有一個存根管家(誰什麼都不做)和一個MockBowlingLane,他產生一系列分數值。 BowlingGameCoordinator負責處理當前的投球手,框架和投擲,因此分數得到積累。

+0

謝謝你的迴應。目前這對我來說有點高。現在我只想專注於得分部分,但這給了我很多想法,並且一旦我完成了一個很好的測試來擴展我的得分手。 –

+0

@Dan Bryant描述的方法有時因過於程序化而受到批評, http://www.carlopescio.com/2011/04/your-coding-conventions-are-hurting-you.html。 (問題在於,你最終可能會得到一個方法類基本上比全局過程更好的結果)。然而,它仍然可能是有用的,並且事實上被廣泛使用。 –