Leveraging __init__() via a factory function

We can build a complete deck of cards via a factory function. This beats enumerating all 52 cards. In Python, there are two common approaches to factories, as follows:

  • We define a function that creates objects of the required classes.
  • We define a class that has methods for creating objects. This is the Factory design pattern, as described in books on object-oriented design patterns. In languages such as Java, a factory class hierarchy is required because the language doesn't support standalone functions.

In Python, a class isn't required to create an object factory, but this can be a good idea when there are related factories or factories that are complex. One of the strengths of Python is that we're not forced to use a class hierarchy when a simple function might do just as well.

While this is a book about object-oriented programming, a function really is fine. It's common, idiomatic Python.

We can always rewrite a function to be a proper callable object if the need arises. From a callable object, we can refactor it into a class hierarchy for our factories. We'll look at callable objects in Chapter 6, Using Callables and Contexts.

The advantage of class definitions is code reuse via inheritance. The purpose of a factory class is to encapsulate the complexities of object construction in a way that's extensible. If we have a factory class, we can add subclasses when extending the target class hierarchy. This can give us polymorphic factory classes; different factory class definitions can have the same method signatures and can be used interchangeably.

If the alternative factory definitions don't actually reuse any code, then a class hierarchy won't be as helpful in Python. We can simply use functions that have the same signatures.

The following is a factory function for our various Card subclasses:

def card(rank: int, suit: Suit) -> Card:
if rank == 1:
return AceCard("A", suit)
elif 2 <= rank < 11:
return Card(str(rank), suit)
elif 11 <= rank < 14:
name = {11: "J", 12: "Q", 13: "K"}[rank]
return FaceCard(name, suit)
raise Exception("Design Failure")

This function builds a Card class from a numeric rank number and a suit object. The type hints clarify the expected argument values. The -> Card hint describes the result of this function, showing that it will create a Card object. We can now build Card instances more simply. We've encapsulated the construction issues into a single factory function, allowing an application to be built without knowing precisely how the class hierarchy and polymorphic design works.

The following is an example of how we can build a deck with this factory function:

deck = [card(rank, suit) 
    for rank in range(1,14) 
        for suit in iter(Suit)] 

This enumerates all the ranks and suits to create a complete deck of 52 cards. This works nicely, because the Enum subclasses will iterate over the list of enumerated values.

We do not need to use iter(Suit). We can use Suit in the preceding generator, and it will work nicely. While the for suit in Suit form will work, mypy will signal errors. Using list(Suit) or iter(Suit) will mute the errors by making the intent clear.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset