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