官术网_书友最值得收藏!

2.3 面向?qū)ο缶幊?/h2>

2.2節(jié)中實現(xiàn)井字棋游戲的代碼體現(xiàn)了一種面向過程的編程設(shè)計思路。例如,從接收玩家輸入到判斷勝負,再到顯示新的棋盤,將整體游戲邏輯根據(jù)相應(yīng)過程拆開,然后分別用函數(shù)實現(xiàn)每個過程的邏輯。當程序功能非常簡單時,基于過程的設(shè)計思路沒什么問題。但當程序功能越來越復雜時,我們需要一種更靈活的設(shè)計思路,也就是面向?qū)ο缶幊蹋溆⑽拿麨镺bject-Oriented Programming,通??s寫為OOP。

面向?qū)ο缶幊痰闹饕^點是不應(yīng)該將程序拆分為若干過程,而應(yīng)該將其拆分為自然對象的模型。面向?qū)ο缶幊躺婕皫讉€關(guān)鍵概念,包括類、對象、組件、屬性和行為。我們分別來解釋這些關(guān)鍵概念。類可以看作一張藍圖或圖紙,代表了抽象概念,而對象是具體的事物。類和對象的關(guān)系是抽象和具體的關(guān)系,就好比漢字“馬”所代表的抽象概念,和一匹正在草原上奔跑的駿馬所代表的具體事物的關(guān)系。戰(zhàn)國時期所說的“白馬非馬”的故事,就包含抽象和具體的關(guān)系。

一個復雜的類可能是由多個組件構(gòu)成的。例如,汽車是由發(fā)動機、車身、底盤等組件構(gòu)成的,而這些組件本身也是類,如發(fā)動機類、車身類或底盤類。類是有屬性的,屬性是類的數(shù)據(jù)特征,例如馬是有顏色的,顏色是馬這個類的屬性,如果具體到作為對象的一匹馬,則有的馬是白色的,有的馬是黑色的。不同的具體的馬作為對象,可以有不同的屬性值。

類也是有行為的。行為定義了類可以做什么事(具有什么功能),例如馬可以奔跑,這是馬的一種功能。

面向?qū)ο缶幊痰脑O(shè)計思路體現(xiàn)了一種模塊化建造、分而治之的思想,就像造汽車,工廠根據(jù)不同部件的關(guān)聯(lián)程度將汽車分成幾個模塊,分發(fā)到不同的車間來建造。例如發(fā)動機車間專門造發(fā)動機,車身車間專門造車身,底盤車間專門造底盤,總裝車間根據(jù)接口將它們組裝起來,各車間只需專注自己的模塊的建造,互不干擾。而且在研發(fā)新款車型時,可能只需要更改底盤,而老款的發(fā)動機仍可以使用。面向?qū)ο缶幊痰膬?yōu)點歸納如下。

分別用類來封裝各自的數(shù)據(jù)和函數(shù),代碼相對獨立,更容易修改和管理。

讓設(shè)計思路更清晰,編程更高效,代碼更容易理解且不容易出錯。

可以直接使用現(xiàn)成的類,代碼能更好地重用。

這里還是以2.2節(jié)的井字棋游戲為例進行說明。在面向?qū)ο缶幊痰脑O(shè)計思路下,游戲本身是一個類,某個特定的游戲是一個對象,它是這個類的具體實例。游戲類包括一個重要的組件,也就是棋盤類。棋盤類包含兩個屬性,也就是棋盤大小和棋盤空間本身。棋盤類也包括若干函數(shù),例如顯示棋盤信息函數(shù)、判斷勝負函數(shù)等。作為游戲類的組件,棋盤類還有一個屬性,即當前需要落子的玩家。另外游戲類也包括若干函數(shù),例如處理玩家輸入的函數(shù)等。

我們可以根據(jù)游戲邏輯,將游戲程序用類圖的形式進行重新設(shè)計。圖2-4所示為設(shè)計好的游戲類Game和棋盤類Board的類圖。類圖中顯示了各個類中包含的屬性和函數(shù)。它可以讓我們更清楚地檢查類的設(shè)計,以及類之間的關(guān)系。

圖2-4

下面我們根據(jù)面向?qū)ο缶幊痰脑O(shè)計思路來修改原有的代碼。首先定義棋盤類,也就是Board類。在初始化函數(shù)中定義兩個屬性,分別是棋盤大小屬性size,以及棋子信息屬性pieces。

class Board:
 
    def __init__(self,size):
        self.size=size
self.pieces = ['.'] * size * size

Board類有顯示棋盤的函數(shù)show。

    def show(self):
        print("\n")
        print("%s|%s|%s"%(self.pieces[0],self.pieces[1],self.pieces[2]))
        print("-+-+-")
        print("%s|%s|%s"%(self.pieces[3],self.pieces[4],self.pieces[5]))
        print("-+-+-")
print("%s|%s|%s"%(self.pieces[6],self.pieces[7],self.pieces[8]))

Board類也有用于判斷有沒有空白位置可以落子的hasMovesLeft函數(shù)。

    def hasMovesLeft(self):
return'.'inself.pieces

Board類還需要有一個用于判斷當前落子位置是否符合游戲規(guī)則的isMoveValid函數(shù),以及用于落子后修改棋子信息的setMove函數(shù)。因為用戶的輸入是二維的坐標數(shù)據(jù),所以需要建立一個輔助函數(shù)locToMove,負責將輸入從二維的棋盤坐標轉(zhuǎn)換為一維的列表索引。

    def locToMove(self,loc):
        return int(loc[1]+loc[0]*self.size)
    def isMoveValid(self,loc):
        move = self.locToMove(loc)
        if self.pieces[move]=='.':
            return True
        else:
            return False
    def setMove(self,loc,player):
            move = self.locToMove(loc)
self.pieces[move]=player

Board類當然也包括判斷棋局勝負的hasWon函數(shù)。這里的實現(xiàn)邏輯和2.2節(jié)中代碼顯示的一樣。

    def hasWon(self,player):
        winningSet = [player in range(self.size)]
        row1 = self.pieces[:3]
        row2 = self.pieces[3:6]
        row3 = self.pieces[6:]
        if winningSet in [row1,row2,row3]:
            return True
        col1=[self.pieces[0],self.pieces[3],self.pieces[6]]
        col2=[self.pieces[1],self.pieces[4],self.pieces[7]]
        col3=[self.pieces[2],self.pieces[5],self.pieces[8]]
        if winningSet in [col1,col2,col3]:
            return True
        diag1=[self.pieces[0],self.pieces[4],self.pieces[8]]
        diag2=[self.pieces[6],self.pieces[4],self.pieces[2]]
        if winningSet in [diag1,diag2]:
            return True
return False

然后在Board類基礎(chǔ)上構(gòu)造游戲類,即Game類。Game類的初始化函數(shù)中包括當前玩家屬性currentPlayer,以及前面定義好的Board類,將其實例化,把生成的棋盤對象作為Game類的一個組件。

class Game:
 
    def __init__(self,boardSize,startPlayer):
        self.currentPlayer = startPlayer
        self.board=Board(boardSize)
        print("井字棋游戲開始")
        print("規(guī)則:三子連成直線即勝利")
print("X先手,O后手")

Game類中包括輪換玩家的getNextPlayer函數(shù)。

    @staticmethod
    def getNextPlayer(currentPlayer):
    if currentPlayer=='X':
            return'O'
        else:
return'X'

Game類中還包括處理玩家輸入的getPlayerMove函數(shù)。

    def getPlayerMove(self):
        while(True):
            userMove=input(f'\n玩家{self.currentPlayer}輸入棋盤坐標(坐標取值0,1,2):X,Y?')
            userMoveLoc=[int(char) for char in userMove.split(',')]
            if self.board.isMoveValid(userMoveLoc):
                self.board.setMove(userMoveLoc,self.currentPlayer)
break

然后將完整的游戲邏輯整合到play函數(shù)中。

    def play(self):
        self.board.show()
        while self.board.hasMovesLeft():
            self.getPlayerMove()
            self.board.show()
            if self.board.hasWon(self.currentPlayer):
                print('\n玩家'+self.currentPlayer+'勝利!')
                break
self.currentPlayer=self.getNextPlayer(self.currentPlayer)

最終的main入口非常簡潔。我們只需要將Game類實例化,生成game對象,再調(diào)用其play函數(shù)即可。在代碼重構(gòu)過程中,我們將原本零散的代碼進行了劃分,按類進行重構(gòu),使其邏輯層次更為清晰、更容易理解。完整的代碼可以參見第2章的對應(yīng)代碼文件tic_human_class.py。

if __name__ == '__main__':
 
    game = Game(boardSize=3,startPlayer='X')
game.play()

主站蜘蛛池模板: 江安县| 峡江县| 广平县| 栖霞市| 怀安县| 犍为县| 陈巴尔虎旗| 花莲市| 抚松县| 元江| 南城县| 阿坝县| 阳山县| 梧州市| 盐亭县| 怀宁县| 阜南县| 辽阳县| 专栏| 宁海县| 花莲市| 曲阜市| 高邑县| 明星| 郑州市| 乌什县| 儋州市| 吉林省| 林州市| 水富县| 习水县| 永德县| 淄博市| 壤塘县| 金山区| 昆山市| 瑞金市| 卢湾区| 临高县| 将乐县| 江都市|