Python

利用海龜式繪圖說明Python物件導向程式設計觀念

物件導向程式設計可以把資料和物件封裝在一起,進一步加強模組化的程度,而且包裝好的模組也可以成為一個可重用的單位,是現代程式語言非常重要的特性,而Python也是具備物件導向特性的程式語言,以下就以海龜式繪圖來說明如何運用物件導向程式設計的特性。

我們要定義的物件類別階層關係如下:

首先,是類別的定義,在這裡我們定義了一個共通的父類別Shape,類別中包括資料成員x, y, color,以及函式成員move(), set_color(), 以及draw():

class Shape():
    x = 0
    y = 0
    color = 'green'

    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color

    def move(self, dx, dy):
        self.x = self.x + dx
        self.y = self.y + dy

    def set_color(self, color):
        self.color = color

    def draw(self):
        pass

其中__init__()是建構子函式,它是在把類別建立成物件時會被呼叫用來進行物件初始化設定的函式,至於draw()函式則是以pass這個指令先把它帶過,因為後來的所有子類別都會重新設計一個自己的draw()函式以取代它。

在定義了Shape類別之後,假設我們要再定義一個用來繪製「點(Point)」的函式,就可以利用繼承的方式,承襲Shape原有的定義,只要再加上繪點的實作就可以了。類別Point的定義如下:

class Point(Shape):
    
    def __init__(self, x, y, color):
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.dot()
        tu.up()

由於點的繪製只需要座標和顏色,所需要的資料和父類別Shape一樣,因此在繼承之後就不用再定義自己的資料,只要建立一個「__init__」方法函式,並把所有接收到的資料全部利用「super().__init(x, y, color)」讓父類別自己去處理就可以了。

至於draw()函式,則是實際在指定的座標上利用指定的顏色繪製出「點」的的程式碼。它的名稱和父類別中的名稱一模一樣,當以此子類別建立物件時,呼叫draw()會自動執行子物件中的draw()而不是父類別的。特別要留意的是,父類別中的set_color()以及move()這兩個函式在子類別Point中並沒有實作,因此在子類別也就是Point的物件中如果呼叫了這兩個函式的話,系統還是會到父物件Shape中去執行它們。

除了Point之外,我們也可以建立一個畫圓的函式,同樣也是繼承自Shape,但因為畫圓還需要一個半徑的資料,因此在下方的Circle的類別定義中,可以看到另外一個叫做「r」的資料成員用來表示半徑,如下所示:

class Circle(Shape):
    
    r = 50
    def __init__(self, x, y, r, color):
        self.r = r
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.setheading(270)
        tu.forward(self.r)
        tu.setheading(0)
        tu.down()
        tu.pencolor(self.color)
        tu.circle(self.r)
        tu.up()

接著定義一個用來繪製線段的Line類別,它也是繼承自Shape類別,但是多了另外一個終點座標(ex, ey),定義如下:

class Line(Shape):
    
    ex = 100
    ey = 100
    def __init__(self, x, y, ex, ey, color):
        self.ex = ex
        self.ey = ey
        super().__init__(x, y, color)
    
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.goto(self.ex, self.ey)
        tu.up()

從上面的程式碼可以看出Line類別新增了ex以及ey這兩個資料變數,它的定義方式和Circle這個類別是類似。子類別也可以是別的類別的父類別,以下繪製矩形的類別Rect即為此例,它把Line這個類別視為父類別:

class Rect(Line):
    
    def __init__(self, x, y, ex, ey, color):
        super().__init__(x, y, ex, ey, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.pencolor(self.color)
        tu.down()
        tu.setheading(0)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.up()

從上面的例子中可以看出,Rect類別的資料和Line類別的資料個數及名稱是一樣的,但是對於ex和ey的解釋則不同,在Line中,ex和ey是線段另外一個端點的座標,而在Rect中,ex和ey則是欲繪製之矩形的寬度和高度,雖然如此,全部的內容也只有在draw()函式進行不同的處理,其它的函式內容則是不用變更的。
這幾個類別之間的父子關係如圖1-5-6所示,從此圖中即可看出,最上層的父類別是Shape,其中Point、Line、以及Circle分別繼承自Shape,在Line類別中多加了ex和ey這兩個資料,而Circle則只多加了1個用來表示半徑的r。此外,Rect繼承自Line,它同時擁有了Shape和Line中所有的內容,可以存取x, y, ex, ey, 以及color這幾個成員資料,但是它自行改寫了draw()函式,讓它可以繪製出矩形而不是線條。

在前面的部份我們分別定義了Point、Circle、Line、以及Rect這幾個子類別,在主程式中執行的方法如下:

import turtle
screen = turtle.Screen()
screen.setup(500,500)
tu = turtle.Turtle()
tu.up()
tu.hideturtle()

class Shape():
    x = 0
    y = 0
    color = 'green'
    
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
    
    def move(self, dx, dy):
        self.x = self.x + dx
        self.y = self.y + dy
        
    def set_color(self, color):
        self.color = color
        
    def draw(self):
        pass

class Point(Shape):
    
    def __init__(self, x, y, color):
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.dot()
        tu.up()

class Circle(Shape):
    
    r = 50
    def __init__(self, x, y, r, color):
        self.r = r
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.setheading(270)
        tu.forward(self.r)
        tu.setheading(0)
        tu.down()
        tu.pencolor(self.color)
        tu.circle(self.r)
        tu.up()
        
class Line(Shape):
    
    ex = 100
    ey = 100
    def __init__(self, x, y, ex, ey, color):
        self.ex = ex
        self.ey = ey
        super().__init__(x, y, color)
    
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.goto(self.ex, self.ey)
        tu.up()

class Rect(Line):
    
    def __init__(self, x, y, ex, ey, color):
        super().__init__(x, y, ex, ey, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.pencolor(self.color)
        tu.down()
        tu.setheading(0)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.up()

p = Point(120, 120, 'red')
p.draw()

c = Circle(0, 0, 50, 'blue')
c.draw()

l = Line(-200, 50, -50, -80, 'black')
l.draw()

r = Rect(100, -100, 120, 80, 'green')
r.draw()

screen.exitonclick()

以線條的建立為例,在程式中以「l = Line(-200, 50, -50, -80, ‘black’)」的型式來建立一個點的物件,其它的形狀也是使用類似的方式建立各自的物件。建立出來的物件均以變數的型式來作為存取的依據,因此在呼叫draw()函式要執行繪圖作業時,是以「l.draw()」的型式來進行。而且值得注意的是,當使用「l = Line(-200, 50, -50, -80, ‘black’)」建立l這個物件時,-200, 50, -50, -80, 以及’black’已經被存於記憶體中了,示意圖如下所示:


此時執行l.draw(),雖然表面上似乎沒有提供任何參數資料給draw()這個方法函式,但是在函式中draw()所使用的是Line類別中的ex, ey以及其父類別中的x, y, color這幾個變數的內容,這證明了在物件l建立之後,資料和方法函式已經確實地被封裝在一起了。執行結果如下所示:

在子類別中雖然沒有定義set_color()以及move()函式,但因為它是父類別的函式,在子類別中沒有定義的話,就會自動使用到父類別的函式,請看以下的例子:

import turtle
screen = turtle.Screen()
screen.setup(500,500)
tu = turtle.Turtle()
tu.up()
tu.hideturtle()

class Shape():
    x = 0
    y = 0
    color = 'green'
    
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.color = color
    
    def move(self, dx, dy):
        self.x = self.x + dx
        self.y = self.y + dy
        
    def set_color(self, color):
        self.color = color
        
    def draw(self):
        pass

class Point(Shape):
    
    def __init__(self, x, y, color):
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.dot()
        tu.up()

class Circle(Shape):
    
    r = 50
    def __init__(self, x, y, r, color):
        self.r = r
        super().__init__(x, y, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.setheading(270)
        tu.forward(self.r)
        tu.setheading(0)
        tu.down()
        tu.pencolor(self.color)
        tu.circle(self.r)
        tu.up()
        
class Line(Shape):
    
    ex = 100
    ey = 100
    def __init__(self, x, y, ex, ey, color):
        self.ex = ex
        self.ey = ey
        super().__init__(x, y, color)
    
    def draw(self):
        tu.goto(self.x, self.y)
        tu.down()
        tu.pencolor(self.color)
        tu.goto(self.ex, self.ey)
        tu.up()

class Rect(Line):
    
    def __init__(self, x, y, ex, ey, color):
        super().__init__(x, y, ex, ey, color)
        
    def draw(self):
        tu.goto(self.x, self.y)
        tu.pencolor(self.color)
        tu.down()
        tu.setheading(0)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.forward(self.ex)
        tu.right(90)
        tu.forward(self.ey)
        tu.right(90)
        tu.up()

rect = Rect(-200, -100, 50, 50, 'green')
rect.draw()
rect.move(60, 60)
rect.set_color('red')
rect.draw()
rect.move(60, 60)
rect.set_color('yellow')
rect.draw()
rect.move(60, 60)
rect.set_color('blue')
rect.draw()


screen.exitonclick()

在這個程式中,先利用「Rect(-200, -100, 50, 50, ‘green’)」建立一個rect物件,接著呼叫rect.draw()把這個矩形繪製上去。之後分別利用3次rect.move(60, 60)把這個矩形移到右上角,並透過rect.set_color(‘blue’)等指令設定不同的顏色,再分別把這3個矩形繪製上去。從這個例子可以看得出來,我們是針對同一個物件在進行操作,這個物件中的資料內容可以透過在父類別中所提供的方法函式進行修改。執行結果如下所示:

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s