关于oop:组合类C中的冗余代码 | 珊瑚贝

Redundant code in composition class C++


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

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

  • 卡片
  • 甲板
  • 播放器
  • 一副牌包括——为了简单起见——有一组卡片。
    – 它可以显示所有的卡片
    – 它可以洗牌
    -它可以移除卡片

    一手牌就是一副牌——它的好处是
    – 它可以计算它的手值
    -它可以添加卡片到手

    现在开始我的问题 – 播放器设计

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

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

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

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


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

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

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

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

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

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      class Player
      {
      private:
          Hand hand;

      public:
          void AddToHand(Card & card)
          {
              hand.Add(card);
          }
      };

    • 使用观察者模式。当用户(类用户)调用 Player.GetHand().AddCard() 时,Hand 会通知 Player,数据已更改,并且 Player 可以采取相应的行动。你可以很容易地使用 C 11 中的 std::function 来实现事件。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      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 实例之间桥梁的装饰器(参见装饰器模式)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      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/… 方法感到不舒服。最后,每个方法只有一行,不包括简单的错误检查。<br/>至于你的第二个例子,哇。但第一个更像我的实现。它简单而流畅。谢谢。


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

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


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

    • 呼叫转移让我有点失望。起初感觉像是一个设计缺陷。
    • 呼叫转移本身并不是设计缺陷。不过,如果对 Hand 类中每个方法的访问是从 Player 类透明地转发的(例如,没有额外的代码),我会担心。在这种情况下,我建议使用先前提出的解决方案之一。


    来源:https://www.codenong.com/14666353/

    微信公众号
    手机浏览(小程序)

    Warning: get_headers(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57

    Warning: get_headers(): Failed to enable crypto in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57

    Warning: get_headers(https://static.shanhubei.com/qrcode/qrcode_viewid_9939.jpg): failed to open stream: operation failed in /mydata/web/wwwshanhubei/web/wp-content/themes/shanhuke/single.php on line 57
    0
    分享到:
    没有账号? 忘记密码?