我正在尝试拿起C ++。一切进展顺利,直到我的“练习”计划击中我非常轻微的障碍。我认为,这个障碍源于设计问题。
想想二十一点(21)。我做了几节课。
甲板包括 - 为了简单起见 - 有一系列卡片。
- 它可以显示所有卡片
- 它可以洗牌
- 它可以删除卡
一只手是甲板 - 有益于
- 它可以计算出它的手值
- 它可以添加卡片
现在谈谈我的问题 - 播放器设计
-A Player有一只手(私人访问)
我的玩家问题是,手上有一个名为addCardToHand的方法函数。如果我必须创建一个名为addCardToHand(Card c)的Player方法,我会感到冗余/糟糕的设计,其中调用并传递给手中的相同方法。
或
将Hand h声明为公共可访问成员,并在'main()'中执行类似的操作
玩家p;
卡aCard;
p.h.addCard(ACARD);
任何建议都具有启发性和高度赞赏。请记住我正在学习。
答案 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
接口之间的某些转换。