Programming for Fun and Profit - Using the Card.dll

Good Abstractions are an Abstruse Art

The caveat divid et impera—divide and conquer—is our guiding principle. A big part of what divide and conquer means relative to programming is that if we divide a problem into the correct abstractions then we can conquer the problem. In fact, taking this concept a bit further, if we divide a problem into good abstractions the problem is significantly easier to conquer.

Finding good abstractions is difficult to do however. Yet, by practicing this abstruse art in a well-understood problem domain—for example, the notion of playing cards—we can become more practiced at finding good abstractions early. Applying the notion of divide and conquer to our card drawing tools, we can quickly resolve on some reasonably good abstractions.

There are only four suits available to us. Certainly we can use integers to express card suits but an enumeration is more expressive. Another similar abstraction is an enumeration representing the possible face values of the cards Ace to King. Listing 4 demonstrates our enumerations.

Listing 4: Using enumerations makes the notion of suit and face-value constrained to a specific set of named values and more expressive to the human reader.

Imports System
Public Enum Face
Ace
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Jack
Queen
King
End Enum
Public Enum Suit
Diamond
Club
Heart
Spade
End Enum

Now when we talk about the value of a card we can do so in the domain of the problem: cards have a suite and a face value.

Clearly in the domain of playing cards is the notion of a card. A single card class would be a good place to add a constructor, initializing the face value and suit, and a good place to add our paint methods. Listing 5 demonstrates the new Card class with the aforementioned features.

From the listing you can see that we made the API methods private. This eliminates consumers of card from calling them directly. The shared Init and Deinit method make a valiant effort to ensure cdtInit is called just once, but in this implementation the Deinit method will need to be called by the consumer. (We could implement IDisposable, have the constructor track how many cards were created, and the Dispose method decrement the counter, calling Deinit when the counter is 0.) Finally, we wrap the cards.dll API methods in wrapper methods to ensure that the device context resource is managed correctly every time. (Notice that we eliminated the width and height arguments (dx and dy respectively) of the two paint methods. The width and height are fixed by the cdtInit method, so we might as well use this information.) The result is that our form is radically simplified and we can reuse Card, Suit, and Face in any Windows solution we'd like. Here is the revised form code (see listing 6).

After creating the Card class all we need to do is create an instance of a card and paint it in the OnPaint event handler.

Thus far we have discovered the easy abstractions. Suit, Face, and Card are pretty easy to find. The hard part is finding as many of the abstractions as we can relative to our problem domain. For example, it is reasonable that we might want a collection of cards, referred to as a deck. However, a pinochle deck has no cards less than 9 but many other games need all of the cards. Furthermore some games like BlackJack might use multiple decks and cards like the Ace may have more than one value depending on context: Ace can be electively used to represent the value of 1 or 11.

Now we have moved into the realm of moderate complexity. What if we want one set of classes to represent all games? What about rules? How do we codify rules to permit changing the rules' object depending on the game selected? What if we want to support Internet play, console play, or multi-player games? Our challenges become significantly greater.

The objective is to figure out what your real goals are and to code to support those goals. If it is possible to support known objectives and permit future growth—for instance, supporting multiple games and one player while leaving room for multi-player in the future—then you are likely to exceed your customer's expectations.