一、面向對象的概念
1.1 什麼是面向對象(object oriented
)
世間萬物皆對象,抽象的也是對象,一切可見或不可見都是對象
1.2 對象的基本組成
對象包含兩個部分:
-
對象的組成元素
- 是對象的
數據模型
,用於描述對象的數據
又稱爲對象的屬性
,或者對象的成員變量
- 是對象的
-
對象的行爲
- 是對象的
行爲模型
,用於描述對象能夠做什麼事情
又被稱爲對象的方法
- 是對象的
1.3 對象特點
- 每一個對象都是獨一無二的
- 對象是一個特定的事物,他的職能是完成特定功能
- 對象是可以重複使用
1.4 面向對象簡介
- 面向對象編程
就是編程的時候數據結構(數據組織方式 )都通過對象的結構進行存儲,使用屬性
和方法
組織起來 - 爲什麼要使用面向對象編程?
對象的描述方式更加貼合真實世界,有利於對大型業務的理解
1.5 面向對象的實質
- 面向對象就是把生活中要解決的問題都用對象的方式進行存儲--把所有的數據用屬性、方法表現出來。
- 對象之間的互動是通過
方法
的調用完成互動
1.6 面向對象的基本思路
- 識別對象
任何實體都可以被識別爲一個對象 - 識別對象的屬性
對象裏面存儲的數據被識別爲屬性
對於不同的業務邏輯,關注的數據不同,對象裏面存儲的屬性也不同 - 識別對象的行爲
對象自己的屬性數據的改變
對象外部的交互
1.7 面向對象的基本原則
- 對象內部
高內聚
對象只負責一項特定的職能
(職能可大可小)
所有對象相關的內容都封裝到對象內部 - 對象外部
低耦合
外部世界可以看到對象的一些屬性(並非全部)
外部世界可以看到對象可以做某些事情(並非全部)
軟件設計儘可能的做到:高內聚,低耦合,模塊與模塊間應該是獨立的,沒有依賴關係
二、面向對象的基本實踐
2.1 類的概念
- 物以類聚,把具有相似特性的對象對壘到一個類中
- 類定義了這些相似對象擁有的相同的屬性和方法
- 類是相似對象的描述,成爲類的定義,是該類對象的藍圖或者原型
- 類的對象稱爲一個類的
實例
(Instance) - 類的屬性和方法統稱爲
類成員
2.2 類的實例化
- 類的實例化:通過類定義創建一個類的對象
- 類的定義屬性值都是空或默認值,而對象的屬性都有具體的值
2.3 類的定義
- 類的定義以關鍵字
class
開始,後面跟着這個類的名稱。類的命名通常每個單詞的第一個字母大寫,以中括號開始和結束 - 類的實例化爲對象時使用關鍵字
new
,new
之後緊跟類的名稱和一對括號 - 對象中得成員屬性和方法可以通過
->
符號來訪問
2.4 構造函數
- 默認構造函數在對象被實例化的時候自動調用
$this
是Php
裏面的僞變量
,表示對象本身。可以通過$this->
的方式訪問對象的屬性和方法- 每一次用
new
實例化對象的時候,都會用類名後面的參數列表調用構造函數 php
類函數的構造函數function __construct(){}
運行時自動調用
2.5 析構函數
function __destruct(){}
析構函數是根據後入先出的原則- 有兩種方式會被執行析構函數:對象被設置未
null
或者程序結束時會被自動調用析構函數,,所佔用的資源被系統回收 - 析構函數通常被用於清理程序使用的資源,比如釋放打開的文件等等
- 析構函數在該對象不會再被使用的情況下自動調用,
如果被複制了,而不是
&引用,就不會調用析構函數
2.6 對象&
引用的基本概念
$james1 = $james; //相當於複製出來多一個引用,兩者是獨立的兩個引用
$james2 = &$james; //相當於爲james取一個別名,兩者其實是一體的,只是有兩個名字
特別注意:
PHP 永遠會將對象按引用傳遞
(ArrayObject
是一個SPL
對象
,它完全模仿數組的用法,但是卻是以對象來工作)$arr = array(); $arr2 = $arr;
$arr2
是$arr
數組的一份拷貝
,它們之間互不影響,是獨立的兩個數組&
對象(數組)都是相當於起別名
深入理解PHP引用:常見錯誤 #3:關於通過引用返回與通過值返回的困惑
三、面向對象的高級實戰
3.1 對象的繼承
父類:擁有部分相同的屬性和方法
繼承的好處
- 父類裏面定義的類成員可以不用在子類中重複定義,節約了編程的時間和代價
- 同一個父類的子類擁有相同的父類定義的類成員,因此外部代碼調用他們的時候可以一視同仁
-
子類可以修改和調用父類定義的類成員
- 我們稱爲
重寫
(Overwrite
) - 一旦子類修改了,就按照子類修改之後的功能執行
- 我們稱爲
子類:
- 子類可以通過
$this
訪問父類的屬性 - 子類的對象可以直接調用父類的方法和屬性
PHP
的單繼承特性
:類不允許同時繼承多個父類(extends
後面只能跟一個父類名稱)
3.2 訪問控制
面向對象的三種訪問權限:
-
public
是公有的類成員,可以在任何地方被訪問- 可以被類以及子類或者對象都可以訪問
-
protected
受保護的類成員,可以被其自身以及繼承的子類訪問- 可以被子類繼承,但是不能被對象訪問,只能通過封裝的方式讓對象訪問
-
private
私有的類成員,只能被自身訪問- 不能被子類繼承,也不能被對象訪問,只能在自身通過封裝讓外界訪問(例如在類裏面定義一個公開方法來調用私有屬性)
3.3 Static(靜態)關鍵字
靜態成員:定義時在訪問控制關鍵字後添加static
關鍵字即可(訪問控制關鍵字:public. protected. private
)
- 靜態屬性用於保存類的公有數據,可以在不同對象間共享
- 靜態方法裏面只能訪問靜態屬性
- 靜態成員不需要實例化對象就可以訪問
- 類的內部可以通過
self::
或static::
關鍵字訪問自身靜態成員,self::$
屬性 self::方法() - 通過
parent::
關鍵字訪問父類的靜態成員,也可以通過子類::父類靜態成員 - 通過
類名::
的方式在類的外部訪問靜態成員
3.4 重寫和Final關鍵字
- 子類中編寫跟父類完全一致的方法可以完成對父類方法的重寫,方法參數最好有默認參數
- 對於不想被任何類繼承的類可以在
class
之前添加final
關鍵字 - 對於不想被子類重寫(
overwrite
, 修改)的方法,可以在方法定義前面添加final
關鍵字
3.5 數據訪問
parent
關鍵字可以可用於調用父類中被子類重寫了的方法self
關鍵字可以用於訪問類自身的成員方法
,靜態成員和類常量;不能用於訪問類自身的屬性!!!使用常量的時候不需要在常量const
名稱前面添加$
符號static::
關鍵字用於訪問類自身定義的靜態成員,訪問靜態屬性時需要在屬性前面添加$
符號。- 常量屬性
const
不能使用對象訪問,僅能使用類訪問,在類本體內可以使用“self::常量名
”,在類本體外可以使用“類名::常量名
”
3.6 對象接口
接口就是把不同類的共同行爲
進行定義,然後在不同的類裏面實現不同的功能
interface
定義接口,implements
用於表示類實現某個接口- 接口裏面的方法沒有具體的實現,無
{}
- 實現了某個接口的類必須提供接口中定義的方法的具體實現
- 不能實例化接口,但是能夠判斷某個對象是否實現了某個接口。
instanceof
關鍵字判斷某個對象是否實現了某個接口$object instanceof interface
- 接口可以繼承接口(
interface extends interface
) - 接口中定義的所有方法都
必須是公有
,這是接口的特性
3.7 多態
因爲接口的方法實現可以有很多,所以對於接口裏面定義的方法的具體實現是多種多樣的,這種特性我們稱爲多態
不需要知道對象屬於哪個類,只要判斷該對象的類是否實現接口,就能實現調用,相同代碼實現不同結果
形象點說就是同一個接口,不同的對象實現,得出的結果不一樣就是多態,如傳入的是人類對象,得到的是人類吃蘋果,傳入的是猴子對象,得到的就是猴子吃香蕉。相同的一行代碼,對於傳入不同的接口的實現的對象的時候,表現是不同的。
/**
* 多態
* 1. 只要某個對象實現了接口(instanceof),就可以直接在對象上調用接口的方法
*/
interface ICanEat {
public function eat($food);
}
// Human類實現了ICanEat接口
class Human implements ICanEat {
// 跟Animal類的實現是不同的
public function eat($food){
echo "Human eating " . $food . "\n";
}
}
// Animal類實現了ICanEat接口
class Animal implements ICanEat {
public function eat($food){
echo "Animal eating " . $food . "\n";
}
}
function eat($obj){
if($obj instanceof ICanEat){
$obj->eat("FOOD"); // 不需要知道到底是Human還是Animal,直接吃就行了
}else{
echo "Can't eat!\n";
}
}
$man = new Human();
$monkey = new Animal();
// 同樣的代碼,傳入接口的不同實現類的時候,表現不同。這就是爲什麼成爲多態的原因。
eat($man);
eat($monkey);
3.8 抽象類
接口裏面的方法都是沒有實現的,而類裏面的方法都是有實現的。
有沒有一種形態,允許類裏面一部分方法不實現呢?
-
當接口中的某些方法對於所有的實現類都是一樣的實現方法,只有部分方法需要用到多態的特性
- 如人和動物吃東西是不同的,但是呼吸是相同的,不需要爲人和動物分別實現呼吸的功能
abstract
關鍵字用於定義抽象類- 在抽象方法前面添加
abstract
關鍵字可以標明這個方法是抽象方法不需要具體實現{}
- 抽象類中可以包含普通的方法,有方法的具體實現
- 繼承抽象類的關鍵字是
extends
- 繼承抽象類的子類需要實現抽象類中定義的抽象方法
- 抽象類不能被實例化,當子類繼承抽象類的時候,所有的抽象的方法都必須定義
/**
* 抽象類
* 1. 抽象類允許類裏面的部分方法暫時沒有具體實現,這些方法我們成爲抽象方法
* 2. 一旦類裏面有抽象方法,這個類就必須是抽象類
* 3. 抽象類跟接口一樣,不能直接實例化爲對象
*/
// 抽象類前面以abstract關鍵字開始
abstract class ACanEat {
// 沒有實現的方法需要設定爲抽象方法
// 抽象方法需要在子類中實現
abstract public function eat($food);
public function breath(){
echo "Breath use the air.\n";
}
}
// Human類實現了ICanEat接口
class Human extends ACanEat {
// 跟Animal類的實現是不同的
public function eat($food){
echo "Human eating " . $food . "\n";
}
}
// Animal類實現了ICanEat接口
class Animal extends ACanEat {
public function eat($food){
echo "Animal eating " . $food . "\n";
}
}
$man = new Human();
$man->eat("Apple");
$man->breath(); // 和Animal共用了抽象類ICanEat的breath方法
$monkey = new Animal();
$monkey->eat("Banana");
$monkey->breath();
四、PHP面向對象的特殊實踐
4.1 魔術方法之_toString()和invoke()
__toString()
當對象被當作String使用時,這個方法會被自動調用(需要在類中定義__tostring()
方法。調用 echo $object__invoke()
當對象被當作方法調用時,這個方法會被自動調用(需要在類中定義__invoke()
方法)。調用 $object($parameter)
/**
* 魔術方法1
* 1. 當對象被當做String使用時,__toString()會被自動調用
* 2. 當對象被當成方法調用時,__invoke()會被自動調用
*/
class MagicTest{
public function __toString(){
return "This is the Class MagicTest.\n";
}
public function __invoke($x){
echo "__invoke called with parameter " . $x . "\n";
}
}
$obj = new MagicTest();
echo $obj;
$obj(5);
4.2 魔術方法之__call()和__callStatic()
-
__call()
方法:當對象訪問不存在的方法名稱時,此方法自動調用。- 調用示例:
public function __call($name,$argument){}
- 注意:訪問控制關鍵字必須爲
public
;必須有兩個參數:對象訪問的方法名稱($name
)、方法包含的參數($argument
==> 自動轉換成數組)。
- 調用示例:
-
__callStatic()
方法:當對象訪問不存在的靜態方法名稱時,此方法自動調用。- 調用示例:
public static function __callStatic($name,$argument){}
- 注意:同
__call()
;此方法爲靜態方法(static)。
- 調用示例:
-
這兩種方法也被稱爲方法的重載(
overloading
)- 注意區分重寫(
overwrite
) - 通過這兩個方法,同一個方法的調用可以對應不同的方法的實現(同一個方法的靜態調用、動態調用對應不同的方法實現)
- 注意區分重寫(
/**
* 魔術方法之方法重載
* 1. 當對象訪問不存在的方法名稱時,__call()方法會被自動調用
* 2. 當對象訪問不存在的靜態方法名稱時,__callStatic()方法會被自動調用
*/
class MagicTest{
/**
* 自動將參數轉換成數組
* array (size=2)
* 0 => string 'para1' (length=5)
* 1 => string 'para2' (length=5)
* @param $name
* @param $arguments
*/
public function __call($name, $arguments){
var_dump($arguments);
echo "Calling " . $name . " with parameters: " . implode(', ', $arguments) . "\n";
}
public static function __callStatic($name, $arguments){
echo "Static calling " . $name . " with parameters: " . implode(', ', $arguments) . "\n";
}
}
$obj = new MagicTest();
$obj->runTest("para1", "para2");
MagicTest::runTest("para3","para4");
4.3 魔術方法之__get()、__set()、__isset()和__unset()
- 在給不可訪問
屬性
賦值時,__set()會被調用 定義function __set($name,$value) 讀取
不可訪問屬性的值時,__get()會被調用 定義function __get($name)- 當對不可訪問屬性調用
isset()
或empty()
時,__isset()
會被調用 - 當對不可訪問的屬性調用
unset()
時,__unset()
會被調用
這幾個方法也被成爲屬性重載
的魔術方法
所謂不可訪問屬性,實際上就是在調用某個屬性時發現這個屬性沒有被定義,這時候不同的操作會觸發不同的魔術方法
4.4 魔術方法之__clone()
$obj1 = $ojb; //不能實現對象複製,兩個對象變量指向同一對象
$obj1 = clone $obj; //實現對象複製,變成值相同的兩個對象
調用clone
時會自動調用__clone()
方法
$james = new NbaPlayer(); //$ja0 對應內存地址(假設爲 address0 )中存儲的是 james對象的標識符
$james2 = clone $james; //當希望生成一個真正獨立存儲的 NbaPlayer() 對象,但新對象的所有數據都和 $james 對象中的相同時,使用關鍵字clone
當在class NbaPlayer()
中定義了 __clone()
方法 後,使用clone
關鍵字時,系統將調用用戶定義的__clone()
方法 (此時可以對clone
後生成的新對象的屬性進行修改)
完!