第十六章:继承
16.1继承
面向对象语言所拥有的特性是继承,继承指定义一个新的类拥有原来类的方法。
继承最大的好处就是可以在不改变现存代码的情况下添加新的方法,称之为继承是因为新定义的类拥有父类的所有方法。新创建的类知之为子类。
继承是一个功能强大的特性,某些情况下如果没有继承,一些层序几乎无法实现。同时继承有助于代码的重用,你可以在不改变代码的情况下改变父类的行为。
某些情况下继承也能让代码更加难以理解,当某个方法被调用的时候需要寻找其真正的方法定义,相关的代码可能分散在多个模块中,我们需要根据需求来决定是否使用继承。
本章中我们会使用继承编写一个纸牌游戏。
16.2一手牌
大部分的纸牌游戏,我们需要持有一手牌,一手牌同一副牌一样都包含一个纸牌的集合,需要起牌和出牌。
一手牌和一副牌也有区别,依赖于纸牌游戏的玩法,一手牌可能包含了一副牌中所没有的操作。例如:在德州扑克中我们需要分类,或者同其他的玩家的牌进行比较,在桥牌中我们需要计算积分。
一手牌是一副牌的子类,它包含一副牌的所有方法,并且可以添加新的方法。
在类的定义中,父类的名字出现在类定义的括号中:
class Hand(Deck):
表达式该类继承自Deck类。
Hand构造函数初始化类的属性值name和cards字符串name标识哪一手牌,可能为玩牌人的名字,name是可选参数,拥有一个空的字符串作为默认值,cards是拥有的牌,初始化为空的集合:
class Hand(Deck):
def ___init__(self, name=""):
self.cards = []
self.name = name
任何纸牌游戏都有从牌桌上获取牌以及移除牌的操作,移除牌我们可以从父类Deck中继承,这里实现获取纸牌。
def addCard(self, card):
self.cards.append(card)
16.3Dealing cards
现在我们也有了Hand类,我们需要从一副牌中获取牌到手上,那么应该是hand类中还是deck类中呢?一副牌可能由多个人玩,那么我们将该方法放在Deck中。
deal方法应该非常通用,不同的玩法有不同的规则,我们会从一副牌中减少一张牌,获取牌的人增加一张牌。deal方法有两个参数,一个列表表示取牌人手上的牌,以及获取牌的数量。
def deal(self, hands, nCards=999):
nHands = len(hands)
for i in range(nCards):
if self.isEmpty():
break
card = self.popCard()
hand = hands[i % nHands]
hand.addCard(card)
第二个参数ncards可选,默认为一个很大的值,将一副扑克牌获取完。取模操作符计算当前牌应该属于哪一个人。
16.4打印Hand
打印某个人有的牌我们可以包装printDeck以及从Deck中继承的__str__方法,重写父类Deck中的__str__方法
def __str__(self):
s = "Hand " + self.name
if self.isEmpty():
s = s + " is empty\n"
else:
s = s + " contains\n"
return s+Deck.__str__(self)
16.5CardGame类
CardGame类关注所有游戏的共同点,例如:创建一副牌并进行洗牌:
class CardGame:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
这里我们第一次看到初始化方法__init__中除了初始化属性之外,执行一些逻辑,而对于特定的纸牌游戏我们可以继承CardGame实现自己的特殊方法,例如我们实现纸牌游戏老处女。
这种玩法的规则是依照纸牌的花色和值出光手中的牌,例如:梅花4和黑桃4配对,应为他们都是黑色的,方块4和红心4配对都是红色的。游戏开始前,梅花q从纸牌中移除,因此黑桃q没有能够配对的牌,玩家依次获取剩下51张牌中的牌。
当手中没有可配对的牌时游开始,轮流的,每位玩家从旁边玩家的手中抽取一张牌,如果抽取的牌可以和自己手中的牌配对,那么移除,否则这张牌就归自己所有,最后所有的牌都出完了,只剩下不能配对的黑桃q在失败者的手中。
我们用电脑模拟纸牌游戏,电脑扮演所有的玩家,电脑所模拟的游戏和真实游戏有细微区别,真实的玩家会努力让旁边的玩家抽取到自己手中的黑桃q,而电脑只是简单从下家的牌中抽取一张。
16.6OldMaidHand类
玩OldMain的玩家需要普通玩家的一些额外的特性,我们定义一个新的类OldMainHand继承Hand,提供方法removeMatches移除可以配对的牌。
class OldMaidHand(Hand):
def removeMatches(self):
count = 0
originalCards = self.cards[:]
for card in orginalCards:
match = Card(3-card.suit,card.rank)
if match in self.cards:
self.cards.remove(card)
self.cards.remove(match)
print("Hand %s: %s matches %s" % (self.name, card, match))
count = count + 1
return count
对玩家手中的牌做了一个拷贝,因此在移除可以配对的牌的时候还可以对玩家手中的牌进行遍历,对于在遍历过程中对集合进行修改,python会报错。对于手中的每张牌,我们计算出与其相匹配的牌然后再手中进行寻找。如果存在则移除这两张相匹配的纸牌。