面向對象編程(Object Oriented Programming, OOP, 面向對象程序設計)是一種計算機編程架構,OOP的一條基本原則是計算機程序是由單個能夠起到子程序作用的單元或對象組合而成,OOP達到了軟件工程的三個目標:重用性、靈活性和擴展性。為了實現整體運算,每個對象都能夠接收信息、處理數據和向其它對象發送信息。面向對象一直是軟件開發領域內比較熱門的話題,首先,面向對象符合人類看待事物的一般規律。其次,采用面向對象方法可以使系統各部分各司其職、各盡所能。為編程人員敞開了一扇大門,使其編程的代碼更簡潔、更易於維護,並且具有更強的可重用性。有人說PHP不是一個真正的面向對象的語言,這是事實。PHP 是一個混合型語言,你可以使用OOP,也可以使用傳統的過程化編程。然而,對於大型項目,你可能需要在PHP 中使用純的OOP去聲明類,而且在你的項目裡只用對象和類。這個概念我先不多說了,因為有很多朋友遠離面向對象編程的主要原因就是一接觸面向對象概念的時候就理解不上去, 所以就不想去學下去了。等讀者看完整體內容後再去把概念搞明白吧。
類的概念:類是具有相同屬性和服務的一組對象的集合。它為屬於該類的所有對象提供了統一的抽象描述,其內部包括屬性和服務兩個主要部分。在面向對象的編程語言中,類是一個獨立的程序單位,它應該有一個類名並包括屬性說明和服務說明兩個主要部分。
對象的概念:對象是系統中用來描述客觀事物的一個實體,它是構成系統的一個基本單位。一個對象由一組屬性和對這組屬性進行操作的一組服務組成。從更抽象的角度來說,對象是問題域或實現域中某些事物的一個抽象,它反映該事物在系統中需要保存的信息和發揮的作用;它是一組屬性和有權對這些屬性進行操作的一組服務的封裝體。客觀世界是由對象和對象之間的聯系組成的。
類與對象的關系就如模具和鑄件的關系,類的實例化結果就是對象,而對一類對象的抽象就是類。類描述了一組有相同特性(屬性)和相同行為(方法)的對象。
上面大概就是它們的定義吧,也許你是剛接觸面向對象的朋友, 不要被概念的東西搞暈了,給你舉個例子吧,如果你去中關村想買幾台組裝的PC機,到了那裡你第一步要干什麼,是不是裝機的工程師和你坐在一起,按你提供的信息和你一起完成一個裝機的配置單呀,這個配置單就可以想象成是類,它就是一張紙,但是它上面記錄了你要買的PC機的信息,如果用這個配置單買10台機器,那麼這10台機子,都是按這個配置單組成的,所以說這10台機子是一個類型的,也可以說是一類的。那麼什麼是對象呢,類的實例化結果就是對象,用這個配置單配置出來(實例化出來)的機子就是對象,是我們可以操作的實體,10台機子,10個對象。每台機子都是獨立的,只能說明他們是同一類的,對其中一個機做任何動作都不會影響其它9台機器,但是我對類修改,也就是在這個配置單上加一個或少一個配件,那麼裝出來的9個機子都改變了,這是類和對象的關系(類的實例化結果就是對象)。
就不說他的概念,如果你想建立一個電腦教室,首先要有一個房間, 房間裡面要有N台電腦,有N張桌子, N把椅子, 白板, 投影機等等,這些是什麼,剛才咱們說了, 這就是對象,能看到的一個個的實體,可以說這個電腦教室的單位就是這一個個的實體對象, 它們共同組成了這個電腦教室,那麼我們是做程序,這和面向對象有什麼關系呢?開發一個系統程序和建一個電腦教室類似,你把每個獨立的功能模塊抽象成類,形成對象,由多個對象組成這個系統,這些對象之間都能夠接收信息、處理數據和向其它對象發送信息等等相互作用。就構成了面向對象的程序。
上面已經介紹過了,面向對象程序的單位就是對象,但對象又是通過類的實例化出來的,所以我們首先要做的就是如何來聲明類,做出來一個類很容易,只要掌握基本的程序語法定義規則就可以做的出來,那麼難點在那裡呢?一個項目要用到多少個類,用多少個對象,在那要定義類,定義一個什麼樣的類,這個類實例化出多少個對象,類裡面有多少個屬性,有多少個方法等等,這就需要讀者通過在實際的開發中就實際問題分析設計和總結了。
類的定義: class 類名{ }
使用一個關鍵字class和後面加上一個你想要的類名以及加上一對大括號, 這樣一個類的結構就定義出來了,只要在裡面寫代碼就可以了, 但是裡面寫什麼? 能寫什麼?怎樣寫才是一個完整的類呢?上面講過來,使用類是為了讓它實例出對象來給我們用,這就要知道你想要的是什麼樣的對象了,像上面我們講的一個裝機配置單上寫什麼,你裝出來的機子就有什麼。比如說,一個人就是一個對象,你怎麼把一個你看好的人推薦給你們領導呢?當然是越詳細越好了:
首先,你會介紹這個人姓名、性別、年齡、身高、體重、電話、家庭住址等等。
然後,你要介紹這個人能做什麼,可以開車,會說英語,可以使用電腦等等。
只要你介紹多一點,別人對這個人就多一點了解,這就是我們對一個人的描述, 現在我們總結一下,所有的對象我們用類去描述都是類似的,從上面人的描述可以看到, 做出一個類來,從定義的角度分兩部分,第一是從靜態上描述,第二是從動態上描述, 靜態上的描述就是我們所說的屬性,像上面我們看到的,人的姓名、性別、年齡、身高、體重、電話、家庭住址等等。動態上也就是人的這個對象的功能,比如這個人可以開車,會說英語,可以使用電腦等等,抽象成程序時,我們把動態的寫成函數或者說是方法,函數和方法是一樣的。所以,所有類都是從屬性和方法這兩方面去寫, 屬性又叫做這個類的成員屬性,方法叫做這個類的成員方法。
class 人{
成員屬性:姓名、性別、年齡、身高、體重、電話、家庭住址
成員方法:可以開車, 會說英語, 可以使用電腦
}
通過在類定義中使用關鍵字" var "來聲明變量,即創建了類的屬性,雖然在聲明成員屬性的時候可以給定初始值, 但是在聲明類的時候給成員屬性初使值是沒有必要的,比如說要是把人的姓名賦上“張三”,那麼用這個類實例出幾十個人,這幾十個人都叫張三了,所以沒有必要, 我們在實例出對象後給成員屬性初始值就可以了。如: var $somevar;
<?php class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //下面是人的成員方法 function say() //這個人可以說話的方法 { echo "這個人在說話"; } function run() //這個人可以走路的方法 { echo "這個人在走路"; } } ?>
上面就是一個類的聲明,從屬性和方法上聲明出來的一個類,但是成員屬性最好在聲明的時候不要給初始的值,因為我們做的人這個類是一個描述信息,將來用它實例化對象,比如實例化出來10個人對象,那麼這10個人, 每一個人的名子,性別, 年齡都是不一樣的,所以最好不要在這個地方給成員屬性賦初值,而是對每個對象分別賦值的。
用同樣的辦法可以做出你想要的類了, 只要你能用屬性和方法能描述出來的實體都可以定義成類,去實例化對象。
為了加強你對類的理解,我們再做一個類,做一個形狀的類,形狀的范圍廣了點, 我們就做個矩形吧,先分析一下,想一想從兩方面分析,矩形的屬性都有什麼?矩形的功能都有什麼?
class 矩形 { //矩形的屬性 矩形的長; 矩形的寬; //矩形的方法 矩形的周長; 矩形的面積; }
<?php class Rect { var $kuan; var $gao; function zhouChang() { 計算矩形的周長; } function mianJi() { 計算矩形的面積; } } ?>
如果用這個類來創建出多個矩形對象,每個矩形對象都有自己的長和寬, 都可以求出自己的周長和面積了。
我們上面說過面向對象程序的單位就是對象,但對象又是通過類的實例化出來的,既然我們類會聲明了,下一步就是實例化對象了。當定義好類後,我們使用new關鍵字來生成一個對象。
<?php class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //下面是人的成員方法 function say() //這個人可以說話的方法 { echo "這個人在說話"; } function run() //這個人可以走路的方法 { echo "這個人在走路"; } } $p1=new Person(); $p2=new Person(); $p3=new Person(); ?>
$p1=new Person();
這條代碼就是通過類產生實例對象的過程,$p1就是我們實例出來的對象名稱,同理,$p2, $p3也是我們實例出來的對象名稱,一個類可以實例出多個對象,每個對象都是獨立的,上面的代碼相當於實例出來3個人來,每個人之間是沒有聯系的,只能說明他們都是人類,每個人都有自己的姓名,性別和年齡的屬性,每個人都有說話和走路的方法,只要是類裡面體現出來的成員屬性和成員方法,實例化出來的對象裡面就包含了這些屬性和方法。
對像在PHP裡面和整型、浮點型一樣,也是一種數據類,都是存儲不同類型數據用的,在運行的時候都要加載到內存中去用, 那麼對象在內存裡面是怎麼體現的呢?內存從羅輯上說大體上是分為4段, 棧空間段、堆空間段、代碼段、 初始化靜態段,程序裡面不同的聲明放在不同的內存段裡面,棧空間段是存儲占用相同空間長度並且占用空間小的數據類型的地方,比如說整型1, 10, 100, 1000, 10000, 100000等等,在內存裡面占用空間是等長的,都是64位4個字節。 那麼數據長度不定長,而且占有空間很大的數據類型的數據放在那內存的那個段裡面呢?這樣的數據是放在堆內存裡面的。棧內存是可以直接存取的,而堆內存是不可以直接存取的內存。對於我們的對象來數就是一種大的數據類型而且是占用空間不定長的類型,所以說對象是放在堆裡面的,但對象名稱是放在棧裡面的,這樣通過對象名稱就可以使用對象了。
$p1=new Person();
對於這個條代碼, $p1是對象名稱在棧內存裡面,new Person()是真正的對象是在堆內存裡面的。
每個在堆裡面的實例對象是存儲屬性的,比如說,現在堆裡面的實例對象裡面都存有姓名、性別和年齡。每個屬性又都有一個地址。$p1=new Person();等號的右邊$p1是一個引用變量,通過賦值運算符“=”把對象的首地址賦給“$p1”這個引用變量,所以$p1是存儲對象首地址的變量,$p1放在棧內存裡邊,$p1相當於一個指針指向堆裡面的對象,所以我們可以通過$p1這個引用變量來操作對象,通常我們也稱對象引用為對象。
上面看到PHP對象中的成員有兩種一種是成員屬性,一種是成員方法。對象我們已經可以聲明了,$p1=new Person();怎麼去使用對象的成員呢?要想訪問對象中的成員就要使用一個特殊的操作符“->”來完成對象成員的訪問:
對象->屬性 $p1->name; $p2->age; $p3->sex;
對象->方法 $p1->say(); $p2->run();
<?php class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //下面是人的成員方法 function say() //這個人可以說話的方法 { echo "這個人在說話"; } function run() //這個人可以走路的方法 { echo "這個人在走路"; } } $p1=new Person(); //創建實例對象$p1 $p2=new Person(); //創建實例對象$p2 $p3=new Person(); //創建實例對象$p3 //下面三行是給$p1對象屬性賦值 $p1->name=”張三”; $p1->sex=”男”; $p1->age=20; //下面三行是訪問$p1對象的屬性 echo “p1對象的名子是:”.$p1->name.”
”; echo “p1對象的性別是:”.$p1->sex.”
”; echo “p1對象的年齡是:”.$p1->age.”
”; //下面兩行訪問$p1對象中的方法 $p1->say(); $p1->run(); //下面三行是給$p2對象屬性賦值 $p2->name=”李四”; $p2->sex=”女”; $p2->age=30; //下面三行是訪問$p2對象的屬性 echo “p2對象的名子是:”.$p2->name.”
”; echo “p2對象的性別是:”.$p2->sex.”
”; echo “p2對象的年齡是:”.$p2->age.”
”; //下面兩行訪問$p2對象中的方法 $p2->say(); $p2->run(); //下面三行是給$p3對象屬性賦值 $p3->name=”王五”; $p3->sex=”男”; $p3->age=40; //下面三行是訪問$p3對象的屬性 echo “p3對象的名子是:”.$p3->name.”
”; echo “p3對象的性別是:”.$p3->sex.”
”; echo “p3對象的年齡是:”.$p3->age.”
”; //下面兩行訪問$p3對象中的方法 $p3->say(); $p3->run(); ?>
從上例中可以看出只是對象裡面的成員就要使用對象->屬性、對象->方法形式訪問,再沒有第二種方法來訪問對象中的成員了。
現在我們知道了如何訪問對象中的成員,是通過“對象->成員”的方式訪問的,這是在對象的外部去訪問對象中成員的形式,那麼如果我想在對象的內部,讓對象裡的方法訪問本對象的屬性,或是對象中的方法去調用本對象的其它方法這時我們怎麼辦?因為對象裡面的所有的成員都要用對象來調用,包括對象的內部成員之間的調用,所以在PHP裡面給我提供了一個本對象的引用$this, 每個對象裡面都有一個對象的引用$this來代表這個對象,完成對象內部成員的調用, this的本意就是“這個”的意思, 上面的實例裡面,我們實例化三個實例對象$P1、 $P2、 $P3,這三個對象裡面各自存在一個$this分別代表對象$p1、$p2、$p3 。
通過上圖我們可以看到,$this就是對象內部代表這個對象的引用,在對象內部和調用本對象的成員和對象外部調用對象的成員所使用的方式是一樣的。
$this->屬性 $this->name; $this->age; $this->sex;
$this->方法 $this->say(); $this->run();
修改一下上面的實例,讓每個人都說出自己的名字,性別和年齡:
<?php class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //下面是人的成員方法 function say() //這個人可以說話的方法 { echo "我的名子叫:".$this->name." 性別:".$this->sex." 我的年齡是:".$this->age."
"; } function run() //這個人可以走路的方法 { echo "這個人在走路"; } } $p1=new Person(); //創建實例對象$p1 $p2=new Person(); //創建實例對象$p2 $p3=new Person(); //創建實例對象$p3 //下面三行是給$p1對象屬性賦值 $p1->name="張三"; $p1->sex="男"; $p1->age=20; //下面訪問$p1對象中的說話方法 $p1->say(); //下面三行是給$p2對象屬性賦值 $p2->name="李四"; $p2->sex="女"; $p2->age=30; //下面訪問$p2對象中的說話方法 $p2->say(); //下面三行是給$p3對象屬性賦值 $p3->name="王五"; $p3->sex="男"; $p3->age=40; //下面訪問$p3對象中的說話方法 $p3->say(); ?>
分析一下這個方法:
function say() //這個人可以說話的方法
{
echo "我的名子叫:".$this->name." 性別:".$this->sex." 我的年齡是:".$this->age."
";
}
在$p1、$p2和$p3這三個對象中都有say()這個方法,$this分別代表這三個對象, 調用相應的屬性,打印出屬性的值,這就是在對象內部訪問對象屬性的方式, 如果相在say()這個方法裡調用run()這個方法也是可以的,在say()這個方法中使用$this->run()的方式來完成調用。
大多數類都有一種稱為構造函數的特殊方法。當創建一個對象時,它將自動調用構造函數,也就是使用new這個關鍵字來實例化對象的時候自動調用構造方法。
構造函數的聲明與其它操作的聲明一樣,只是其名稱必須是__construct( )。這是PHP5中的變化,以前的版本中,構造函數的名稱必須與類名相同,這種在PHP5中仍然可以用,但現在以經很少有人用了,這樣做的好處是可以使構造函數獨立於類名,當類名發生改變時不需要改相應的構造函數名稱了。為了向下兼容,如果一個類中沒有名為__construct( )的方法,PHP將搜索一個php4中的寫法,與類名相同名的構造方法。
在一個類中只能聲明一個構造方法,而是只有在每次創建對象的時候都會去調用一次構造方法,不能主動的調用這個方法,所以通常用它執行一些有用的初始化任務。比如對成屬性在創建對象的時候賦初值。
<? //創建一個人類 class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //定義一個構造方法參數為姓名$name、性別$sex和年齡$age function __construct($name, $sex, $age) { //通過構造方法傳進來的$name給成員屬性$this->name賦初使值 $this->name=$name; //通過構造方法傳進來的$sex給成員屬性$this->sex賦初使值 $this->sex=$sex; //通過構造方法傳進來的$age給成員屬性$this->age賦初使值 $this->age=$age; } //這個人的說話方法 function say() { echo "我的名子叫:".$this->name." 性別:".$this->sex." 我的年齡是:".$this->age."
"; } } //通過構造方法創建3個對象$p1、p2、$p3,分別傳入三個不同的實參為姓名、性別和年齡 $p1=new Person(“張三”,”男”, 20); $p2=new Person(“李四”,”女”, 30); $p3=new Person(“王五”,”男”, 40); //下面訪問$p1對象中的說話方法 $p1->say(); //下面訪問$p2對象中的說話方法 $p2->say(); //下面訪問$p3對象中的說話方法 $p3->say(); ?>
與構造函數相對的就是析構函數。析構函數是PHP5新添加的內容,在PHP4中沒有析構函數。析構函數允許在銷毀一個類之前執行的一些操作或完成一些功能,比如說關閉文件,釋放結果集等,析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行,也就是對象在內存中被銷毀前調用析構函數。與構造函數的名稱類似,一個類的析構函數名稱必須是__destruct( )。析構函數不能帶有任何參數。
<? //創建一個人類 class Person { //下面是人的成員屬性 var $name; //人的名子 var $sex; //人的性別 var $age; //人的年齡 //定義一個構造方法參數為姓名$name、性別$sex和年齡$age function __construct($name, $sex, $age) { //通過構造方法傳進來的$name給成員屬性$this->name賦初使值 $this->name=$name; //通過構造方法傳進來的$sex給成員屬性$this->sex賦初使值 $this->sex=$sex; //通過構造方法傳進來的$age給成員屬性$this->age賦初使值 $this->age=$age; } //這個人的說話方法 function say() { echo "我的名子叫:".$this->name." 性別:".$this->sex." 我的年齡是:".$this->age."
"; } //這是一個析構函數,在對象銷毀前調用 function __destruct() { echo “再見”.$this->name.”
”; } //通過構造方法創建3個對象$p1、p2、$p3,分別傳入三個不同的實參為姓名、性別和年齡 $p1=new Person(“張三”,”男”, 20); $p2=new Person(“李四”,”女”, 30); $p3=new Person(“王五”,”男”, 40); //下面訪問$p1對象中的說話方法 $p1->say(); //下面訪問$p2對象中的說話方法 $p2->say(); //下面訪問$p3對象中的說話方法 $p3->say(); ?>
封裝性是面向對象編程中的三大特性之一,封裝性就是把對象的屬性和服務結合成一個獨立的相同單位,並盡可能隱蔽對象的內部細節,包含兩個含義:1.把對象的全部屬性和全部服務結合在一起,形成一個不可分割的獨立單位(即對象)。2.信息隱蔽,即盡可能隱蔽對象的內部細節,對外形成一個邊界〔或者說形成一道屏障〕,只保留有限的對外接口使之與外部發生聯系。
封裝的原則在軟件上的反映是:要求使對象以外的部分不能隨意存取對象的內部數據(屬性),從而有效的避免了外部錯誤對它的"交叉感染",使軟件錯誤能夠局部化,大大減少查錯和排錯的難度。
用個實例來說明吧,假如某個人的對象中有年齡和工資等屬性,像這樣個人隱私的屬性是不想讓其它人隨意就能獲得到的,如果你不使用封裝,那麼別人想知道就能得到,但是如果你封裝上之後別人就沒有辦法獲得封裝的屬性,除非你自己把它說出去,否則別人沒有辦法得到。
再比如說,個人電腦都有一個密碼,不想讓其它人隨意的登陸,在你的電腦裡面拷貝和粘貼。還有就是像人這個對象,身高和年齡的屬性,只能是自己來增漲,不可以讓別人隨意的賦值等等。
使用private這個關鍵字來對屬性和方法進行封裝:
原來的成員: var $name; //聲明人的姓名 var $sex; //聲明人的性別 var $age; //聲明人的年齡 function run(){…….} 改成封裝的形式: private $name; //把人的姓名使用private關鍵字進行封裝 private $sex; //把人的性別使用private關鍵字進行封裝 private $age; //把人的年齡使用private關鍵字進行封裝 private function run(){……} //把人的走路方法使用private關鍵字進行封裝 注意:只要是成員屬性前面有其它的關鍵字就要去掉原有的關鍵字“var”。
通過private就可以把人的成員(成員屬性和成員方法)封裝上了。封裝上的成員就不能被類外面直接訪問了,只有對象內部自己可以訪問;
私有的成員是不能被外部訪問的, 因為私有成員只能在本對象內部自己訪問,比如,$p1這個對象自己想把他的私有屬性說出去,在say()這個方法裡面訪問了私有屬性,這樣是可以。(沒有加任何訪問控制,默認的是public的,任何地方都可以訪問)
因為構造方法是默認的公有方法(構造方法不要設置成私有的),所以在類的外面可以訪問到,這樣就可以使用構造方法創建對象, 另外構造方法也是類裡面的函數,所以可以用構造方法給私有的屬性賦初值。Say()的方法是默認公有的, 所以在外面也可以訪問的到, 說出他自己的私有屬性。
從上面的例子中我們可以看到,私有的成員只能在類的內部使用,不能被類外部直接來存取,但是在類的內部是有權限訪問的,所以有時候我們需要在類的外面給私有屬性賦值和讀取出來,也就是給類的外部提供一些可以存取的接口,上例中構造方法就是一種賦值的形式,但是構造方法只是在創建對象的時候賦值,如果我們已經有一個存在的對象了,想對這個存在的對象賦值,這個時候,如果你還使用構造方法傳值的形式傳值,那麼就創建了一個新的對象,並不是這個已存在的對象了。所以我們要對私有的屬性做一些可以被外部存取的接口,目的就是可以在對象存在的情況下,改變和存取屬性的值,但要注意,只有需要讓外部改變的屬性才這樣做,不想讓外面訪問的屬性是不做這樣的接口的,這樣就能達到封裝的目的,所有的功能都是對象自己來完成,給外面提供盡量少的操作。