封裝

我們在談到函式的時候講過封裝,透過包裝若干運算便能創造出函式,然而物件也是封裝來的,物件不但封裝了若干函式(方法),還封裝了若干的資料(屬性),更重要的一點是,這些屬性通常是供給方法做運算的。我們把同屬一類或同屬一物的資料跟運算打包起來,讓我們能夠輕易且精準地完成任務。

我們考慮一個矩形的類別,要描述一個矩形,我們通常需要長 (length) 跟寬 (width) :

class Rectangle:

    def __init__(self, length, width):
        self.length = length
        self.width = width

    def perimeter(self):
        return 2 * (self.length + self.width)

    def area(self):
        return self.length * self.width

如此一來,要計算一個矩形的周長和面積,我們會這樣做:

rec = Rectangle(4,2)
print(rec.perimeter())
print(rec.area())

這個過程似乎沒有什麼大不了的,也許讀者會覺得一些獨立的 function 足矣:

def perimeter(length, width):
    return 2 * (length + width)

def area(length, width):
    return length * width

這兩者的差別在哪呢?我們發現後者在定義函式的時候,每個函式都必須要重新接收一次長跟寬這兩個參數。

這就是一個類別明顯的優勢,對於同一個矩形來說,計算週長和面積所用到的資料是一樣的,所以利用獨立函式來完成任務時,重複且多餘的參數實在令人心煩,類別與物件之所以可以避免,便在於我們將運算所需的資料 (長與寬) 也一起封裝進去了,減少了傳遞上的麻煩與失誤。

第二個優勢在於,如果使用獨立函式來完成任務,會有資料誤用的情形,怎麼說呢?

area(-1, 0)

上面的代碼我們傳進了負數與 0,這是完全不合法的長與寬,這實在是因為運算跟資料的分離,導致兩者可能無法配合,像此例就是個誤用的情形。

但使用類別可以有效地解決此情況,因為我們可以確保在 Rectangle 中,perimeterarea 運算所用的資料絕對是正確的長與寬,講的精確一點,如果一開始生成 Rectangle 的時候我們就保證了資料正確,往後類別中的方法調用資料時就一勞永逸地可以放心使用了,我們可以這樣做:

class Rectangle:

    def __init__(self, length, width):
        if length <= 0 or width <=0:
            raise ValueError('length and width should be positive!')
        self.length = length
        self.width = width

當我們檢查發現長寬非正值的時候,我們可以利用 raise 來主動引發錯誤 ValueError,並且附帶有錯誤訊息,這樣子的機制可以讓我們在最一開始便確認資料的正確性,如果無誤便可以將之與矩形的方法封裝在一起。

如果我們也想對獨立的函式做檢查,那可就多了許多重複而無謂的工作了,畢竟當函式種類繁多時,這樣的檢查並無效率。

封裝將資料及其相應的運算打包在一起,於是運算時不需要重複提供資料,也不怕會誤用資料。

results matching ""

    No results matching ""