1.11 多策略的init()方法

    有些对象的创建来自多个来源。例如,我们也许需要克隆一个对象作为备忘录模式的一部分,或者冻结一个对象以使它可以用来作为字典的键或放入哈希集合;这也是setfronezenset类的实现方式。

    有很多全局的设计模式使用了多种方式来创建对象。其中一个为多策略初始化,init()函数的实现逻辑较为复杂,也会用到类层次结构中不同的(静态)构造函数。

    它们是非常不同的实现方式,在接口的定义上就有根本区别。

    避免克隆方法 在Python中,克隆方法是很少用到的,因为它会引入不必要的重复对象。使用克隆或许意味着没有正确地理解Python中面向对象的设计原则。 一个克隆方法为对象创建的细节做了不必要的隐藏,被克隆的源对象无法知道目标对象的结构。然而,如果源对象提供了可读性和封装都良好的接口,反过来(目标对象知道源对象的结构)就是可以接受的。

    之前的例子可以被高效的克隆是因为它们非常简单,在下一章中会进行展开描述。然而,为了更详细地说明更多关于对象克隆的基本技巧,我们会讨论一下如何把可变的Hand对象冻结成为不可变的Hand对象。

    以下代码演示了两种创建Hand对象的例子。

    class Hand3:
      def init( self, args, **kw ):
        if len(args) == 1 and isinstance(args[0],Hand3):
          # Clone an existing hand; often a bad idea
          other= args[0]
          self.dealer_card= other.dealer_card
          self.cards= other.cards
        else:
          # Build a fresh, new hand.
          dealer_card,
    cards = args
          self.dealer_card= dealer_card
          self.cards= list(cards)

    第1种方式,Hand3实例从已有的Hand3对象创建。第2种方式,Hand3对象的创建基于Card实例。

    一个fronzenset对象的创建可以基于已有的实例,或基于已存在集合对象。下一章会具体介绍创建不可变对象。基于已有的Hand,创建一个Hand对象,可用于创建一个Hand对象的备忘录模式,例如下面这段实现。

    h = Hand( deck.pop(), deck.pop(), deck.pop() )
    memento= Hand( h )

    我们使用mem ento变量来保存Hand对象。可以用来比较当前对象和之前被处理的对象,我们也可以冻结它用于集合或映射。

    1.11.1 更复杂的初始化方式

    为了将多策略应用于初始化,通常要被迫放弃显式命名的参数。这样的设计虽然获得了灵活性,却使得参数名不够透明,意图不够明显,需要针对不同的使用场景分别提供文档进行解释说明。

    也可以扩展初始化的实现来分离Hand对象。要分离的Hand对象只需修改构造函数。以下代码段演示了如何分离一个Hand对象。

    class Hand4:
      def init( self, args, **kw ):
        if len(args) == 1 and isinstance(args[0],Hand4):
          # Clone an existing handl often a bad idea
          other= args[0]
          self.dealer_card= other.dealer_card
          self.cards= other.cards
        elif len(args) == 2 and isinstance(args[0],Hand4) and 'split'
    in kw:
          # Split an existing hand
          other, card= args
          self.dealer_card= other.dealer_card
          self.cards= [other.cards[kw['split']], card]
        elif len(args) == 3:
          # Build a fresh, new hand.
          dealer_card,
    cards = args
          self.dealercard= dealercard
          self.cards= list(cards)
        else:
          raise TypeError( "Invalid constructor args={0!r}
    kw={1!r}".format(args, kw) )
      def __str
    ( self ):
        return ", ".join( map(str, self.cards) )

    这个设计需要传入更多的纸牌对象来创建合适的、分离的Hand对象。当我们从一个Hand4对象中分离出另一个Hand4对象时,使用split参数作为索引从原Hand4对象中读取Card对象。以下代码演示了我们怎样分离出一个Hand对象。

    d = Deck()
    h = Hand4( d.pop(), d.pop(), d.pop() )
    s1 = Hand4( h, d.pop(), split=0 )
    s2 = Hand4( h, d.pop(), split=1 )

    我们初始化了一个Hand4类的实例然后再分离出其他的Hand4实例,命名为s1s2,然后将Card对象传入每个Hand对象。在21点的规则中,只有当手中两张牌大小相等的时候才可允许分牌。可以看到init()函数的逻辑已经非常复杂,优势在于,它可以基于已有集合同时创建多个像fronzenset这样的对象。然而也将需要更多的注释和文档来说明这些行为。

    1.11.2 静态函数的初始化

    当我们有多种方式来创建一个对象时,有时使用静态函数好过使用复杂的init()函数。

    也可以考虑使用类函数作为初始化的另一种选择,然而将依赖的对象作为参数传入函数会更好。当冻结或分离一个Hand对象时,我们或许希望创建两个新的静态函数来完成任务。使用静态函数作为代理构造函数在语法上略有差别,但是在代码的组织上却有明显的优势。

    以下是Hand类的实现,使用了静态函数来完成初始化,从已有的Hand实例创建两个新实例。

    class Hand5:
      def init( self, dealercard, cards ):
        self.dealer_card= dealer_card
        self.cards = list(cards)
      @staticmethod
      def freeze( other ):
        hand= Hand5( other.dealer_card,
    other.cards )
        return hand
      @staticmethod
      def split( other, card0, card1 ):
        hand0= Hand5( other.dealercard, other.cards[0], card0 )
        hand1= Hand5( other.dealer_card, other.cards[1], card1 )
        return hand0, hand1
      def __str
    ( self ):
        return ", ".join( map(str, self.cards) )

    使用一个函数完成了冻结和备忘录模式,用另一个函数将Hand5对象分离为两个子实例。

    这样既可以增强可读性,也不必使用参数名称来解释接口意图。

    以下代码段演示了我们如何把Hand5对象进行分离:

    d = Deck()
    h = Hand5( d.pop(), d.pop(), d.pop() )
    s1, s2 = Hand5.split( h, d.pop(), d.pop() )

    我们创建了一个Hand5类的h实例,把它分为另外两个Hand实例,名为s1s2,然后分别为它们赋值。而使用init()函数实现同样的功能时,split()静态函数的实现版本简化了很多。然而它并没有遵守一个原则:使用已有的set对象来创建fronzenset对象。