cross-platform c++ classes?

Rob Wehrli plug-devel@lists.PLUG.phoenix.az.us
Mon May 21 14:16:01 2001


David Demland wrote:
> 
> 
> I am creating a blackjack program. There are five objects to this program:
> Card, Deck, Hand, Player, and Dealer. Most of the work each does can be

Don't forget Chip, Bet, Payout, Shoe and Table.

Shoes have some number of Decks and may know how to shuffle Deck(s).
Player(s) make Bet(s).
Table(s) contain Players (always at least two since Dealer->Player)
Chip(s) are representives of Bet amounts and can be transfered between
Player and Table using Payout (+ and -).  Payout might better be as an
interface used by Dealer(s).

> figured out pretty easy. I'll look at just one of the objects for now the
> Dealer. The Dealer is a inherited class from Player. This is because the
> Dealer is nothing more than a super player so to speak.

There are some subtle nuances to consider when inheriting Dealer from
Player.  ISA Dealer a Player?  Using inheritance for specialization is
not uncommon, but oftentimes a poorer choice.  A Dealer must abide by a
different set of rules than a Player.  Does this "specialization" make
the Dealer a unique Player type or does it add confusion to the
inheritance path?  A Dealer (in some rule implementations) must hit on
16 and stand on 17, for example, whereas Players do not have to abide by
this rule.  A Dealer always has a hidden "hole" card, whereas Players
oftentimes do not.  Now that there are some very distinct differences
between Players and Dealers, it almost suggests a different base class
for both rather than a Dealer is a kind of Player.  ...not that you can
not facilitate an implementation where a Dealer ISA Player, but is it a
really good use of inheritance?  Perhaps both are kinds of Participants?
...or some other, more generic base where there can be a virtual
implementation or pure virtual base class for your derived classes to
"follow."

> 
> The Dealer must be able to show that cards that have been dealt out to all
> the players. So this object has a method on it called DisplayAll(void). It

The Dealer shouldn't know anything about how cards are displayed.  Cards
should have their own "Display()" method, where they know how to display
themselves and where the implementation of the display interface is
something they use based on a portable graphics library.  A Dealer might
"call" a Card's Display() method as it is being handed out (or not, in
the case of the hole card).  Display "ALL" seems to only be useful when
turning up the hole card...but then, doesn't it make sense to just call
Display on the hole card at that time rather than bog the system down
running through every card's display method?  ...obviously assuming that
the Display method was moved from Dealer to Card?  What does a Dealer
know about Display-ing a Card?  He assumedly knows when it is the right
time based on some set of Rule(s), but an appropriate Card class could
easily take that role more appropriately.

> would be easy to place all the display code in this method. If this is done
> then this code is tied to one platform. To solve this problem, the
> DisplayAll(void) method calls a function called DisplayCard(Card &P_card).
> This function takes the card passed in and then displays it. This function

Some function needs to have a Card passed to it so that it can display
it?  Why not have the Card know how to Display itself by using a
Graphics "primative" or "library" wrapper interface that abstracts one
or more function calls into a simplified "display engine" for Cards? 
That way, as your underlying Display interface changes, you Cards need
only a pointer to the Display interface to be able to map to a variety
of platform specific display mechanisms through the interface.

> is in a library that holds all the low level display functions. This way to
> replace how these functions work and re-linking the program can have
> different displays. This will allow a library for Win32, X, Mac, DOS, and
> Unix console output without effecting the blackjack application code.

While it may not directly affect the BJ app code, it doesn't lend itself
well to "all-around" good OO practices.  The key to OO is that class
definitions are responsible for implementing the data and methods used
to manipulate the data.  What that means is that a Card should know how
to manipulate a Card's data.  Someone else probably doesn't know jack
about a Card, but as soon as they do, "Pure OO" is broken.  A Card may
know how to *use* a Display "device" so that it can effectively
implement a Display( me ) method, and just as soon as the implementation
of a Card changes, you won't be breaking other code...or at least,
you'll contain the changes within Card's implementation and not have to
rebuild some other piece that has some knowledge of the inner workings
of Card.  That way, Dealer simply calls (uses) Card's Display() method
without caring how it is implemented.  If the implementation of Card's
Display() method changes, Dealer doesn't care, he didn't know anything
more than how to use it to begin with.

> 
> It is simple to do. Just remember to separate the process from display. This

Separation of Data manipulation from Presentation of the Data is a
common architectural theme.  Microsoft calls it "Document/View," where
the document is the data and the view is the presentation of the data. 
The Doc is oftentimes passed to a presentation-centric "view" that knows
how to present the data based on the needs of the windowing subsystem
and implementor's requirements for the presentation of a particular set
of data.  This "close coupling" of data and presentation tend to create
two pieces of code that are somewhat dependent on each other while
separating functionality on a "role" basis.  However, careful observance
of Microsoft's MFC-based implementation of D/V will find an
intelligently established set of relationships between base classes that
must be subclassed by users for their own D/V implementations.  Behind
the scenes, MS has given the programmer as much language "training" and
good language feature usage "principles" as possible then left it up to
them to simply fill in the blanks with their specific implementation
without too much concern for future bending of the rules of good OOP.

> will allow a platform independent application with a small display library
> only. This type of programming allows for the application for become more of
> a "middleware" type of application. This is one of the key points of the
> case against Microsoft about how they want to eliminate "middleware" just to
> ensure that users are on the WinTel platform.

I'd be at least a bit cautious when trying to summarize anything related
to MS.  They have perhaps 35,000 of some of the world's best programming
talent in their "skills inventory."  If you understand that all of what
Windows is (these days) is COM, then you'll find at least some hardship
in swallowing the above statement regarding eliminating middleware. 
Microsoft = componentware.  Everything is a component.  Just about every
single piece of Microsoft code I've seen (past the first couple of
service packs :) above the C library implementation is very
well-written, highly optimized, strong OO-based code.  They have done a
tremendous job at turning their platform into a very unified example of
extremly closed code...that happens to be "open" only because it is
rather popular and many millions of people are "discovering"
new/undocumented things about it everyday.  Nothing could be more
"right" from the perspective of truly "equalizing" Microsoft than the
Justice Department forcing them to make their code open source, but it
is truly anti-business in today's world...and I'm not so sure that it
would even be a "good thing," but at least we'd all learn VERY MUCH if
it happened.

I hope that I haven't insulted you in anyway in this replie, I sometimes
come across as a "know-it-all" and that is at least the furthest thing
from the truth.  Perhaps I'm overly opinionated :) ...if I've offended
you, please accept my apologies, my goal is only to try to share my
experiences rather than incite riots and heated debates...which are
always a possibility when programming is involved.  The truth about MS
(IMHO) is that once you forget about the closed system, once you forget
about the "company profit ethic," you'll find that they have produced
some of the finest code in the world.  You may not be able to appreciate
it fully from the perspective of their other "problems," but they have
invested more good money in their codebase than most nations make in a
year.  I believe that in a not-too distant future, MS will *have* to
open their source to remain competitive, but today, it may as well be a
million years from now.  Remember the Mundie anti-open source message? 
It sounds as if he is facing a rather large pill to swallow.

Lastly, OO was designed to make programming more like real life.  A Cat
"isa" Animal...OK, easy enough.  But a Dealer isa Player?  Logical
outsider's opinions would naturally say, yes..a dealer is playing and
therefore isa player.  But as we start to define what makes a "Player"
and what makes a "Dealer," we soon realize that they are certainly
similar, but one is definitely not the other (inheritance for
specialization "error").  The logical fact that in real life they are
both "players" serves only to confuse the issue and muddy the waters of
"Pure OOP."  They are both Participants in the game, but to go further
and say that the Dealer isa Player suggests the logical, which hides the
pragmatic aspects that make them extremely different, if only in the
smallest, most subtle ways.  Whenever we see these kinds of subtleties,
a warning flag should POP UP and make us ever more careful to ensure
that we're avoiding confusion at all costs, even to the point of
rewriting complete class libraries if necessary.  It is, certainly, a
utopian endeavor that isn't easily communicated to all OO programmers. 
In the Cat isa Animal, sure, it is, we all know it to be true because it
meets the description of what Animals are.  A Dealer isa Player meets
the description at first glance, and thus is the source of potentially
endless confusion because a Cat isa Animal because it "IS," but a
Dealer's *role* as a Player, while similar, is *not* what a Player IS. 
Does that make any sense?  So, a Dealer ISA Participant and a Player ISA
Participant because BOTH "are" a kind of Participant.  It is tricky for
some folks to get the "finer" sense of OO because it seems nit-picky at
times, but subtle "OO errors" have a way of growing into truly amazing
proportions very quickly as programmers take for granted that you said
IT IS, but at some point, it IS NOT. :)  When we say that it is, it has
to be all of the time in every case or else it isn't.  Bird.fly()?
Ostrich isa Bird but _all_ Birds do not fly().  This last point is an
obvious example in the extreme condition, but it serves to illustrate
the mindset needed for better OOP.

For C++ programmers everywhere, read Cline's FAQ book.  While it won't
be overly helpful in answering any major ambiguities or gaps in
knowledge of OOP or C++ language fundamentals, it is very helpful in
being a reminder of what are GOOD things to do and what are BAD things
to do.  C++ is always a learning process for all OO programmers.  Once,
while sitting in a room full of C++ masters from around the world, the
one thing that they could definitively agree upon was that there are
plenty of ways of accomplishing some task using any number of features
found in the (extremely complex) language, but that no matter which path
you take/took, you could learn something new from and about it because
it enables you to model real life...you may model it "properly" or
"improperly" to any degree in between with C++ because it will happily
allow you to say class Dealer : public Player{}; (or any other example) 
Because C++ knows *nothing* about real life.  It is up to us to
interpret life (the artistic part of being an OO programmer) and to
ensure that our model is "correctly" modeling that portion of life that
we're implementing.  Obviously, there is a lot of room for
interpretation in any implementation choice.  And, the one thing that is
certainly a constant in life is that it changes over time.  Just as life
and our model of it must change, our implementation of it must change. 
For that reason, we use the mechanics of the language to facilitate the
eventual changes by using the _4_ key OO "points of interest." 
Abstraction, Inheritance, Polymorphism and Encapsulation.  (Most use "3"
points and leave Abstraction off of the list, but I feel that it is a
necessary "player" to *reaffirm* the GOAL of correct use of the
language.  Of course, an in-depth study of Encapsulation would require a
lot of discussion of abstraction...and a "Pure OOP" discussion on
Inheritence requires it, too, especially in pure virtual bases. 
Polymorphism is uniquely abstraction through multiple implementations,
which is a whole 'nother topic :)

> 
> Thank You,
> 
> David Demland

Take Care.

Rob!