วิธีคิดแบบนักวิทยาการคอมพิวเตอร์/Inheritance

บทที่ 16 Inheritance
16.1 Inheritance
บทที่ 16 Inheritance.
16.1 Inheritance.
ลักษณะของปัญหาส่วนใหญ่บ่อยครั้งถูกเชื่อมโยงกับ object-oriented programming คือ inheritance ความสามารถในการกำหนด class ใหม่ที่ซึ่งถูกแก้ไขเวอร์ชันของ class ที่มีอยู่
 ข้อได้เปรียบที่สำคัญของลักษณะนี้นี้คุณสามารถเพิ่ม method ใหม่ไปใน class โดยปราศจากการแก้ไข class เดิมที่มีอยู่ ซึ่งมันเรียกว่า inheritance เนื่องจาก class ใหม่สืบทอด methods ทั้งหมดมาจาก class ที่มีอยู่ การขยายคำเปรียบเทียบนี้ class ที่มีอยู่บางครั้งถูกเรียกว่า parent class และ class ใหม่อาจถูกเรียกว่า child class หรือบางครั้งเรียกว่า sub class
 Inheritance เป็นลักษณะเฉพาะที่มีประสิทธิภาพ บางโปรแกรมมีความซับซ้อนมากโดยไม่มี inheritance เราสามารถเขียนให้สั้น รัดกุม และง่ายด้วยมัน (inheritance) เช่นเดียวกัน inheritance สามารถทำให้สะดวกขึ้นในการนำโค้ดไปใช้ใหม่ เนื่องจากคุณสามารถปรับแต่พฤติกรรมการกระทำของ parent classes โดยไม่มีการแก้ไข classes เหล่านั้น ในบางกรณี โครงสร้างของ inheritance สะท้อนให้เห็นปัญหาของโครงสร้างปกติ ซึ่งทำให้โปรแกรมง่ายต่อการเข้าใจ
 ในทางตรงกันข้าม inheritance สามารถทำให้โปรแกรมยากต่อการอ่าน เมื่อ method ถูกเรียก บางครั้งไม่ชัดเจนที่ซึ่งจะหาคำจำกัดความของมัน โค้ดที่เกี่ยวเนื่องกันอาจกระจัดกระจายในระหว่างหลายๆ modules เช่นเดียวกัน หลายสิ่งเหล่านี้สามารถจัดการด้วยการใช้ inheritance สามารถจัดการได้เป็นอย่างดี ถ้าโครงสร้างปกติของปัญหาไม่ให้ยืมตัวมันเองเพื่อ inheritance รูปแบบของการเขียนโปรแกรมนี้จะทำให้เสียหายมากว่าทำให้ดี
 ในบทนี้เราจะพิสูจน์การใช้ของ inheritance เป็นส่วนของโปรแกรมซึ่งเล่น card game Old Maid หนึ่งในเป้าหมายของเราคือ เขียนโค้ดทีสามารถนำไปใช้ใหม่กับ card game อื่นๆ
16.2 A hand of cards
16.2 A hand of cards.
เกือบจะทุกๆ card game เราต้องการแสดงให้เห็น hand of cards แน่นอน hand คล้ายกับ deck ทั้งสองอย่างสร้างขึ้นจากชุดของไพ่ และทั้งสองต้องการการกระทำ เช่น การเพิ่มและการนำไพ่ออก เช่นเดียวกัน เราต้องการความสามารถในการสับไพ่ของทั้ง deck และ hand
 Hand แตกต่างจาก deck ด้วยเช่นกันขึ้นอยู่กับเกมที่จะเล่น เราต้องการแสดงการกระทำบางอย่างบน hand ที่ทำให้ไม่รู้สึกเป็น deck ตัวอย่างใน poker เราอาจจัดกลุ่ม hand (straight, flush, etc.)
 หรือเปรียบเทียบมันกับอีก hand หนึ่ง ใน bridge เราอาจต้องการคำนวณแต้มจาก hand เพื่อทำการประมูล
 สถานการณ์นี้แนะนำให้ใช้ inheritance ถ้า hand เป็น subclass ของ deck มันจะมี methods ทั้งหมดของ deck และสามารถเพิ่ม method ใหม่เข้าไปได้
 ใน class definition ชื่อของ parent class ปรากฏอยู่ในวงเล็บ
 class Hand(Deck):
 pass
 statement นี้บ่งบอกว่า class hand สืบทอดจาก class deck ที่มีอยู่
 hand สร้าง attributes เริ่มต้นสำหรับ hand ซึ่งคือ name และ card โดย string name ระบุถึง hand ซึ่งเป็นไปได้มากด้วยชื่อของผู้เล่นที่ถือมันไว้ name เป็น optional parameter ด้วย string ที่ว่างเปล่าเป็นค่าเริ่มต้น cards เป็น list ของไพ่ใน hand เริ่มต้นด้วย list ที่ว่างเปล่า
 class Hand(Deck):
 def __init__(self, name=""):
 self.cards = []
 self.name = name
 ทุกๆ card game มันจำเป็นที่จะต้องเพิ่มและเอาไพ่ออกจาก deck การเอาไพ่ออกได้ถูกเอาใจใส่เรียบร้อยแล้ว ตั้งแต่ hand สืบทอด removeCard จาก deck แต่เราต้องเขียน addCard
 class Hand(Deck):
 def addCard(self,card) :
 self.cards.append(card)
 อีกครั้ง การละไว้บ่งบอกว่าเราต้องละเลย method อื่นๆ list เชื่อมต่อ method เพิ่มไพ่ใหม่ไปยังท้ายสุดของ list ของไพ่
16.3 Dealing cards
16.3 Dealing cards.
ตอนนี้เรามี class hand เราต้องการแจกไพ่จาก deck เข้าไปยัง hands มันจะไม่ชัดเจนในทันทีไม่ว่า method นี้จะเข้าไปอยู่ใน class hand หรือไม่ ใน class deck ตั้งแต่มันทำงานบน deck เดียวและหลายๆ hands มันเป็นธรรมดาที่วางมันลงใน deck 
 การแจกไพ่ควรยุติธรรม ต่างเกมต่างมีความต้องการที่แตกต่างกัน เราอาจต้องการแจกไพ่ออกจาก deck ทั้งหมดในครั้งเดียวหรือเพิ่มไพ่ในแต่ละ hand
 Deal มีพารามิเตอร์ 2 ตัว เป็น list ของ hands และจำนวนไพ่ทั้งหมดที่แจก หากใน deck มีไพ่ไม่เพียงพอ method deal จะทำการหยุด
 class Deck :
 def deal(self, hands, nCards=999):
 nHands = len(hands)
 for i in range(nCards):
 if self.isEmpty: break # break if out of cards
 card = self.popCard # take the top card
 hand = hands[i % nHands] # whose turn is next?
 hand.addCard(card) # add the card to the hand
 พารามิเตอร์ตัวที่สอง nCards เป็น optional ค่าเริ่มต้นเป็น large number ที่มีประสิทธิภาพ หมายถึงไพ่ทั้งหมดใน deck จะถูกแจกจนหมด
 ตัวแปร loop i เริ่มจาก 0 ถึง nCards -1 แต่ละครั้งที่ผ่านเข้าไปใน loop ไพ่จะถูกเอาออกจาก deck โดยใช้ list method pop ซึ่งจะเอาออกและคืนรายการสุดท้ายใน list 
 Modulus operator (%) ยอมให้เราแจกไพ่ 1 ใบต่อครั้งของแต่ละ hand เมื่อ i เท่ากับจำนวน hands ใน list expression i% nHands กลับไปยังจุดเริ่มต้นของ list (index 0)
16.4 Printing a Hand
16.4 Printing a Hand.
การ print หัวข้อของ hand เราสามารถได้เปรียบของ printDeck และ __str__ method สืบทอดจาก deck ตัวอย่างเช่น
 class Deck :
 »> deck = Deck
 »> deck.shuffle
 »> hand = Hand("frank")
 »> deck.deal([hand], 5)
 »> print hand
 Hand frank contains
 2 of Spades
 3 of Spades
 4 of Spades
 Ace of Hearts
 9 of Clubs
 มันไม่ใช่ hand ที่ใหญ่ แต่มันเป็นการทำ straight flush
 ถึงแม้มันสะดวกที่จะสืบทอด existing methods มีข้อมูลที่มากขึ้นใน Hand object เราอาจต้องการรวมเมื่อเรา print ออกมา ทำโดยคุณสามารถให้ __str__ method ใน Hand class ผ่านข้ามหนึ่งใน Deck class
 class Hand(Deck)
 def __str__(self):
 s = "Hand " + self.name
 if self.isEmpty:
 return s + " is empty\n"
 else:
 return s + " contains\n" + Deck.__str__(self)
 ในขั้นต้น s คือ string ที่แสดงถึง hand ถ้า hand ว่างเปล่า โปรแกรมจะให้คำว่า empty และ return ค่าผลลัพธ์ออกมา
 ไม่เช่นนั้น โปรแกรมจะผนวกการบรรจุคำ และ string เป็นตัวแทนของ deck คำนวณโดยการสร้าง __str__ method ใน Deck class บน self
 มันอาจดูแปลกที่ส่ง self ซึ่งอ้างถึง Hand ปัจจุบัน จนกระทั่งคุณจำได้ว่า Hand คือ ชนิดของ Deck ดังนั้น Hand objects จึงสามารถทำทุกสิ่งที่ Deck objects สามารถทำได้ ดังนั้น มันถูกต้องที่จะส่ง Hand ไปยัง Deck method
 โดยทั่วไป มันจะถูกต้องเสมอที่ใช้ instance ของ subclass ในตำแหน่งของ instance ของ parent class
16.5 The CardGame class
16.5 The CardGame class.
CardGame class ดูแลงานพื้นฐานทั่วไปของเกมทั้งหมด เช่น การสร้าง deck และ การสับไพ่
 class CardGame:
 def __init__(self):
 self.deck = Deck
 self.deck.shuffle
 นี้คือ case แรกที่เราได้เห็น initialization method กระทำการคำนวณที่สำคัญหลัง initializing attributes 
 การ implement เกมโดยเฉพาะ เราสามารถสืบทอดจาก CardGame และเพิ่มลักษณะต่างๆ เข้าไปสำหรับเกมใหม่ ตัวอย่างเช่น พวกเราจะเขียนเลียนแบบของ Old Maid
 Object ของ Old Maid คือ กำจัดไพ่ในมือให้หมดไป คุณทำโดยการจับคู่ rank และ สี ของไพ่ ตัวอย่างเช่น 4 โพธิ์ดำจับคู่กับ 4 ดอกจิก เป็นสีดำทั้งคู่ แจ๊คหัวใจจับคู่กับแจ๊คข้าวหลามตัดซึ่งเป็นสีแดงทั้งคู่
 เมื่อเริ่มเกม แหม่มโพธิ์ดำจะถูกเอาออกไปจาก deck ดังนั้น แหม่มดอกจิกไม่สามารถจับคู่ได้ ไพ่ที่ยังคงเหลือ 51 ใบ ถูกแจกให้กับผู้เล่น หลังจากแจกเสร็จ ผู้เล่นทั้งหมดจับคู่และทิ้งไพ่ที่เป็นไปได้มาก
 เมื่อไม่สามารถจับคู่ได้มากกว่านี้ การเริ่มเล่น ในรอบ แต่ละผู้เล่นหยิบไพ่มา 1 ใบ (ห้ามดู) จากเพื่อนที่ใกล้ที่สุดไปทางซ้ายผู้ซึ่งยังคงมีไพ่ ถ้าเลือกเจอไพ่ที่ตรงกับในมือของผู้เล่น ในที่สุดการจับคู่ที่เป็นไปได้จะเกิดขึ้น จะเหลือเพียงแหม่มดอกจิกในมือของผู้แพ้
 ในเกมจำลองคอมพิวเตอร์ของเรา คอมพิวเตอร์เป็นผู้เล่นทั้งหมด โชคไม่ดีที่ความแตกต่างน้อยมากที่จะแพ้ในเกมจริงๆ ในเกมจริงๆ ผู้เล่นมี Old Maid ต้องพยายามให้ผู้เล่นใกล้เคียงหยิบไพ่นั้นโดยแสดงมันให้มีความเด่นชัดน้อยกว่า หรือ บางทีพลาดที่จะแสดงมันออกมา หรือแม้พลาดและพลาดอีกที่จะแสดงไพ่ออกมา คอมพิวเตอร์จะหยิบอย่างง่ายๆ จากคนข้างๆ โดยการสุ่ม
16.6 OlaMaidHand class
16.6 OlaMaidHand class.
ผู้เล่น Ola Maid มีความต้องการความสามารถบ้าง ที่เหนือกว่าความสามารถทั่วไปของ Hand เราจะระบุ class ใหม่ OldMaidHand ซึ่งสืบทอดจาก Hand และให้ method ที่เพิ่มขึ้นนี้เรียกว่า removeMatches
 class OldMaidHand(Hand):
 def removeMatches(self):
 count = 0
 originalCards = self.cards[:]
 for card in originalCards:
 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
 เราเริ่มโดยการคัดลอก list ของไพ่ ดังนั้นเราสามารถตรวจการคัดลอกขณะที่กำลังนำไพ่ออกจากต้นฉบับ ตั้งแต่ self.cards ถูกแก้ไขใน loop เราไม่ต้องการใช้มันในการควบคุมสิ่งที่มาขวาง python ค่อนข้างจะสับสน ถ้ามันตรวจสอบ list ที่มีการเปลี่ยนแปลง
 สำหรับไพ่แต่ละใบในมือ เรามองว่าควรจะจับคู่กับอะไรและไปหามัน การจับคู่ไพ่มี rank ที่เหมือนกัน และ suit ที่สีเหมือนกัน expression 3 – card.suit เปลี่ยนโพธิ์ดำเป็นดอกจิก และ ข้าวหลามตัดเป็นหัวใจ คุณควรพอใจในการทำงานของ opposite operation ถ้าการจับคู่ไพ่อยู่ในมือ ไพ่ทั้งสองก็จะถูกทิ้งไป
 ตามตัวอย่างซึ่งพิสูจน์การใช้ removeMatches :
 »> game = CardGame
 »> hand = OldMaidHand("frank")
 »> game.deck.deal([hand], 13)
 »> print hand
 Hand frank contains
 Ace of Spades
 2 of Diamonds
 7 of Spades
 8 of Clubs
 6 of Hearts
 8 of Spades
 7 of Clubs
 Queen of Clubs
 7 of Diamonds
 5 of Clubs
 Jack of Diamonds
 10 of Diamonds
 10 of Hearts
 »> hand.removeMatches
 Hand frank: 7 of Spades matches 7 of Clubs
 Hand frank: 8 of Spades matches 8 of Clubs
 Hand frank: 10 of Diamonds matches 10 of Hearts
 »> print hand
 Hand frank contains
 Ace of Spades
 2 of Diamonds
 6 of Hearts
 Queen of Clubs
 7 of Diamonds
 5 of Clubs
 Jack of Diamonds
 สังเกตว่าไม่มี __init__ method สำหรับ OldMaidGame class เราสืบทอดมันมาจาก Hand
16.7 OldMaidGame class
16.7 OldMaidGame class.
ตอนนี้เราสามารถเปลี่ยนความตั้งใจของเราเพื่อเกมด้วยตัวมันเอง OldMaidGame คือ subclass ของ CardGame มี method ใหม่ เรียกว่า play ซึ่งรับค่า list ของผู้เล่นเป็นพารามิเตอร์
 ตั้งแต่ __init__ ถูกสืบทอดจาก CardGame โดย OldMaidGame ใหม่จะบรรจุการแจกไพ่สำรับใหม่
 class OldMaidGame(CardGame):
 def play(self, names):
 # remove Queen of Clubs
 self.deck.removeCard(Card(0,12))
 # make a hand for each player
 self.hands = []
 for name in names :
 self.hands.append(OldMaidHand(name))
 # deal the cards
 self.deck.deal(self.hands)
 print "---------- Cards have been dealt"
 self.printHands
 # remove initial matches
 matches = self.removeAllMatches
 print "---------- Matches discarded, play begins"
 self.printHands
 # play until all 50 cards are matched
 turn = 0
 numHands = len(self.hands)
 while matches < 25:
 matches = matches + self.playOneTurn(turn)
 turn = (turn + 1) % numHands
 print "---------- Game is Over"
 self.printHands
 บางขั้นตอนของเกมได้ถูกแยกไปใน methods ซึ่ง removeAllMatches ข้าม list ของ hands และสร้าง removeMatches ขึ้น
 class OldMaidGame(CardGame):
 def removeAllMatches(self):
 count = 0
 for hand in self.hands:
 count = count + hand.removeMatches
 return count
 count คือ ตัวเก็บสะสมหน่วยความจำตัวเลขที่จับคู่กันที่เพิ่มเข้าไปในแต่ละมือ และ return ค่าผลรวมออกมา
 เมื่อจำนวนตัวเลขที่จับคู่กันถึง 25 ไพ่ทั้ง 50 ใบ ได้ถูกทิ้งไปจากมือแล้ว ซึ่งหมายความว่ามีไพ่หนึ่งใบที่เหลือและเกมจะจบลง 
 ตัวแปร turn เก็บค่า track การเปลี่ยนแปลงของผู้เล่น มันเริ่มที่ 0 และเพิ่มขึ้นทีละ1 ในแต่ละครั้ง เมื่อมันถึง numHands ใน modulus มันจะกลับไปยังที่ 0
 Method playOne รับค่าพารามิเตอร์ที่ระบุถึง turn ที่มันเป็น
 Return value คือจำนวนการจับคู่ที่ทำระหว่าง turn นี้
 class OldMaidGame(CardGame):
 def playOneTurn(self, i):
 if self.hands[i].isEmpty:
 return 0
 neighbor = self.findNeighbor(i)
 pickedCard = self.hands[neighbor].popCard
 self.hands[i].addCard(pickedCard)
 print "Hand", self.hands[i].name, "picked", pickedCard
 count = self.hands[i].removeMatches
 self.hands[i].shuffle
 return count
 ถ้ามือของผู้เล่นว่าง ผู้เล่นจะถูกออกจากเกม ดังนั้นเขาหรือหล่อนไม่ได้ทำอะไร และ return ค่า 0
 อีกอย่างหนึ่ง หนึ่ง turn ประกอบด้วยการหาผู้เล่นคนแรกทางซ้ายซึ่งมีไพ่ การรับไพ่มาจากคนข้างๆ และตรวจดูว่าเข้าคู่หรือไม่ ก่อนจะเวียนไพ่ในมือที่ถูกสับไพ่แล้ว ดังนั้นการเลือกของผู้เล่นคนถัดไปคือการสุ่ม
 Method findNeighbor เริ่มมีผู้เล่นทันทีทางซ้ายและต่อเนื่องเป็นวงกลมจนกระทั้งหาผู้เล่นที่ยังคงมีไพ่
 class OldMaidGame(CardGame):
 def findNeighbor(self, i):
 numHands = len(self.hands)
 for next in range(1,numHands):
 neighbor = (i + next) % numHands
 if not self.hands[neighbor].isEmpty:
 return neighbor
 ถ้า findNeighbor เวียนเป็นวงกลมเรื่อยๆ โดยปราศจากการหาไพ่ มันจะ return ค่า None และเป็นเหตุให้เกิด error ขึ้นในโปรแกรม โชคดีที่เราสามารถตรวจสอบว่าสิ่งนั้นจะไม่เกิดขึ้น
 เราได้ละเลย printHands ไป คุณสามารถเขียน method นี้ด้วยตัวคุณเอง
 ตามผลลัพธ์ที่แสดงออกมาจากรูปแบบที่สั้นลงของเกมซึ่งมีเพียง 15 ใบด้านบนสุด ซึ่งแจกให้ผู้เล่น 3 คน มีสำรับเล็ก โดยหยุดเล่นหลังจากจับคู่ได้ 7 คู่ แทนที่จะเป็น 25 คู่
 »> import cards
 »> game = cards.OldMaidGame
 »> game.play(["Allen","Jeff","Chris"])
 ---------- Cards have been dealt
 Hand Allen contains
 King of Hearts
 Jack of Clubs
 Queen of Spades
 King of Spades
 10 of Diamonds
 Hand Jeff contains
 Queen of Hearts
 Jack of Spades
 Jack of Hearts
 King of Diamonds
 Queen of Diamonds
 Hand Chris contains
 Jack of Diamonds
 King of Clubs
 10 of Spades
 10 of Hearts
 10 of Clubs
 Hand Jeff: Queen of Hearts matches Queen of Diamonds
 Hand Chris: 10 of Spades matches 10 of Clubs
 ---------- Matches discarded, play begins
 Hand Allen contains
 King of Hearts
 Jack of Clubs
 Queen of Spades
 King of Spades
 10 of Diamonds
 Hand Jeff contains
 Jack of Spades
 Jack of Hearts
 King of Diamonds
 Hand Chris contains
 Jack of Diamonds
 King of Clubs
 10 of Hearts
 Hand Allen picked King of Diamonds
 Hand Allen: King of Hearts matches King of Diamonds
 Hand Jeff picked 10 of Hearts
 Hand Chris picked Jack of Clubs
 Hand Allen picked Jack of Hearts
 Hand Jeff picked Jack of Diamonds
 Hand Chris picked Queen of Spades
 Hand Allen picked Jack of Diamonds
 Hand Allen: Jack of Hearts matches Jack of Diamonds
 Hand Jeff picked King of Clubs
 Hand Chris picked King of Spades
 Hand Allen picked 10 of Hearts
 Hand Allen: 10 of Diamonds matches 10 of Hearts
 Hand Jeff picked Queen of Spades
 Hand Chris picked Jack of Spades
 Hand Chris: Jack of Clubs matches Jack of Spades
 Hand Jeff picked King of Spades
 Hand Jeff: King of Clubs matches King of Spades
 ---------- Game is Over
 Hand Allen is empty
 Hand Jeff contains
 Queen of Spades
 Hand Chris is empty
 So Jeff loses.
16.8 Glossary
16.8 Glossary.
inheritance: The ability to define a new class that is a modified version of a previously defined class.
 parent class: The class from which a child class inherits.
 child class: A new class created by inheriting from an existing class; also called a “subclass.”