4

我正在尝试学习 C++。一切都很顺利,直到我的“练习”计划遇到了很小的障碍。我相信,这个障碍源于设计问题。

想想二十一点(21)。我做了几节课。

  1. 卡片
  2. 甲板
  3. 播放器

一个甲板由 - 为简单起见 - 有一系列卡片组成。
-它可以显示所有卡片
-它可以洗牌
-它可以移除卡片

一手牌就是一副牌——它的好处是
——它可以计算它的手牌价值
——它可以把牌加到手上

现在来解决我的问题 - 播放器设计

-A Player Has A hand (private access)
我对玩家的问题是,那只手有一个名为 addCardToHand 的方法函数。如果我必须创建一个名为 addCardToHand(Card c) 的 Player 方法,在该方法中调用并传递给同一方法,我会感到一种冗余/糟糕的设计。

或者

将 Hand h 声明为可公开访问的成员,并在“main()”中执行类似于
Player p 的操作;
卡卡;
phaddCard(卡片);

任何建议都会很有启发性并受到高度赞赏。请记住,我正在学习。

4

2 回答 2

3

这里最好的答案是:这取决于:) 不过,我会尝试澄清一下。

第一个问题是:Player 类有没有内部逻辑?如果是 Hand 的简单容器,我会简单地写Player.GetHand().AddCard(),因为没有理由重复Player.AddCard()方法内部的代码,问题就解决了。

现在让我们假设,需要实现额外的逻辑来将一张牌添加到玩家的手上。这意味着,在将牌添加到手牌时,必须调用 Player 类中的附加代码。在这种情况下,我看到了三种可能的解决方案。

(来源仅用于演示目的,可能无法编译)

  • 限制对 Hand 的访问,这样任何人都无法从 Player 中检索它。玩家必须实现 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;
        }
    }
    

就个人而言,在第二种情况下,我会选择第二种解决方案 - 它非常简单,易于扩展和重用,以防进一步需要。

于 2013-02-02T22:14:48.577 回答
0

函数调用转发是一种常见的做法。您应该将其视为添加某种程度的抽象。这不是再次做同样的事情(冗余意味着),而是使用另一种方法实现一种方法。

您可以想象将来会进行一些修改,例如添加Player卡片缓存,或者在用户调用时需要更新的其他内容addCardToHand。如果你没有实现转发方法,你会在哪里添加缓存更新代码?

另请注意,这些函数的“接口”Player::addCardToHand不需要与Card::addCard参数相同,返回值可以不同。Player也许在这种情况下它不是那么重要,但通常转发功能是可以在' 接口和' 接口之间添加一些翻译的地方Hand

于 2013-02-02T22:17:10.490 回答