在上一回《寫給 PHP 新手的物件導向教學(二):建構子、方法及屬性》中,
我們說明了類別的建構子、方法跟屬性,這一回我們會來聊聊物件的封裝、可見度,以及一點點的繼承。
封裝 (encapsulation)
「封裝」是物件導向的其中一個重要概念,封裝的意思是,將類別內部實作的細節隱藏起來,不讓別人知道或使用。
為什麼要隱藏起來呢?我們試著來看現實中的例子,
比較消極的例子是,我賺了一些錢,但我不想跟別人講我是怎麼做到的,所以我把神奇的賺錢方法私藏起來。
而比較積極一點的解釋方式是,透過封裝,人們把複雜的細節隱藏起來了,剩下公開的部分是簡單好理解的東西,會比較好用。
以消費型 3C 產品為例,好的產品總是用起來簡單、好學習,它們只會留下對使用者來說必要的功能,使用者不必去理解太多背後的原理跟細節,舉例來說, Apple 的產品介面通常比較簡潔,對使用者來說比較易上手。
我們再回到物件導向的部分,利用封裝的概念,程式設計師可以自己決定是否將方法或屬性暴露給外部使用,可能是因為不想讓別人知道或使用,也可能是因為只想對外保留簡單的介面。
而要在程式語言實現封裝的概念,其中一種做法就是可見度。
可見度 (visibility):公開 (public) 及私有 (private)
可見度,是以類別為中心,決定類別外部是否可以使用類別自己的屬性或方法。
PHP 總共有三種可見度,public
、private
及 protected
。
那我們先從 public
開始,相信有閱讀前一回的讀者,已經看過 public
了,顧名思義,它就是公開的。
而公開的意思是,對類別外部公開,類別以外的任何人都可以使用公開的屬性跟方法。
// 類別外部
class Student {
// 在 {} 間,就是類別內部
}
// 類別外部
因此,當一個屬性或方法是 public
的,類別外部就可以看的到它,可以存取它或使用它。
第二個可見度是 private
, private
也是一個經常被使用的可見度,它代表是類別所私有的,對類別外是不公開的,在外部無法使用它也無法存取它。
要怎麼設定屬性或方法的可見度?只要在類別或屬性宣告的前方加上想要的可見度就可以囉。我們接著來看一點範例,可以更清楚所謂的「公開」跟「私有」。
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 | 不可見 | 不可見 |