繼承和多型
類的組合與繼承
- 假設我們有兩個類,一個 person,另外一個是 family;在 family 類中我們建立 person 類中的物件,並且我們把這個物件視為 family 類的一個屬性,並呼叫它的方法處理問題,那麼這種複用方式也稱為組合。
- 類與類之間還有一種父與子的關係,子類可以繼承父類的屬性和方法,我們稱之為繼承。在繼承裡,子類擁有父類的屬性和方法,同時子類也有自己的屬性和方法。
<?php class person{ public $name = 'Tom'; public $gender; static $money = 10000; public function __construct(){ echo '這裡是父類',PHP_EOL; } public function say(){ echo $this->name,"\t is ",$this->gender,"\r\n"; } } class family extends person{ public $name; public $gender; public $age; static $money = 1000; public function __construct(){ parent::__construct(); echo '這裡是子類',PHP_EOL; } public function say(){ parent::say(); echo $this->name,"\t is \t",$this->gender,",and is \t ",$this->age,PHP_EOL; } public function cry(){ echo parent::$money,PHP_EOL; echo '% >_< %',PHP_EOL; echo self::$money,PHP_EOL; echo "(*^_^*)"; } } $poor = new family(); $poor->name = 'Lee'; $poor->gender = 'female'; $poor->age = 25; $poor->say(); $poor->cry();
返回結果
這裡是父類 這裡是子類 Lee is female Lee is female,and is 25 10000 % >_< % 1000 (*^_^*)%
- 組合和繼承都是提高程式碼可重用行的手段。在設計物件模型時,可以按照語義識別類之間的組合關係和繼承關係。
例如:
- 繼承是一種 “是,像” 的關係,組合則是一種 “需要” 的關係 【父親和兒子應該是繼承關係,父親和家庭應該是組合關係 】
- 組合偏重與和區域性的關係,繼承偏重與父與子的關係。
- 從方法複用的角度考慮,如果兩個類具有很多相同的程式碼和方法,我們就可以從這兩個類中抽象出一個父類,提供公共方法,然後兩個類作為子類,提供個性方法,這時繼承更好。
- 組合的限制很少,組合之間的類可以關係很小 (體現為複用程式碼),設定沒有關係。
程式設計中
- 繼承和組合的取捨往往都不是這麼直接明瞭,很難說出二者是 “像” 的關係還是需要的關係。甚至說把它拿到現實世界中建模,更加無法決定是繼承還是組合的關係了。這時,它該如何辦,有什麼標準?這個標準就是:低耦合
低耦合
- 耦合是一個軟體結構內不同模組之間互聯程度的度量,也就是不同模組之間的依賴關係。
- 低耦合是指模組和模組之間,儘可能地使模組間獨立存在;模組與模組之間的介面儘量少而簡單。現代的物件導向的思想不強調為真實世界建模,變得更加理性化一些,把目標放在解耦上。
解耦
- 目的是為了解除模組與模組之間的依賴。
- 繼承和組合二者在語義上難以區分,但是我們更傾向於使用組合。
- 繼承存在的問題:
- 繼承破壞封裝性;(鳥類為父類,而鴨子和鴕鳥作為子類,它們卻擁有飛翔的方法)
- 繼承是緊耦合的,使得子類和父類捆綁在一起。組合僅是通過唯一介面和外部進行通訊,耦合度低於繼承。
- 繼承擴充套件複雜,隨著繼承層數的增加和子類的增加,將涉及大量方法重寫。使用組合,可以根據型別約束,實現動態組合,減少程式碼。
- 不恰當的使用繼承可能違反現實世界中的邏輯;
組合
<?php class car{ public function addoil(){ echo "Add oil \r\n"; } } class bmw extends car{ } class benz{ public $car; public function __construct(){ $this->car = new car(); } public function addoil(){ $this->car->addoil(); } } $bmw = new bmw(); $bmw->addoil(); $benz = new benz(); $benz->addoil();
- 在建立組合物件時,組合需要一一建立區域性物件,這一點程度上增加了一些程式碼,而繼承不需要這一步,繼承擁有父類的方法,可以直接使用
如何使用繼承:
- 精心設計專門用於被繼承的類,繼承樹的抽象層應該比較穩定,一般不多於三層;
- 對於不是專門用於被繼承的類,禁止其被繼承,也就是使用 final 修飾符。使用 final 修飾符即可防止重要方法被非法覆寫,又能給編輯器尋找優化的機會;
- 優先考了用組合關係提高程式碼的可重用性;
- 子類是一直特殊的型別,而不只是父類的一個角色;
- 子類擴充套件,而不是覆蓋或者使父類的功能失效;
- 底層程式碼多用組合,頂層 / 業務層程式碼多用繼承。底層用組合可以提供效率,避免物件臃腫。頂層程式碼用繼承可以提高靈活性,讓業務使用更方便。
既要組合的靈活,又要繼承的簡潔
- 多重繼承,一個類可以同時繼承多個父類,組合兩個父類的功能;缺點:多重繼承過於靈活,並且會帶來 “零星問題”,故為其使用帶來了不少困難,模型變得複雜起來。
- traits php5.4 引入的新的語法結構,可以方便我們實現物件的擴充套件,是除 extend,implements 外的另外一種擴充套件物件的方式,traits 即可以使單繼承模式的語言獲得多重繼承的靈活,又可以避免多重繼承帶來的種種問題
traits 的用法
- 通過在類中使用 use 關鍵字宣告要組合的 Trait 名稱,而具體某個 Trait 的宣告使用 trait 關鍵詞,Trait 不能直接例項化
<?php trait Drive { public $carName = 'BMW'; public function driving() { echo "driving {$this->carName}\n"; } } class Person { public function age() { echo "i am 18 years old\n"; } } class Student extends Person { use Drive; public function study() { echo "Learn to drive \n"; } } $student = new Student(); $student->study(); $student->age(); $student->driving();
Learn to drive i am 18 years old driving BMW
- Student 類通過繼承 Person,有了 age 方法,通過組合 Drive,有了 driving 方法和屬性 carName。
如果 Trait、基類和本類中都存在某個同名的屬性或者方法,最終會保留哪一個呢
<?php trait Drive { public function hello() { echo "hello 周伯通\n"; } public function driving() { echo "周伯通不會開車\n"; } } class Person { public function hello() { echo "hello 大家好\n"; } public function driving() { echo "大家都會開車\n"; } } class Student extends Person { use Drive;//trait 的方法覆蓋了基類Person中的方法,所以Person中的hello 和driving被覆蓋 public function hello() { echo "hello 新學員\n";//當方法或屬性同名時,當前類中的方法會覆蓋 trait的 方法,所以此處hello會覆蓋trait中的hello } } $student = new Student(); $student->hello(); $student->driving();
hello 新學員
周伯通不會開車
- 當方法或屬性同名時,當前類中的方法會覆蓋 trait 的 方法,而 trait 的方法又覆蓋了基類中的方法。
如果要組合多個 Trait,通過逗號分隔 Trait 名稱:
use Trait1, Trait2;
<?php trait Hello { public function sayHello() { echo "Hello 我是周伯通\n"; } } trait World { use Hello; public function sayWorld() { echo "hello world\n"; } abstract public function getWorld(); public function inc() { static $c = 0; $c = $c + 1; echo "$c\n"; } public static function doSomething() { echo "Doing something\n"; } } class HelloWorld { use World; public function getWorld() { return 'do you get World ?'; } } $Obj = new HelloWorld(); $Obj->sayHello(); $Obj->sayWorld(); echo $Obj->getWorld() . "\n"; HelloWorld::doSomething(); $Obj->inc(); $Obj->inc();
Hello 我是周伯通 hello world do you get World ? Doing something 1 2
語言中得多型
- 多型準確的含義是:同一類的物件收到相同訊息時,會得到不同的結果。而這個訊息是不可預測的。多型:顧名思義,就是多種狀態,也就是多種結果
- 多型性是一種通過多種狀態或階段描述相同物件的程式設計方式。它真正的意義在於:實際開發中,只要關心一個介面或基類的程式設計,而不必關心一個物件所屬於的具體類
案例
- 通過判斷傳入的物件所屬的類不同來呼叫其同名方法來實現 ’ 多型 ’
<?php class employee{ protected function working(){ echo '本方法需要過載才能執行'; } } class teacher extends employee{ public function working(){ echo '教書'; } } class coder extends employee{ public function working(){ echo '敲程式碼'; } } function doprint($obj){ //get_class 獲取當前物件呼叫的類名 if(get_class($obj)=='employee'){ echo 'Error'; }else{ $obj->working(); } } doprint(new teacher()); doprint(new coder()); doprint(new employee());
- 通過介面實現多型
<?php interface employee{ public function working(); } class teacher implements employee{ public function working(){ echo '教書'; } } class coder implements employee{ public function working(){ echo '敲程式碼'; } } function doprint(employee $i){ $i->working(); } $a=new teacher; $b=new coder; doprint($a); doprint($b);
- 在這段程式碼中,doprint 函式的引數為一個介面型別的變數,符合 ’ 同一型別,不同結果 ’ 這一條件,具備多型的一般特徵。
總結
- 多型指同一類物件在執行時的具體化。
- php 語言是弱型別的,實現多型更簡單,更靈活。
- 型別轉換不是多型。
- php 中父類和子類看作 ’ 繼父 ’ 和’ 繼子 ’ 關係,他們存在繼承關係,但不存在血緣關係,因此子類無法向上轉型為父類,從而失去多型最典型的特徵。
- 多型的本質就是 if – else – ,只不過實現的層次不同。
更多學習內容可以訪問【對標大廠】精品PHP架構師教程目錄大全,只要你能看完保證薪資上升一個臺階(持續更新)
還有更多進階學習資料領取進階PHP月薪30k>>>架構師成長路線【視訊、面試文件免費獲取】