Python 面向对象编程指南 读书笔记

第一部分 用特殊方法实现Python风格的类

为了实现更好的可扩展性,Python语言提供了大量的特殊方法,它们大体分为如下几类。python

  • 特性访问
  • 可调用对象
  • 集合
  • 数字
  • 上下文
  • 迭代器

第一章 使用__init()__方法

Python中一切事物皆对象!!!!!!
__init__()方法记住两点:程序员

  • __init()__(初始化)是对象生命周期的开始,每一个对象必须正确初始化才可以正常的工做。
  • __init__()能够赋值

对象的生命周期主要是有建立、初始化、销毁。
‘显示而非隐式’:对于每一个__init__()方法,都应当显示的制定要初始化的变量。
每当建立一个对象,python会建立一个空对象,而后调用该对象的__init__()函数,提供了初始化的操做。算法

# 以21点为例做为说明。
class Card(object):
    def __init__(self, suit, rank):
        self.suit = suit
        self.rank = rank
        self.hard, self.soft = self._points()


class NumberCard(Card):
    def _points(self):
        return int(self.rank), int(self.rank)


class AceCard(Card):
    def _points(self):
        return 1, 11


class FaceCard(Card):
    def _points(self):
        return 10, 10
   
class Suit(object):
    def __init__(self,name,symbol):
        self.name = name
        self.symbol = symbol

Club,Diamond,Heart,Spade = Suit('Club','♣'),Suit('Diamond','♦'),Suit('Heart','♥'),Suit('Spade','♠')

经过工厂函数来调用__init__():

def card(rank, suit):
    if rank == 1:
        return AceCard('A', suit)
    elif 2 <= rank < 11:
        return NumberCard(str(rank), suit)
    elif 11 <= rank < 14:
        name = {11: 'J', 12: 'Q', 13: 'K'}[rank]
        return FaceCard(name, suit)
    else:
        raise Exception("rank out of range")
这个函数经过传入牌面值rank 和花色值suit来建立card对象.
deck = [card(rank, suit) for rank in range(1, 14) for suit in (Club, Diamond, Heart, Spade)]
print(deck[0].rank,deck[0].suit.symbol)

这段代码完成了52张牌对象的建立.编程

使用映射和类来简化设计.

因为类是第一级别的对象,从rank参数射到对象是很容易的事情.
下面的Card类工厂就是使用映射实现的版本.设计模式

def card4(rank,suit):
    class_ = {1:AceCard,11:FaceCard,12:FaceCard,13:FaceCard}.get(rank,NumberCard)
    return class_(rank,suit)

须要修改映射逻辑,除了提供Card子类,还须要提供rank对象的字符串结果.如何实现这两部分映射,有四种常见方案.app

  • 能够创建两个并行映射
  • 能够映射为一个二元组.
  • 能够映射为partial()函数.
  • 能够考虑修改类定义的完成映射逻辑.

    1.并行映射dom

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

这样是不值得作的,带来映射键1,11,12,13的逻辑重复.函数

不要使用并行结构,并行结构应该被元祖或者一些更好的组合所代替ui

  1. 映射到一个牌面值的元组
def card6(rank,suit):
   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)

从rank值映射到类对象时不多见的,并且两个参数只有一个用于对象的初始化.从rank映射到一个相对简单的类或者是函数对象,而没必要提供目的不明确的参数,这才是明智的选择.插件

3.partial 函数设计

def card7(rank,suit):
  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)

经过调用partial()函数而后复制给part_class,完成于rank对象的管的关联,可使用一样的方式来建立suit对象,而且完成最终的Card对象的建立.partial()函数的使用在函数时编程中是很常见的.当时用的是函数而非对象方法的时候就能够考虑使用.

大体上,partial()函数在面向对象编程中不是很经常使用,咱们能够简单的的提供构造函数不一样版原本作相同的事情.partial()函数和构造对象时的流畅接口很相似.

  1. 工厂模式的流畅的API设计

    有时候咱们定义类中的方法必须按照特定的顺序来调用.这种顺序调用的方法和建立 partial() 函数的方式很是相似.

咱们能够在流畅接口函数中设置能够返回self值的rank对象,而后传入花色类从而建立Card实例/

如下是Card工厂流畅接口的定义,包含两个函数,他们必须按照顺序调用.

class CardFactory(object):
    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)))
    
    def suit(self,suit):
        return self.class_(self.rank_str,suit)

先使用rank()函数更新了构造函数的状态,而后经过suit()函数创造了 最终的Card对象.

def A (rank):

    a,b ={                      # 自己为一个字典的传递值.返回对应的值.是dict的get方法
        1: (AceCard, 'A'),
        11: (FaceCard, 'J'),
        12: (FaceCard, 'Q'),
        13: (FaceCard, 'K')
    }.get(rank, (NumberCard, str(rank)))
    return a,b # 返回的是一个tuple(),a 为 <class '__main__.NumberCard'> , b 为'3'

a = A(3)
print(a)

咱们先实例化一个工厂对象,然而后在建立Card实例,这用方式没有利用__init__() 在Card类层级结构的做用,改变的是调用者建立建立对象的方式.

在每一个子类中实现__init__()方法

如下代码演示了如何把__init__()方法提到基类Card中实现的过程.而后在子类中能够重用基类的实现.

class Card(object):
    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(AceCard, self).__init__("A", suit, 1, 11)


class FaceCard(Card):
    def __init__(self, rank, suit):
        super(FaceCard, self).__init__({11: 'J',
                                        12: 'Q',
                                        13: 'K'}[rank], suit, 10, 10)
        

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')

在这里重构了基类中的__init__,虽然将它复杂化,可是这样的权衡是正常的.

使用工厂函数封装的复杂性

在 `__init__()`方法和工厂函数之间存在一些权衡,一般直接调动比'程序员友好'的`__init__()`函数并把复杂性分发给工厂函数更好.当须要封装复杂的构造函数逻辑时,考虑使用工厂函数则更好.

简单的组合对象

一个组合对象也能够称做容器.

若是业务逻辑相对简单,为何定义新类?

类的定义的一个优点是:

  • 类给对象提供了简单的,不须要实现的接口.

设计集合类,一般是下面三种策略:

  • 封装:这个实际是基于现有集合类来定义一个新类,属于外观模式的一个使用场景.
  • 扩展:这个设计是对现有集合类进行扩展,一般使用定义子类的方式来实现.
  • 建立:即从新设计.

以上是面向对象设计的核心.

封装集合类

如下是对内部集合进行封装设计.

import random
class Deck(object):
   def __init__(self):
       self._cards = [card6(r+1,s) for r in range(13) for s in (Club,Diamond,Heart,Spade)]
       random.shuffle(self._cards)
   def pop(self):
       return self._cards.pop()

d = Deck()

hand = [d.pop(),d.pop()]

通常来讲买外观模式或者封装类中的方法实现只是对底层对象相应函数的代理调用.

class Desk3(list):
    def __init__(self, decks=1):
       super(Desk3, self).__init__()
       for i in range(decks):
           self.extend(card6(r + 1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
           random.shuffle(self)
           burn = random.random(1,52)
           for i in range(burn):
               self.pop()

这里咱们使用了基类的 __init__()函数来建立了一个空集合,而后调用了 self.extrend()来吧多副牌加载到发牌机中.

复杂的组合对象

模拟打牌策略

class Hand:
    def __init__(self,dealer_card):
        self.dealer_card = dealer_card
        self.cards = []
        
    def hard_total(self):
        return sum(c.hard for c in self.cards)
    
    def soft_total(self):
        return sum(c.soft for c in self.cards)
d = Deck()
h = Hand(d.pop())
h.cards.append(d.pop())
h.cards.append(d.pop())

须要一个一个的添加很是不方便

完成组合对象的初始化

__init__()初始化方法应当返回一个完成的对象,固然这个是理想的状况.而这样也带来复杂性,由于要建立的对象内部可能包含了集合,集合里面又包含了其余对象.

一般考虑使用一个流畅的接口来完成逐个讲对象添加到集合的操做,同时将集合对象做为构造函数来完成初始化.例如:

class Hand2:
    def __init__(self, dealer_card, *cards):
        self.dealer_card = dealer_card
        self.cards = list(cards)

    def hard_total(self):
        return sum(c.hard for c in self.cards)

    def soft_total(self):
        return sum(c.soft for c in self.cards)

d = Deck()
h = Hand2(d.pop(),d.pop(),d.pop(),d.pop())
print(h.cards)

不带__init__方法的无状态对象

一个策略对象以插件的形式复合在主对象上来完成一种算法或逻辑.它或许以来主对象中的数据,策略对象自身并不携带任何数据.一般策略类会和亨元设计模式一块儿使用:在策略对象中避免内部存储.所须要的值都从策略对象方法参数传入.策略对象自身是无状态的.能够把它看作是一系列函数的集合.

这里定义了一个类给Player实例提供了游戏的选择模式,如下这个策略包括拿牌和下注.

class GameStrategy:
    def insurnace(self, hand):
        return False

    def split(self, hand):
        return False

    def double(self, hand):
        return False

    def hit(self, hand):
        return False

每一个函数须要传入已有的Hand对象,函数逻辑所须要的数据基于现有的可用信息.意味着数据来自于庄家跟玩家的手牌.

一块儿其余的类定义

玩家有两张策略:打牌和下注.每一个Player实例回合模拟器进行不少次交互.咱们这里把这个模拟器命名为Table

Table类的职责须要配合Player实例完成如下事件:

  • 玩家必需要基于玩牌策略初始化一个牌局.
  • 随后玩家会获得一手牌
  • 若是

如下是Table类中投注和牌的逻辑处理相关的代码

class Table:
    def __init__(self):
        # 生成52张牌
        self.deck = Deck()

    def place_bet(self, amount):
        print('Bet', amount)

    def get_hand(self):
        try:
            # self.hand = Hand2(d.pop(), d.pop(), d.pop())
            # self.hole_card = d.pop() 书上是这么写的我认为不对,改成下面写法
            self.hand = Hand2(self.deck.pop(), self.deck.pop(), self.deck.pop())
            self.hole_card = self.deck.pop()
        except IndexError:
            # Out of cards: need to shuffle
            self.deck = Deck()
            return self.get_hand()
        print('Deal', self.hand)
        return self.hand

    # 没有看明白hand从何而来,因此也未找到insure的方法。估计是写错了。
    def can_insure(self, hand):
        return hand.dealer_card.insure

class BettingStrategy:
    def bet(self):
        raise NotImplementedError('No bet method')
    
    def record_win(self):
        pass
    
    def record_lose(self):
        pass
    
class Flat(BettingStrategy):
    def bet(self):
        return 1

上面的那一段代码还未看懂须要之后再来看一遍.

多策略的__init__()方法

class Hand4:
    def __init__(self, *args, **kwargs):
        print(len(args),args,kwargs)
        if len(args) == 1 and isinstance(args[0], Hand4):
            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 kwargs:
            # Split an existing hand
            other, card = args
            self.dealer_card = other.dealer_card
            self.cards = [other.cards[kwargs['split']], card]
        elif len(args) == 3:
            # Bulid a fresh ,new hand
            dealer_card,*cards = args
            self.dealer_card = dealer_card
            self.cards = list(cards)

        else:
            raise TypeError('Invaild constructor args= {0!r} kw={1!r}'.format(args,kwargs))

    def __str__(self):
        return ','.join(map(str,self.cards))


d = Deck()

h = Hand4(d.pop(),d.pop(),d.pop())
print(h)

# s1 = Hand4(h,d.pop(),split = 0)
# s2 = Hand4(h,d.pop(),split = 1)
class Hand5:
    def __init__(self,dealer_card,*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.dealer_card,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))

d = Deck()

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

上面这段代码实现了:当第一轮发完牌是,dealer手牌有一张,Player手牌有两张,当手牌的两张牌相同的时候玩家能够选择分牌,将手中的的两张牌分为两组牌,继续进行游戏.而后发牌器会给Palyer每组牌中个发一张牌

更多的__init__()技术

如下是Player类的定义,初始化使用两个策略对象和一个table对象

相关文章
相关标签/搜索