3

私は現在、SOLID の設計原則とビヘイビア駆動型の開発を学ぼうとしていますが、単一責任の原則について理解するのにかなり苦労しています。テスト駆動開発を使用する c# の優れたチュートリアルを見つけようとしましたが、その意味で価値のあるものを見つけることができませんでした。とにかく、数日かけてそれについて読んだ後、私は経験から学ぶのが最善の方法であると判断したので、できる限りそれらの原則を使用して小さなアプリの作成を開始しました.

シンプルなボウリングスコア計算機です。一番簡単なところからやっていくのが一番いいと思ったので、ボール(投げ)レベルから始めました。現在、ボール (スロー) スコアを処理するためのテストとインターフェイスとクラスを作成しました。<10または>0。これを実装した後、ball クラスは基本的に null 可能な整数にすぎないことに気付きました。そのため、必要ないかもしれませんが、今のところはあります。

ボールに続いて、次に追加する論理的なものはフレーム インターフェイスとクラスであると判断しました。これは私が行き詰まったところです。これが私がいる場所です:

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 の方法で実装する方法がわからないことです。クラスにロジックを追加することを考えていましたが、それは単一責任の原則に反しているようです。次に、それを別のインターフェイス/クラスとして追加し、スコアが更新されたときに各フレームでそのクラスを呼び出すことを考えました。別のインターフェイスなどを作成して、IValidatorそれを Frame クラスに実装することも考えました。残念ながら、これらのうちどれが最善の/堅実な方法であるかはわかりません。

では、フレームのスコアを検証するメソッドを実装するための適切で堅実な方法は何でしょうか?


注: 私のコードに対する批判は大歓迎です。より良いコードを作成する方法を学ぶことにとても興奮しています。

4

2 に答える 2

3

私はSRPというと、責任という側面に重きを置きがちです。クラスの名前は、理想的にはその責任を説明する必要があります。一部のクラスでは、これはそのクラスが「あるべき」ものに関するものです (フレームが動作を欠いており、単に状態を表している場合は、フレームが良い例です) が、動作の責任がある場合、名前はクラスが想定されているものに関するものです。 「する」こと。

スコアを計算すること自体はかなり小さな責任なので、もう少し大きく、より自然に分解できるものを考えてみましょう。これは、単純な責任と適切に妄想的なカプセル化を備えたボウリング ゲームの 1 つの考えられる内訳です (ここでは私たちは皆友達ですが、誰かが間違ってチートすることを誰も望んでいません)。

//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 は、現在のボウラー、フレーム、およびスローを処理するため、スコアが累積されます。

于 2012-11-07T01:53:15.183 に答える
2

ICheckValidインターフェイスの目的は何ですか?他に電話しcheck_validますか?私の意見では、frame_numberは実際には a の読み取り専用プロパティであるように思われるためFrame、追加のインターフェイスを使用せずにコンストラクターでその一貫性を正しく検証するのはなぜ間違っているのでしょうか? (コンストラクターは一貫したオブジェクトを生成することになっているため、受信パラメーターを好きなように自由に検証できます。)

ただし、このフィールドを適切に検証する方法を尋ねるよりも、実際になぜframe_numberプロパティが必要なのかを尋ねる方が良いかもしれませFrameん これは、ある配列内のこのアイテムのインデックスのようです。インデックスを使用するだけでよいのですが、なぜFrame? 後で次のような if/else ロジックを書きたいと思うかもしれません:

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

ただし、この if/else ステートメントを .xml の多くの部分に記述することになるため、これが SOLID アプローチである可能性は低いですFrame。むしろ、基本クラスを作成し、FrameBaseそこにロジックの多くを定義し、いくつかの抽象メソッドを および に実装しOrdinaryFrameTenthFrame、さまざまなルールを定義することができます。これにより、完全に回避できるようになりframe_numberます。9OrdinaryFramesと 1を作成するだけTenthFrameです。

批判については、あなたのコードはボールとフレームを抽象化しているように見えますが、何らかの理由で「スロー」または「ロール」を無視しています。各ロールの軌道情報を追加する必要があると考えてください。IFrameインターフェイスを変更して、 のようなものを追加する必要がありますvoid SetThrowTrajectory(int throwNumber, IThrowTrajectory trajectory)。ただし、たとえば で throw を抽象化するIBallRollと、軌跡関連の機能が簡単にそこに収まります (および、 などのブール演算プロパティIsStrike) IsSpare

于 2012-11-06T09:27:30.533 に答える