【问题标题】:Redundant code in composition class C++组合类 C++ 中的冗余代码
【发布时间】:2013-02-02 20:47:00
【问题描述】:

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

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

  1. 甲板
  2. 播放器

一副牌由 - 为简单起见 - 有一组卡片组成。
-它可以显示所有的卡片
-它可以洗牌
-它可以移除卡片

一手牌就是一副牌——好处是
-它可以计算它的手值
-可以加牌

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

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

将 Hand h 声明为可公开访问的成员,并在“main()”中执行类似的操作
播放器 p;
卡片卡片;
p.h.addCard(aCard);

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

【问题讨论】:

  • 您感到冗余并非没有根据,但也并不少见。如果玩家拥有的Hand 是私有的,只需要外部容器的特定读写访问方法(在本例中为Player),那么您在此处看到的应该是自然结果该隐私和访问协议。或者,您可以将玩家 Hand 传递给从 Deck 提取的函数,但这只是设计思想。
  • 您可以通过调用receiveCard 函数之一来移除冗余,并让玩家决定如何处理它。 :-) 扑克玩家肯定希望他的牌是私密的。
  • 这可能会偏离正确的 OO 设计,但您可以完全摆脱 Hand 并将手存储在 Player 中。
  • 谁:我就是这么想的。我找不到任何其他文献告诉我。
  • Bo:ReceiveCard 方法是个好主意。我正在尝试一个更自动化的系统,但我认为我可以使用 receiveCard 方法做更多的事情。

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


【解决方案1】:

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

第一个问题是: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); 
        }
    };
    
  • 使用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 接口。 Hand 显然应该实现 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;
        }
    }
    

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

【讨论】:

  • 其实我之前实现的和你的第一个例子一样。所有逻辑都在每个适当的类中得到处理。我只是觉得在播放器中编写另一个 addCard/calculateHand/... 方法感到不舒服。最后,每个方法只有一行,不包括简单的错误检查。
    至于你的第二个例子,哇。但第一个更像我的实现。它简单而流畅。谢谢。
【解决方案2】:

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

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

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

【讨论】:

  • 呼叫转移让我有点失望。起初感觉像是一个设计缺陷。
  • 呼叫转移本身并不是设计缺陷。不过,如果对 Hand 类中每个方法的访问是从 Player 类透明地转发的(例如,没有额外的代码),我会感到担心。在这种情况下,我建议使用之前提出的解决方案之一。
猜你喜欢
  • 1970-01-01
  • 2010-11-13
  • 2015-04-26
  • 2014-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多