4

私はC++を手に入れようとしています。私の「練習」プログラムがヒットするまで、すべてが順調に進んでいました。その障害は、設計上の問題に起因すると私は信じています。

ブラックジャック(21)を考えてみてください。いくつかのクラスを作りました。

  1. カード
  2. デッキ
  3. プレーヤー

デッキは、単純化のために、カードの配列を持っています。
-すべてのカードを表示できます
-シャッフル
できます-カードを削除できます

手札はデッキです -利点があり ます -
手札の価値を計算できます
-手札にカードを追加できます

さて、私の問題に取り掛かります-プレーヤーのデザイン

-プレーヤーにはハンドがあります (プライベート アクセス)
プレーヤーに関する私の問題は、ハンドに addCardToHand というメソッド関数があることです。addCardToHand(Card c) という Player メソッドを作成して、同じメソッドを呼び出して手元に渡すと、冗長性/設計の悪さを感じます。

また


Hand h をパブリック アクセス可能なメンバーとして宣言し、「main()」でPlayer p のようなことを行い ます。
カード aCard;
phaddCard(カード);

どんなアドバイスも啓発的であり、高く評価されます。私が学んでいることを覚えておいてください。

4

2 に答える 2

3

ここでの最良の答えは次のとおりです。場合によって異なります:)ただし、少し明確にしようと思います。

最初の質問は、Player クラスに内部ロジックがあるかどうかです。Hand の単純なコンテナであれば、単純に と記述します。メソッドPlayer.GetHand().AddCard()内のコードを複製する理由がなくPlayer.AddCard()、問題が解決されるからです。

ここで、プレーヤーの手札にカードを追加するための追加のロジックを実装する必要があるとします。つまり、カードをハンドに追加するときに、Player クラスの追加コードを呼び出す必要があります。このような場合、3 つの解決策が考えられます。

(ソースはデモンストレーションのみを目的としており、コンパイルできない場合があります)

  • プレイヤーから誰もハンドを取得できないように、ハンドへのアクセスを制限します。プレーヤーは、AddToHand、RemoveFromHand などのメソッドを実装する必要があります。実行可能ですが、使い心地はよくありません。

    class Player
    {
    private:
        Hand hand;
    
    public:
        void AddToHand(Card & card) 
        { 
            hand.Add(card); 
        }
    };
    
  • オブザーバー パターンを使用します。ユーザー (クラス ユーザー) が Player.GetHand().AddCard() を呼び出すと、Hand は Player にデータが変更されたことを通知し、Player はそれに応じて動作できます。これは、C++11 の std::function を使用してイベントを実装することで非常に簡単に実現できます。

    class Deck
    {
    private:
        std::function<void(void)> cardsChanged;
    
    public:
        void Add(Card card)
        {
            // Add a card
            if (!(cardsChanged._Empty()))
                cardsChanged();
        }
    
        void SetCardsChangedHandler(std::function<void(void)> newHandler)
        {
            cardsChanged = newHandler;
        }
    };
    
    // (...)
    
    class Player
    {
    private:
        Hand hand;
    
        void CardsChanged() { ... }
    (...)
    public:
        Player()
        {
            hand.SetCardsChangedHandler([&this]() { this.CardsChanged(); } );               
        }
    };
    
  • 必要なすべてのインターフェイス メソッドで IHand インターフェイスを定義します。Hand は明らかに IHand を実装し、Player.GetHand() は IHand を返す必要があります。秘訣は、Player によって返される IHand が必ずしも Hand インスタンスである必要はなく、代わりに、ユーザーと実際の Hand インスタンスの間のブリッジとして機能するデコレーターにすることができるということです (デコレーター パターンを参照)。

    class IHand
    {
    public:
        virtual void Add(Card card) = 0;
        virtual void Remove(Card card) = 0;
    };
    
    class Hand : public IHand
    { 
        // Implementations
    }
    
    class PlayersHand : public IHand
    {
    private:
        Hand & hand;
        Player & player;
    
    public:
        PlayersHand(Hand & newHand, Player & newPlayer)
        {
            hand = newHand;
            player = newPlayer;
        }
    
        void Add(Card card)
        {
            hand.Add(card);
            player.HandChanged();
        }
    
        // ...
    };
    
    class Player
    {
    private:
        Hand hand;
        PlayersHand * playersHand;
    
    public:
        Player()
        {
            playersHand = new PlayersHand(hand, this);
        }
    
        IHand GetHand()
        {
            return playersHand;
        }
    }
    

個人的には、2 番目のケースでは、2 番目のソリューションを選択します。これは非常に簡単で、さらに必要な場合に拡張して再利用するのが簡単です。

于 2013-02-02T22:14:48.577 に答える
0

関数呼び出しの転送は一般的な方法です。ある程度の抽象化を追加するものと考える必要があります。これはまったく同じことを繰り返しているわけではありませんが (冗長性は意味します)、別の方法を使用して 1 つの方法を実装しています。

Playerのカードキャッシュを追加したり、ユーザーが を呼び出したときに更新する必要があるその他のものを追加したりするなど、将来のいくつかの変更を想像できますaddCardToHand。転送メソッドを実装しなかった場合、キャッシュ更新コードをどこに追加しますか?

また、「インターフェース」は引数とPlayer::addCardToHand同一である必要はなくCard::addCard、これらの関数では戻り値が異なる場合があることに注意してください。この場合はそれほど重要ではないかもしれませんが、通常、転送機能はPlayerのインターフェースと のインターフェースの間の変換Handが追加される場所です。

于 2013-02-02T22:17:10.490 に答える