tags:

views:

159

answers:

4

I'm a university student learning programming. For practice I'm writing a blackjack program. I'm using C++ and doing an object oriented approach to this.

I have a Deck class designed that basically builds and shuffles a deck of cards. The deck generated is composed of an array of 52 Card class objects. That's what I have so far.

My plan is to have a Dealer object, which has a Deck of 52 Cards deal a Card to a second Player object and then deal to the Dealer's own hand.

My first question is: Is it bad practice to make the array of Card objects public in the Deck class?

I ask this because I consider the array an attribute and was taught that most attributes should be made private. I don't want to start using bad or lazy practices in my projects and want to do it the right way.

Another question: How are objects, such as the Card object used in my blackjack program, generally moved from within an object -like the dealer- to a second object like a player?

+1  A: 

1) Generally, yes. Conceptually, the Player instances do not mess with the cards belonging to the dealer, so it should be private.

2) One way to do that:

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);
In silico
Remeber that he wants to deal a card to the dealer. so the dealer is also a player.
Martin York
+2  A: 

At least IMO, you're trying to overdo it. In reality, a card deck doesn't have any behavior -- it's just a bunch of cards. You don't have a dealer who tells the deck of cards to shuffle itself; you have a dealer who shuffles the deck. I'd do the same in a program -- the deck would just be an std::vector<card> that's owned by the dealer (and it should almost certainly be private).

For dealing, each player would have its own std::vector<card> for its hand. The dealer would then pass each player one card at a time by calling the player's deal (or whatever) member function.

Jerry Coffin
I like the deck concept. It knows how to reset itself and shuffle.
Martin York
Martin, you are correct. I was also thinking of making these classes versatile enough to use in other programs that will require a deck of playing cards. If you've been to a casino, I know it's standard for them to have several decks stacked and shuffled and placed back to back.
blakejc70
@Martin York: But where do you draw the line? Does the deck do a ripple shuffle, overhand shuffle? Does it know how to cut itself? Deal hands? If you're not careful you could end up with a `std::string` fat interface.
Charles Bailey
"You don't have a dealer who tells the deck of cards to shuffle itself"—remember we are talking about OOP, not reality: http://geekandpoke.typepad.com/geekandpoke/2010/04/simply-explained.html ;-)
Philipp
@Charles Bailey: That's why they pay us the big bucks to make that decision.
Martin York
@Martin: Yes, and that puts us under an obligation to make those decisions responsibly -- and trying to turn a deck into something other than a collection of cards will be irresponsible.
Jerry Coffin
@Jerry Coffin: We will just have to disagree there.
Martin York
+1  A: 

My first question is: Is it bad practice to make the array of Card objects public in the Deck class?

Depends. But it is usually bad to expose data publicly.
This is because public items become part of the interface and thus must be maintained.

It would be better to make the array a private member then expose actions via the public interface. This will then allow you to change the private data later (for instance when you learn how to use a vector you may replace the array with a vector. If the array was public you would not be able to change the type without affecting every other type that used that fact that it was an array).

Principle: Hide implementation details.
This leads to a looser coupling between types.

Another question: How are objects, such as the Card object used in my blackjack program, generally moved from within an object -like the dealer- to a second object like a player?

Remove it from the array in one object (and shrink the array to show it has less cards (thus you may want a container type that can change in size)) Then put it into another array (container) in the destination object.

Martin York
I've learned vectors, but sticking with arrays for now. I want to practice basic arrays and such before moving to STL containers. I guess I should have mentioned that
blakejc70
@blakejc70. Which is exactly why your array should be private. So that when you do learn how to use vectors you could (if you decided it was a better choice than an array) modify your class to use a vector without changing any other classes.
Martin York
+3  A: 

My first question is: Is it bad practice to make the array of Card objects public in the Deck class?

Yes. In general, data members should always be private. It is good OOP to create an interface with no associated data that defines what operations can be performed on the object, and then to provide a concrete class that implements that interface. The data is an implementation detail that should not be visible in the interface or even in the fully concrete class. As an example of why it is bad, you might implement your class using an array of Card objects right now, but maybe later you decide to use a bitset where a single bit indicates whether the card is or isn't present in the deck. If you make your Card array object public, changing the representation in that manner would break other users of your class; however, if you keep it private, you can make that change without impacting the users of your class.

Another question: How are objects, such as the Card object used in my blackjack program, generally moved from within an object -like the dealer- to a second object like a player?

It depends on whether the other object needs to access the original card object, whether the other object will hold onto the original object for a long time or only a short time, or if the other object is able to handle only a copy of the card. It also depends on whether the card is a concrete class or a polymorphic type, since polymorphic objects can only be passed by pointer or reference (because passing polymorphic objects by value will lead to code slicing). With concrete objects, you have the option to pass a copy unless you need to modify or access the original object, in which case a reference is needed. Choosing the right way to pass objects is somewhat complicated, but hopefully this will clarify:

Pass by value if:

It is a primitive type or small, non-polymorphic concrete type that does not need to be modified.

Pass by constant reference -- that is const T& for some type T -- if:

  1. You do not need to modify the original object.
  2. You do not need to read the original object outside of the scope of the function.
  3. You do not need to read the object beyond the scope of the function, or the type is non-polymorphic and cheap to copy, so you can create a copy if you need to hang onto it.

Pass by reference -- that is T& for some type T -- if:

  1. You need to modify the original object.
  2. You do not need to read/write the original object outside of the scope of the function.
  3. You do not need to read the object beyond the scope of the function, or the type is non-polymorphic and cheap to copy, so you can create a copy if you need to hang onto it.

Pass by constant smart pointer to a const -- that is const shared_ptr<const T>& for some type T -- if:

  1. You need to read the original object both in the scope of the function and beyond.
  2. You need to read the object both in the scope of the function and beyond, and the type is non-polymorphic so that it is not possible to safely create a copy of it.

Pass by constant smart pointer -- that is const shared_ptr<T>& for some type T -- if:

  1. You need to read and write the orginal object both in the scope of the function and beyond.

I have given each of the above in deliberate order; you should try the first one that will suffice for the job, only moving onto the next if the previous is not sufficient. Also, I should add that boost::call_traits<T>::param_type can help you choose between passing by value and passing by constant reference in the case of concrete non-polymorphic types (it can determine, based on the size of the object, whether pass by value or pass by constant reference is better).

Michael Aaron Safyan
What is meant by concrete objects? Excuse my ignorance I'm still learning.
blakejc70
@blakejc70, a concrete object is an object with no pure virtual functions; in other words, a class is concrete if it does not contain any virtual functions or if it contains virtual functions but all the virtual functions have been given a definition. By contrast, an abstract class is one that contains some pure virtual functions and some functions that are either non-virtual or virtual but with default definitions. And an interface or pure virtual class is one that contains only pure virtual functions.
Michael Aaron Safyan