传递对象和面向对象设计最佳实践

3
我是一名学习编程的大学生。为了练习,我正在编写一个二十一点程序。我使用C++,采用面向对象的方法。
我设计了一个牌组类(Deck class),可以构建和洗牌一副扑克牌。所生成的牌组由52张卡牌类(Card class)对象组成。这就是我目前为止所做的。
我的计划是创建一个荷官对象(Dealer object),拥有一副由52张卡牌组成的牌组,并将其中一张牌发给另一个玩家对象(Player object),然后再发一张给荷官自己的手牌。
我的第一个问题是:在牌组类中把卡牌对象数组设置为公共属性是否不好? 我问这个问题是因为我认为这个数组是一个属性,而我曾被教导说大多数属性应该设置为私有的。我不想在项目中使用不好或懒惰的做法,希望能够用正确的方式完成。
另一个问题是:在我的二十一点程序中,如何将对象(例如卡牌对象)从一个对象(例如荷官)移动到另一个对象(例如玩家)?
4个回答

3
我的第一个问题是:在Deck类中将Card对象的数组公开是否是不好的做法?
是的。通常情况下,数据成员应该始终是私有的。创建一个没有关联数据的接口来定义可以对对象执行哪些操作,并提供实现该接口的具体类,这是良好的面向对象编程。数据是不应该在接口甚至在完全具体的类中可见的实现细节。为什么这是不好的例子,您可能会使用Card对象的数组来实现您的类,但也许稍后您决定使用位集,其中单个位指示卡片是否存在于牌堆中。如果您使Card数组对象公开,则以这种方式更改表示将破坏您的类的其他用户; 但是,如果将其保持为私有,则可以进行此更改而不影响您的类的用户。
另一个问题:如何将对象(例如我的blackjack程序中使用的Card对象)从一个对象(例如dealer)移动到第二个对象,例如player?
这取决于其他对象是否需要访问原始卡对象,其他对象是否会长时间保留原始对象还是只保留短时间,或者其他对象是否能够处理卡的副本。这还取决于卡是具体类还是多态类型,因为多态对象只能通过指针或引用传递(因为通过值传递多态对象将导致代码切割)。对于具体对象,您可以选择传递一个副本,除非需要修改或访问原始对象,在这种情况下需要引用。选择正确的传递对象方式有点复杂,但希望这将澄清:
如果:
它是原始类型或小型、非多态具体类型,不需要修改,则按值传递。
通过常量引用传递--即const T&为某个类型T--如果:
您不需要修改原始对象。
您不需要在函数范围之外读取原始对象。
您不需要在函数范围之外读取对象,或者该类型是非多态且廉价的副本,因此如果需要保留它,则可以创建副本。
通过引用传递--即T&为某个类型T--如果:
您需要修改原始对象。
您不需要在函数范围之外读/写原始对象。
您不需要在函数范围之外读取对象,或者该类型是非多态且廉价的副本,因此如果需要保留它,则可以创建副本。

如果:

  1. 您需要在函数范围内和之后读取原始对象。
  2. 您需要在函数范围内和之后读取对象,且该类型是非多态的,因此无法安全地创建它的副本。

那么应该传递一个常量智能指针-即对于某种类型 T,是 const shared_ptr<const T>&-

  1. 如果您需要在函数范围内和之后读写原始对象。

我按照有意义的顺序给出了上述内容;您应该首先尝试第一个方案,只有在第一个方案不足以胜任时才转向下一个方案。另外,我应该补充说,对于具体的非多态类型,在传递值和传递常量引用之间选择时,boost::call_traits<T>::param_type 可以帮助您确定(根据对象的大小)何时更适合传递值或传递常量引用。


什么是具体对象?请原谅我的无知,我还在学习中。 - user173424
1
@blakejc70,具体对象是没有纯虚函数的对象;换句话说,如果一个类不包含任何虚函数或者包含虚函数但所有的虚函数都已经被定义了,那么它就是具体的。相比之下,抽象类包含一些纯虚函数和一些非虚函数或者带有默认定义的虚函数。而接口或者纯虚类则是只包含纯虚函数的一个类。 - Michael Aaron Safyan

1

在我看来,你试图过度设计了。实际上,一副牌没有任何行为 - 它只是一堆牌。你没有一个荷官告诉牌组自己洗牌;你有一个荷官洗牌。在程序中,我会做同样的事情 - 牌组只是一个由荷官拥有的std::vector<card>(并且它几乎肯定应该是私有的)。

对于发牌,每个玩家都有自己的std::vector<card>用于手牌。然后,荷官将通过调用玩家的deal(或其他)成员函数逐张地向每个玩家发一张牌。


1
我喜欢“牌堆(deck)”的概念。它知道如何重置自己并洗牌。 - Martin York
"你没有一个荷官来告诉牌组自己洗牌" - 记住我们谈论的是面向对象编程,而不是现实:http://geekandpoke.typepad.com/geekandpoke/2010/04/simply-explained.html ;-) - Philipp
@Charles Bailey:这就是为什么他们付出我们高额薪水做出那个决定的原因。 - Martin York
@Martin:是的,这使我们有责任负责地做出这些决定——试图将一副牌变成其他东西将是不负责任的。 - Jerry Coffin
@Jerry Coffin:在这一点上我们只能不同意。 - Martin York
显示剩余2条评论

0

1)通常是的。从概念上讲,Player实例不会干扰属于Dealer的牌,所以应该是私有的。

2)一种方法是:

struct Card
{
    Suit suit;
    Rank rank;
};

class Player
{
private:
    void AddCard(Card card);
    friend class Dealer;
};

class Dealer : public Player
{
public:
    void DealTo(Player& player);
};

Dealer dealer;
Player player2;
dealer.DealTo(player2);

1
记住他想给荷官发一张牌,所以荷官也是玩家。 - Martin York

0
我的第一个问题是:在Deck类中将Card对象的数组公开是不好的实践吗?
这取决于情况。但通常公开数据是不好的。
这是因为公共项成为接口的一部分,因此必须进行维护。
最好将数组作为私有成员,然后通过公共接口公开操作。这将允许您稍后更改私有数据(例如,当您学习如何使用向量时,您可以用向量替换数组。如果数组是公共的,则无法更改类型而不影响使用该事实的每个其他类型)。
原则:隐藏实现细节。
这导致类型之间的耦合更松散。
另一个问题:如何从一个对象(如荷官)移动对象,例如在我的21点程序中使用的Card对象,到第二个对象(如玩家)中?
从一个对象的数组中删除它(并缩小数组以显示它具有较少的卡片(因此您可能需要一个可以更改大小的容器类型)),然后将其放入目标对象中的另一个数组(容器)中。

我已经学习了向量,但现在还是坚持使用数组。在转向STL容器之前,我想练习基本的数组等知识。我想我应该提到这一点。 - user173424
@blakejc70。这正是为什么你的数组应该是私有的原因。这样,当你学会如何使用向量时,如果你决定使用向量而不是数组,你可以修改你的类来使用向量,而不必改变任何其他类。 - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接