摘自Mastering Object-oriented Python
隱式的基類——object
Python是面向?qū)ο蟪绦蛟O(shè)計語言,有一個類似root的基礎(chǔ)類object類。任何自定義的類,都會隱式繼承object。
class X:
pass
print(X.__class__)
# <class 'type'>
print(X.__class__.__base__)
# <class 'object'>
基類中的初始化方法
- 延遲賦值
這是指先創(chuàng)建類模板,然后在實例中定義屬性并賦值。在Python中,延遲賦值的合法的,但是會存在潛在問題,因此要盡量避免這樣的用法。
在基類中實現(xiàn)__init__()方法
每當(dāng)創(chuàng)建一個對象,Python會先創(chuàng)建一個空對象,然后調(diào)用該對象的__init__()函數(shù)。
一個常見的多態(tài)設(shè)計
class Card:
def __init__(self,rank,suit):
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
使用__init__()方法創(chuàng)建常量清單
可以把創(chuàng)建好的花色對象做緩存,構(gòu)建一個常量池,使得在調(diào)用時對象可被重用,那么性能將得到顯著的提升。
class Suit:
def __init__(self, name, symbol):
self.name = name
self.symbol = symbol
Club, Diamond, Heart, Spade = Suit('Club','?'), Suit('Diamond','?'), Suit('Heart','?'), Suit('Spade','?')
Cards = [AceCard('A', Spade), NumberCard('2',Spade), NumberCard('3',Spade)]
使用工廠函數(shù)調(diào)用__init__()
可以使用工廠函數(shù)來完成所有的Card對象的創(chuàng)建。在Python中實現(xiàn)工廠有兩種途徑
- 定義一個函數(shù),返回不同類的對象
- 定義一個類,包括了創(chuàng)建對象的方法
一個用來生成Card子類對象的工廠函數(shù)例子
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.')
deck = [card(rank,suit) for rank in range(1,14)
for suit in (Club, Diamond, Heart, Spade)]
這里需要注意的是if語句的結(jié)構(gòu),else語句沒有做一些其他步驟,而只是單純地拋出了一個異常。像這樣的catch-all else語句的使用方式是有爭議的。
使用elif簡化設(shè)計來獲得一致性
工廠方法card()中包括了兩個很常見的結(jié)構(gòu)
- if-elif序列
- 映射
這是一個沒有使用映射Card工廠類的例子
def card3(rank, suit):
if rank == 1: return AceCard('A',suit)
elif 2 <= rank < 11: return NumberCard(str(rank),suit)
elif rank == 11: return FaceCard('J', suit)
elif rank == 12: return FaceCard('Q', suit)
elif rank == 13: return FaceCard('K', suit)
else:
raise Exception('Rank out of range.')
相比上一個版本,這個函數(shù)在實現(xiàn)上獲得了更好的一致性。
使用映射和類來簡化設(shè)計
下面這個例子用映射來實現(xiàn),把rank映射為對象,然后又把rank值和suit值作為參數(shù)傳入Card構(gòu)造函數(shù)來創(chuàng)建Card實例。
def card4(rank, suit):
class_ = {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard)
return class_(rank, suit)
實現(xiàn)兩部分映射
并行映射
不推薦使用映射到一個牌面值的元組
def card5(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)
- partial函數(shù)設(shè)計
partial()函數(shù)在面向?qū)ο缶幊讨胁皇呛艹S谩?/p>
- 工廠模式的流暢API設(shè)計
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)
card8 = CardFactory()
deck8 = [card8.rank(r+1).suit(s) for r in range(13)
for s in (Club, Diamond, Heart, Spade)]
這種方法并沒有利用__init__()在Card類層次結(jié)構(gòu)中的作用,改變的是調(diào)用者創(chuàng)建對象的方式。
在每個子類中實現(xiàn)__init__()方法
class Card:
def __init__(self, rank, suit, hard, soft):
self.suit = suit
self.rank = rank
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)
使用__init__()方法和工廠函數(shù)之間存在一些權(quán)衡。通常直接調(diào)用比“程序員友好”的__init__()函數(shù)并把復(fù)雜性分發(fā)給工廠函數(shù)更好。
簡單的組合對象
一個組合對象也可以稱作容器。
設(shè)計集合類,通常有如下3種策略:
- 封裝
- 擴展
- 創(chuàng)建
封裝集合類
定義Deck類,內(nèi)部實際調(diào)用的是list對象。Deck類的pop()方法只是對list對象響應(yīng)函數(shù)的調(diào)用。
class Deck:
def __init__(self):
self._cards = [card8.rank(r+1).suit(s) for r in range(13)
for s in (Club, Diamond, Heart, Spade)]
random.shuffle(self._cards)
def pop(self):
return self._cards.pop()
擴展集合類
pop()函數(shù)只需繼承自list集合就可以很好地工作,其他函數(shù)也一樣。
class Deck2(list):
def __init__(self):
super().__init__(card8.rank(r+1).suit(s) for r in range(13) for s in (Club, Diamond, Heart, Spade))
random.shuffle(self)
完成組合對象的初始化
__init__()初始化方法應(yīng)當(dāng)返回一個完整的對象,這是理想的情況。
不帶__init__()方法的無狀態(tài)對象
對于策略模式的對象來說這是常見的設(shè)計。一個策略對象以插件的形式符合在主對象上來完成一種算法或邏輯,例如GameStrategy類。
(未完待續(xù))