摘要:簡單復(fù)合對象復(fù)合對象也可被稱為容器。它難以序列化對象并像這樣初始化來重建。接口仍然會(huì)導(dǎo)致多種方法計(jì)算。還要注意一些不完全遵循點(diǎn)規(guī)則的方法功能。逐步增加項(xiàng)目的方法和一步加載所有項(xiàng)目的方法是一樣的。另一個(gè)方法就是之前那樣的類定義。
在各個(gè)子類中實(shí)現(xiàn)__init__()注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python
當(dāng)我們看到創(chuàng)建Card對象的工廠函數(shù),再看看Card類設(shè)計(jì)。我想我們可能要重構(gòu)牌值轉(zhuǎn)換功能,因?yàn)檫@是Card類自身應(yīng)該負(fù)責(zé)的內(nèi)容。這會(huì)將初始化向下延伸到每個(gè)子類。
這需要共用的超類初始化以及特定的子類初始化。我們要謹(jǐn)遵Don"t Repeat Yourself(DRY)原則來保持代碼可以被克隆到每一個(gè)子類中。
下面的示例展示了每個(gè)子類初始化的職責(zé):
pythonclass Card: pass class NumberCard(Card): def __init__(self, rank, suit): self.suit = suit self.rank = str(rank) self.hard = self.soft = rank class AceCard(Card): def __init__(self, rank, suit): self.suit = suit self.rank = "A" self.hard, self.soft = 1, 11 class FaceCard(Card): def __init__(self, rank, suit): self.suit = suit self.rank = {11: "J", 12: "Q", 13: "K"}[rank] self.hard = self.soft = 10
這仍是清晰的多態(tài)。然而,缺乏一個(gè)真正的共用初始化,會(huì)導(dǎo)致一些冗余。缺點(diǎn)在于重復(fù)初始化suit,所以必須將其抽象到超類中。各子類的__init__()會(huì)對超類的__init__()做顯式的引用。
該版本的Card類有一個(gè)超類級(jí)別的初始化函數(shù)用于各子類,如下面代碼片段所示:
pythonclass Card: 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().__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ù)。好處是簡化了我們的工廠函數(shù),如下面代碼片段所示:
pythondef 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")
簡化工廠函數(shù)不應(yīng)該是我們關(guān)注的焦點(diǎn)。不過我們從這可以看到一些變化,我們創(chuàng)建了比較復(fù)雜的__init__()函數(shù),而對工廠函數(shù)卻有一些較小的改進(jìn)。這是比較常見的權(quán)衡。
工廠函數(shù)封裝復(fù)雜性
在復(fù)雜的__init__()方法和工廠函數(shù)之間有個(gè)權(quán)衡。最好就是堅(jiān)持更直接,更少程序員友好的__init__()方法,并將復(fù)雜性推給工廠函數(shù)。如果你想封裝復(fù)雜結(jié)構(gòu),工廠函數(shù)可以做的很好。
簡單復(fù)合對象復(fù)合對象也可被稱為容器。我們來看一個(gè)簡單的復(fù)合對象:一副多帶帶的牌。這是一個(gè)基本的集合。事實(shí)上它是如此基本,以至于我們不用過多的花費(fèi)心思,直接使用簡單的list做為一副牌。
在設(shè)計(jì)一個(gè)新類之前,我們需要問這個(gè)問題:使用一個(gè)簡單的list是否合適?
我們可以使用random.shuffle()來洗牌和使用deck.pop()發(fā)牌到玩家手里。
一些程序員急于定義新類就像使用內(nèi)置類一樣草率,這很容易違反面向?qū)ο蟮脑O(shè)計(jì)原則。我們要避免一個(gè)新類像如下代碼片段所示:
pythond = [card6(r+1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade)] random.shuffle(d) hand = [d.pop(), d.pop()]
如果就這么簡單,為什么要寫一個(gè)新類?
答案并不完全清楚。一個(gè)好處是,提供一個(gè)簡化的、未實(shí)現(xiàn)接口的對象。正如我們前面提到的工廠函數(shù)一樣,但在Python中類并不是一個(gè)硬性要求。
在前面的代碼中,一副牌只有兩個(gè)簡單的用例和一個(gè)似乎并不夠簡化的類定義。它的優(yōu)勢在于隱藏實(shí)現(xiàn)的細(xì)節(jié),但細(xì)節(jié)是如此微不足道,揭露它們幾乎沒有任何意義。在本章中,我們的關(guān)注主要放在__init__()方法上,我們將看一些創(chuàng)建并初始化集合的設(shè)計(jì)。
設(shè)計(jì)一個(gè)對象集合,有以下三個(gè)總體設(shè)計(jì)策略:
封裝:該設(shè)計(jì)模式是現(xiàn)有的集合的定義。這可能是Facade設(shè)計(jì)模式的一個(gè)例子。
繼承:該設(shè)計(jì)模式是現(xiàn)有的集合類,是普通子類的定義。
多態(tài):從頭開始設(shè)計(jì)。我們將在第六章看看《創(chuàng)建容器和集合》。
這三個(gè)概念是面向?qū)ο笤O(shè)計(jì)的核心。在設(shè)計(jì)一個(gè)類的時(shí)候我們必須總是這樣做選擇。
1. 封裝集合類以下是封裝設(shè)計(jì),其中包含一個(gè)內(nèi)部集合:
pythonclass Deck: 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()
我們已經(jīng)定義了Deck,內(nèi)部集合是一個(gè)list對象。Deck的pop()方法簡單的委托給封裝好的list對象。
然后我們可以通過下面這樣的代碼創(chuàng)建一個(gè)Hand實(shí)例:
pythond = Deck() hand = [d.pop(), d.pop()]
一般來說,F(xiàn)acade設(shè)計(jì)模式或封裝好方法的類是簡單的被委托給底層實(shí)現(xiàn)類的。這個(gè)委托會(huì)變得冗長。對于一個(gè)復(fù)雜的集合,我們可以委托大量方法給封裝的對象。
2. 繼承集合類封裝的另一種方法是繼承內(nèi)置類。這樣做的優(yōu)勢是沒有重新實(shí)現(xiàn)pop()方法,因?yàn)槲覀兛梢院唵蔚乩^承它。
pop()的優(yōu)點(diǎn)就是不用寫過多的代碼就能創(chuàng)建類。在這個(gè)例子中,繼承list類的缺點(diǎn)是提供了一些我們不需要的函數(shù)。
下面是繼承內(nèi)置list的Deck定義:
pythonclass Deck2(list): def __init__(self): super().__init__(card6(r+1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade)) random.shuffle(self)
在某些情況下,為了擁有合適的類行為,我們的方法將必須顯式地使用超類。在下面的章節(jié)中我們將會(huì)看到其他相關(guān)示例。
我們利用超類的__init__()方法填充我們的list對象來初始化單副撲克牌,然后我們洗牌。pop()方法只是簡單從list繼承過來且工作完美。從list繼承的其他方法也能一起工作。
3. 更多的需求和另一種設(shè)計(jì)在賭場中,牌通常從牌盒發(fā)出,里面有半打喜憂參半的撲克牌。這個(gè)原因使得我們有必要建立自己版本的Deck,而不是簡單、純粹的使用list對象。
此外,牌盒里的牌并不完全發(fā)完。相反,會(huì)插入標(biāo)記牌。因?yàn)橛袠?biāo)記牌,有些牌會(huì)被保留,而不是用來玩。
下面是包含多組52張牌的Deck定義:
pythonclass Deck3(list): def __init__(self, decks=1): super().__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.randint(1, 52) for i in range(burn): self.pop()
在這里,我們使用super().__init__()來構(gòu)建一個(gè)空集合。然后,我們使用self.extend()添加多次52張牌。由于我們在這個(gè)類中沒有使用覆寫,所以我們可以使用super().extend()。
我們還可以通過super().__init__(),使用更深層嵌套的生成器表達(dá)式執(zhí)行整個(gè)任務(wù)。如下面代碼片段所示:
python(card6(r+1, s) for r in range(13) for s in (Club, Diamond, Heart, Spade) for d in range(decks))
這個(gè)類為我們提供了一個(gè)Card實(shí)例的集合,我們可以使用它來模仿賭場21點(diǎn)發(fā)牌的盒子。
在賭場有一個(gè)奇怪的儀式,他們會(huì)翻開廢棄的牌。如果我們要設(shè)計(jì)一個(gè)記牌玩家策略,我們可能需要效仿這種細(xì)微差別。
復(fù)雜復(fù)合對象以下是21點(diǎn)Hand類描述的一個(gè)例子,很適合模擬玩家策略:
pythonclass 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)
在這個(gè)例子中,我們有一個(gè)基于__init__()方法參數(shù)的self.dealer_card實(shí)例變量。self.cards實(shí)例變量是不基于任何參數(shù)的。這個(gè)初始化創(chuàng)建了一個(gè)空集合。
我們可以使用下面的代碼去創(chuàng)建一個(gè)Hand實(shí)例
pythond = Deck() h = Hand(d.pop()) h.cards.append(d.pop()) h.cards.append(d.pop())
缺點(diǎn)就是有一個(gè)冗長的語句序列被用來構(gòu)建一個(gè)Hand的實(shí)例對象。它難以序列化Hand對象并像這樣初始化來重建。盡管我們在這個(gè)類中創(chuàng)建一個(gè)顯式的append()方法,它仍將采取多個(gè)步驟來初始化集合。
我們可以嘗試創(chuàng)建一個(gè)接口,但這并不是一件簡單的事情,對于Hand對象它只是在語法上發(fā)生了變化。接口仍然會(huì)導(dǎo)致多種方法計(jì)算。當(dāng)我們看到第2部分中的《序列化和持久化》,我們傾向于使用接口,一個(gè)類級(jí)別的函數(shù),理想情況下,應(yīng)該是類的構(gòu)造函數(shù)。我們將在第9章的《序列化和存儲(chǔ)——JSON、YAML、Pickle、CSV和XML》深入研究。
還要注意一些不完全遵循21點(diǎn)規(guī)則的方法功能。在第二章《通過Python無縫地集成——基本的特殊方法》中我們會(huì)回到這個(gè)問題。
1. 復(fù)雜復(fù)合對象初始化理想情況下,__init__()方法會(huì)創(chuàng)建一個(gè)對象的完整實(shí)例。這是一個(gè)更復(fù)雜的容器,當(dāng)你在創(chuàng)建一個(gè)包含內(nèi)部其他對象集合的完整實(shí)例的時(shí)候。如果我們可以一步就能構(gòu)建這個(gè)復(fù)合對象,它將是非常有幫助的。
逐步增加項(xiàng)目的方法和一步加載所有項(xiàng)目的方法是一樣的。
例如,我們可能有如下面的代碼片段所示的類:
pythonclass 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)
這個(gè)初始化一步就設(shè)置了所有實(shí)例變量。另一個(gè)方法就是之前那樣的類定義。我們可以有兩種方式構(gòu)建一個(gè)Hand2對象。第一個(gè)示例一次加載一張牌到Hand2對象:
pythond = Deck() P = Hand2(d.pop()) p.cards.append(d.pop()) p.cards.append(d.pop())
第二個(gè)示例使用*cards參數(shù)一步加載一序列的Card類:
pythond = Deck() h = Hand2(d.pop(), d.pop(), d.pop())
對于單元測試,在一個(gè)聲明中使用這種方式通常有助于構(gòu)建復(fù)合對象。更重要的是,這種簡單、一步的計(jì)算來構(gòu)建復(fù)合對象有利于下一部分的序列化技術(shù)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/45381.html
摘要:第一是在對象生命周期中初始化是最重要的一步每個(gè)對象必須正確初始化后才能正常工作。第二是參數(shù)值可以有多種形式。基類對象的方法對象生命周期的基礎(chǔ)是它的創(chuàng)建初始化和銷毀。在某些情況下,這種默認(rèn)行為是可以接受的。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __init__()方法意義重大的原因有兩個(gè)。第一是在對象生命...
摘要:工廠類的函數(shù)就是包裝一些目標(biāo)類層次結(jié)構(gòu)和復(fù)雜對象的構(gòu)造。連貫的工廠類接口在某些情況下,我們設(shè)計(jì)的類在方法使用上定義好了順序,按順序求方法的值很像函數(shù)。這個(gè)工廠類可以像下面這樣使用首先,我們創(chuàng)建一個(gè)工廠實(shí)例,然后我們使用那個(gè)實(shí)例創(chuàng)建實(shí)例。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 通過工廠函數(shù)對 __init_...
摘要:同時(shí),有多個(gè)類級(jí)別的靜態(tài)構(gòu)造函數(shù)的方法。這個(gè)累贅,無論如何,是被傳遞到每個(gè)單獨(dú)的對象構(gòu)造函數(shù)表達(dá)式中。我們可能只有幾個(gè)特定的擔(dān)憂,提供額外關(guān)鍵字參數(shù)給構(gòu)造函數(shù)。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python 沒有__init__()的無狀態(tài)對象 下面這個(gè)示例,是一個(gè)簡化去掉了__init__()的類。這是一個(gè)常見...
摘要:提議以下的新的生成器語法將被允許在生成器的內(nèi)部使用其中表達(dá)式作用于可迭代對象,從迭代器中提取元素。子迭代器而非生成器的語義被選擇成為生成器案例的合理泛化。建議如果關(guān)閉一個(gè)子迭代器時(shí),引發(fā)了帶返回值的異常,則將該值從調(diào)用中返回給委托生成器。 導(dǎo)語: PEP(Python增強(qiáng)提案)幾乎是 Python 社區(qū)中最重要的文檔,它們提供了公告信息、指導(dǎo)流程、新功能的設(shè)計(jì)及使用說明等內(nèi)容。對于學(xué)習(xí)...
閱讀 1318·2021-11-22 14:44
閱讀 2461·2021-09-30 09:47
閱讀 1231·2021-09-09 11:56
閱讀 2095·2021-09-08 09:45
閱讀 4003·2021-08-31 09:40
閱讀 1265·2019-08-30 15:52
閱讀 2053·2019-08-30 14:09
閱讀 1599·2019-08-26 17:04