组合类C ++中的冗余代码

时间:2013-02-02 20:47:01

标签: c++ oop inheritance object-composition object-design

我正在尝试拿起C ++。一切进展顺利,直到我的“练习”计划击中我非常轻微的障碍。我认为,这个障碍源于设计问题。

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

  1. 甲板
  2. 播放器
  3. 甲板包括 - 为了简单起见 - 有一系列卡片。  
    - 它可以显示所有卡片  
    - 它可以洗牌  
    - 它可以删除卡

    一只手是甲板 - 有益于
    - 它可以计算出它的手值
    - 它可以添加卡片

    现在谈谈我的问题 - 播放器设计

    -A Player有一只手(私人访问)
    我的玩家问题是,手上有一个名为addCardToHand的方法函数。如果我必须创建一个名为addCardToHand(Card c)的Player方法,我会感到冗余/糟糕的设计,其中调用并传递给手中的相同方法。

    将Hand h声明为公共可访问成员,并在'main()'中执行类似的操作     
    玩家p;     
    卡aCard;     
    p.h.addCard(ACARD);

    任何建议都具有启发性和高度赞赏。请记住我正在学习。

2 个答案:

答案 0 :(得分:3)

这里最好的答案是:它取决于:)我会尝试澄清一点。

第一个问题是:Player类是否有任何内在逻辑?如果它是Hand的简单容器,我只需编写Player.GetHand().AddCard(),因为没有理由在Player.AddCard()方法中复制代码,问题就解决了。

现在假设, 需要实现额外的逻辑,以便将牌添加到玩家手中。这意味着,在向Hand添加卡时,必须调用Player类中的其他代码。在这种情况下,我看到了三种可能的解决方案。

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

  • 限制对Hand的访问,这样任何人都无法从Player中检索它。玩家必须实现AddToHand,RemoveFromHand等方法。可行,但使用起来不方便。

    class Player
    {
    private:
        Hand hand;
    
    public:
        void AddToHand(Card & card) 
        { 
            hand.Add(card); 
        }
    };
    
  • 使用observer pattern。当用户(类用户)调用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接口。手应该显然实现IHand和Player.GetHand()应该返回IHand。诀窍在于,Player返回的IHand不一定必须是Hand实例,而是可以作为装饰器充当用户和真实Hand实例之间的桥梁(参见decorator pattern)。

    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;
        }
    }
    

就个人而言,在第二种情况下,我会选择第二种解决方案 - 它非常简单,并且在需要时可以轻松扩展和重用。

答案 1 :(得分:0)

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

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

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