Designing comparisons
There are two considerations when defining the comparison operators:
- The obvious question of how to compare two objects of the same class
- The less obvious question of how to compare objects of different classes
For a class with multiple attributes, we often have a profound ambiguity when looking at the comparison operators. It might not be perfectly clear what we're going to compare.
Consider the humble playing card (again!). An expression such as card1 == card2
is clearly intended to compare rank
and suit
. Right? Or is that always true? After all, suit
doesn't matter in Blackjack.
If we want to decide whether a Hand
object can be split, we have to see which of the two code snippets is better. The following is the first code snippet:
if hand.cards[0] == hand.cards[1]
The following is the second code snippet:
if hand.cards[0].rank == hand.cards[1].rank
While one is shorter, brevity is not always best. If we define equality to only consider rank
, we will have trouble defining unit tests because a simple TestCase.assertEqual()
method will tolerate a wide variety of cards when a unit test should be focused on exactly correct cards.
An expression such as card1 <= 7
is clearly intended to compare rank
.
Do we want some comparisons to compare all attributes of a card and other comparisons to compare just rank
? What do we do to order cards by suit
? Furthermore, equality comparison must parallel the hash calculation. If we've included multiple attributes in the hash, we need to include them in the equality comparison. In this case, it appears that equality (and inequality) between cards must be full Card
comparisons because we're hashing the Card
values to include rank
and suit
.
The ordering comparisons between Card
, however, should be rank
only. Comparisons against integers, similarly, should be rank
only. For the special case of detecting a split, hand.cards[0].rank == hand.cards[1].rank
will do nicely because it's explicit on the rule for a split.