2021年11月27日

寫給 PHP 新手的物件導向教學(三):封裝,可見度與繼承

在上一回《寫給 PHP 新手的物件導向教學(二):建構子、方法及屬性》中,

我們說明了類別的建構子、方法跟屬性,這一回我們會來聊聊物件的封裝、可見度,以及一點點的繼承。

封裝 (encapsulation)

「封裝」是物件導向的其中一個重要概念,封裝的意思是,將類別內部實作的細節隱藏起來,不讓別人知道或使用。

為什麼要隱藏起來呢?我們試著來看現實中的例子,

比較消極的例子是,我賺了一些錢,但我不想跟別人講我是怎麼做到的,所以我把神奇的賺錢方法私藏起來。

而比較積極一點的解釋方式是,透過封裝,人們把複雜的細節隱藏起來了,剩下公開的部分是簡單好理解的東西,會比較好用。

以消費型 3C 產品為例,好的產品總是用起來簡單、好學習,它們只會留下對使用者來說必要的功能,使用者不必去理解太多背後的原理跟細節,舉例來說, Apple 的產品介面通常比較簡潔,對使用者來說比較易上手。

我們再回到物件導向的部分,利用封裝的概念,程式設計師可以自己決定是否將方法或屬性暴露給外部使用,可能是因為不想讓別人知道或使用,也可能是因為只想對外保留簡單的介面。

而要在程式語言實現封裝的概念,其中一種做法就是可見度。

 

可見度 (visibility):公開 (public) 及私有 (private)

可見度,是以類別為中心,決定類別外部是否可以使用類別自己的屬性或方法。

PHP 總共有三種可見度,publicprivateprotected

那我們先從 public 開始,相信有閱讀前一回的讀者,已經看過 public 了,顧名思義,它就是公開的。

而公開的意思是,對類別外部公開,類別以外的任何人都可以使用公開的屬性跟方法。

// 類別外部

class Student {

// 在 {} 間,就是類別內部

}

// 類別外部

因此,當一個屬性或方法是 public 的,類別外部就可以看的到它,可以存取它或使用它。

第二個可見度是 privateprivate 也是一個經常被使用的可見度,它代表是類別所私有的,對類別外是不公開的,在外部無法使用它也無法存取它。

要怎麼設定屬性或方法的可見度?只要在類別或屬性宣告的前方加上想要的可見度就可以囉。我們接著來看一點範例,可以更清楚所謂的「公開」跟「私有」。

class Student { 
    public $name;
    private $weight;
    
    public function __construct($name, $weight) {
		$this->name = $name;
		$this->weight = $weight;
    }
}

在範例中,我們的學生有兩個屬性,其中一個是public公開屬性:姓名 $name ,另外一個則是不想讓人知道的 private 私有屬性:體重 $weight

利用前一回提到的建構子,我們可以在創建新學生物件的時候,就把姓名跟體重帶入屬性中。

$student = new Student('Jack', '100 KG');

echo $student->name; 
// 輸出: Jack

echo $student->weight; 
// 跳出:  PHP Fatal error:  
// Uncaught Error: Cannot access private property Student::$weight

當我們試圖在類別外部呼叫使用公開屬性 $name 的時候,是沒問題的,但是當我們在類別外部,使用私有屬性 $weight 時,PHP 則會提示錯誤訊息「Cannot access private property」無法存取私有屬性。

$student = new Student('Jack', '100 KG');
$student->name = 'Tom';
// OK

$student->weight = '40 KG';
// 跳出: PHP Fatal error:  
// Uncaught Error: Cannot access private property Student::$weight

同樣的,我們可以變更公開屬性的值,但無法變更私有屬性的值。

 

繼承

繼承的概念來自現實社會。

在現實中,同個家族的人通常會長得很相似,但又會有一點不一樣。而在程式中,有時候一系列的類別,可能都長得差不多,但又有一些不同。

因此人們就想,我們可不可以把那些一樣的地方,另外抽取出來,整理成一個類別,其他類別只要從這個類別衍伸出來,並處理不一樣的地方就可以了?於是人們就借用了現實社會中的繼承的概念,來實現程式語言的繼承。

在 PHP 中,從一個 A 類別衍伸出另外一個 B 類別,就稱為繼承,被繼承的 A 類別通常稱為母類別,而繼承母類別的 B 類別,則稱為子類別。子類別可以使用母類別的公開屬性及方法,但無法使用母類別的私有屬性及方法。

以學生的例子來看,學生又可以分為,小學生,國中生,高中生。他們對人們來說,都是學生,可是就實際上而言,他們卻又有點不一樣。

要繼承一個類別,只要透過關鍵字 extends 即可,接著讓我們試著來延伸學生類別:

// 學生
class Student {
    public $name;
    private $weight;
    
    public function __construct($name, $weight) {
		$this->name = $name;
		$this->weight = $weight;
    }
}

// 小學生
class ElementaryStudent extends Student {
    public function getWeight() {
        return $this->weight;
    }
}

// 國中生
class JuniorHighStudent extends Student {
}
    
// 高中生
class SeniorHighStudent extends Student {
}

可以看到我們有一個基礎的學生類別 Student,以及另外三個繼承出來的類別,小學生 ElementaryStudent,國中生 JuniorHighStudent,高中生 SeniorHighStudent

我們來測試一下小學生類別,由於子類別繼承了母類別時,建構子也會一併繼承,所以小學生類別目前可以直接使用學生類別的建構子。

$elementaryStudent = new ElementaryStudent('Tommy', '35 KG');

echo $elementaryStudent->name; 
// 輸出: Tommy

echo $elementaryStudent->weight;
// PHP Warning:  Undefined property: ElementaryStudent::$weight

可以看到 $name學生類別是公開的屬性,所以小學生類別也擁有這個屬性,但 $weight學生類別私有的,並不會繼承給子類別,所以對子類別的 小學生類別來說,$weight 是不存在的(可以注意一下警告訊息的不同)。

我們再來驗證另外一件事情,那就是 ElementaryStudent 類別的 getWeight() 方法,雖然是在類別內部,也是沒辦法讀取到 $weight 哦。

$elementaryStudent = new ElementaryStudent('Tommy', '35 KG');
$elementaryStudent->getWeight();
// PHP Warning:  Undefined property: ElementaryStudent::$weight

關於繼承的部份我們先講到這邊,之後會繼續說明。

 

可見度 (visibility):受保護的 (protected)

最後我們再回到第三個也是最後一個可見度:受保護的 protected

protected 是一個特別的可見度,對類別外部來說,受保護的屬性跟方法是不可見的,如同 private ,但繼承的子類別卻可以使用這些母類別中被保護的屬性跟方法。

這就是為什麼我想要先提一下繼承,因為 protected 只有在繼承的情況下,才比較有意義。

大家可以試著把前面 Student 類別的 $weight 的可見度改成 protected,並試試看在子類別中使用,看看效果如何。

 

可見度整理:

 外部繼承的類別 (衍伸的類別)
public可見可見
protected不可見可見
private不可見不可見

 

目錄:寫給 PHP 新手的物件導向教學

下一回:寫給 PHP 新手的物件導向教學(四):this,self 與 static