Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

Tip

Don't use parallel structures

Two parallel structures should be replaced with tuples or some kind of proper collection.

Mapping to a tuple of values

The following is the essence of how mapping is done to a two-tuple:

class_, rank_str= {
    1:  (AceCard,'A'),
    11: (FaceCard,'J'),
    12: (FaceCard,'Q'),
    13: (FaceCard,'K'),
    }.get(rank, (NumberCard, str(rank)))
return class_( rank_str, suit )

This is reasonably pleasant. It's not much code to sort out the special cases of playing cards. We will see how it could be modified or expanded if we need to alter the Card class hierarchy to add additional subclasses of Card.

It does feel odd to map a rank value to a class object and just one of the two arguments to that class initializer. It seems more sensible to map the rank to a simple class or function object without the clutter of providing some (but not all) of the arguments.

The partial function solution

Rather than map to a two-tuple of function and one of the arguments, we can create a partial() function. This is a function that already has some (but not all) of its arguments provided. We'll use the partial() function from the functools library to create a partial of a class with the rank argument.

The following is a mapping from rank to a partial() function that can be used for object construction:

from functools import partial
part_class= {
    1:  partial(AceCard,'A'),
    11: partial(FaceCard,'J'),
    12: partial(FaceCard,'Q'),
    13: partial(FaceCard,'K'),
    }.get(rank, partial(NumberCard, str(rank)))
return part_class( suit )

The mapping associates a rank object with a partial() function that is assigned to part_class. This partial() function can then be applied to the suit object to create the final object. The use of partial() functions is a common technique for functional programming. It works in this specific situation where we have a function instead of an object method.

In general, however, partial() functions aren't helpful for most object-oriented programming. Rather than create partial() functions, we can simply update the methods of a class to accept the arguments in different combinations. A partial() function is similar to creating a fluent interface for object construction.

Fluent APIs for factories

In some cases, we design a class where there's a defined order for method usage. Evaluating methods sequentially is very much like creating a partial() function.

We might have x.a().b() in an object notation. We can think of it as Fluent APIs for factories. The x.a() function is a kind of partial() function that's waiting for b(). We can think of this as if it were Fluent APIs for factories.

The idea here is that Python offers us two alternatives for managing a state. We can either update an object or create a partial() function that is (in a way) stateful. Because of this equivalence, we can rewrite a partial() function into a fluent factory object. We make the setting of the rank object a fluent method that returns self. Setting the suit object will actually create the Card instance.

The following is a fluent Card factory class with two method functions that must be used in a specific order:

class CardFactory:
    def rank( self, rank ):
        self.class_, self.rank_str= {
            1:(AceCard,'A'),
            11:(FaceCard,'J'),
            12:(FaceCard,'Q'),
            13:(FaceCard,'K'),
            }.get(rank, (NumberCard, str(rank)))
        return self
    def suit( self, suit ):
        return self.class_( self.rank_str, suit )

The rank() method updates the state of the constructor, and the suit() method actually creates the final Card object.

This factory class can be used as follows:

card8 = CardFactory()
deck8 = [card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade)]

First, we create a factory instance, then we use that instance to create Card instances. This doesn't materially change how __init__() itself works in the Card class hierarchy. It does, however, change the way that our client application creates objects.