Implementing __init__() in each subclass
As we look at the factory functions for creating Card
objects, we see some alternative designs for the Card
class. We might want to refactor the conversion of the rank number so that it is the responsibility of the Card
class itself. This pushes the initialization down into each subclass.
This often requires some common initialization of a superclass as well as subclass-specific initialization. We need to follow the Don't Repeat Yourself (DRY) principle to keep the code from getting cloned into each of the subclasses.
The following is an example where the initialization is the responsibility of each subclass:
class Card: pass class NumberCard( Card ): def __init__( self, rank, suit ): self.suit= suit self.rank= str(rank) self.hard = self.soft = rank class AceCard( Card ): def __init__( self, rank, suit ): self.suit= suit self.rank= "A" self.hard, self.soft = 1, 11 class FaceCard( Card ): def __init__( self, rank, suit ): self.suit= suit self.rank= {11: 'J', 12: 'Q', 13: 'K' }[rank] self.hard = self.soft = 10
This is still clearly polymorphic. The lack of a truly common initialization, however, leads to some unpleasant redundancy. What's unpleasant here is the repeated initialization of suit
. This must be pulled up into the superclass. We can have each __init__()
subclass make an explicit reference to the superclass.
This version of the Card
class has an initializer at the superclass level that is used by each subclass, as shown in the following code snippet:
class Card: def __init__( self, rank, suit, hard, soft ): self.rank= rank self.suit= suit self.hard= hard self.soft= soft class NumberCard( Card ): def __init__( self, rank, suit ): super().__init__( str(rank), suit, rank, rank ) class AceCard( Card ): def __init__( self, rank, suit ): super().__init__( "A", suit, 1, 11 ) class FaceCard( Card ): def __init__( self, rank, suit ): super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )
We've provided __init__()
at both the subclass and superclass level. This has the small advantage that it simplifies our factory function, as shown in the following code snippet:
def card10( rank, suit ): if rank == 1: return AceCard( rank, suit ) elif 2 <= rank < 11: return NumberCard( rank, suit ) elif 11 <= rank < 14: return FaceCard( rank, suit ) else: raise Exception( "Rank out of range" )
Simplifying a factory function should not be our focus. We can see from this variation that we've created rather complex __init__()
methods for a relatively minor improvement in a factory function. This is a common trade-off.