PHP設計模式之原型模式示例詳解
前言
原型模式其實更形象的來說應該叫克隆模式。它主要的行為是對物件進行克隆,但是又把被克隆的物件稱之為最初的原型,於是,這個模式就這樣被命名了。說真的,從使用方式來看真的感覺叫克隆模式更貼切一些。
Gof類圖及解釋
GoF定義:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件
GoF類圖
程式碼實現
abstract class Prototype { public $v = 'clone' . PHP_EOL; public function __construct() { echo 'create' . PHP_EOL; } abstract public function __clone(); }
首先我們通過模擬的方式定義了一個原型,這裡主要是模擬了__clone()這個方法。其實這是PHP自帶的一個魔術方法,根本是不需要我們去進行定義的,只需要在原型類中進行實現就可以了。當外部使用clone關鍵字進行物件克隆時,直接就會進入這個魔術方法中。在這個魔術方法裡面我們可以對屬性進行處理,特別是針對引用屬性進行一些獨特的處理。在這個例子中,我們只使用了一個值型別的變數。無法體現出引用型別的問題,我們將在後面的例項中演示對引用型別變數的處理。
class ConcretePrototype1 extends Prototype { public function __clone() { } } class ConcretePrototype2 extends Prototype { public function __clone() { } }
模擬的具體實現的原型,其實就是主要去具體的實現__clone()方法。後面我們看具體的例子時再說明。
class Client { public function operation() { $p1 = new ConcretePrototype1(); $p2 = clone $p1; echo $p1->v; echo $p2->v; } } $c = new Client(); $c->operation();
客戶端使用clone來複制P1 可以看到p2也具有相同的$v屬性。
- 原型模式看似就是複製了一個相同的物件,但是請注意,複製的時候,__construct()方法並沒有被呼叫,也就是當你執行這段程式碼的時候,create只輸出了一次。這也就帶出了原型模式最大的一個特點——減少建立物件時的開銷。
- 基於上述特點,我們可以快速的複製大量相同的物件,比如要給一個數組中塞入大量相同的物件時。
- 複製出來的物件中如果都是值型別的屬性,我們可以任意修改,不會對原型產生影響。而如果有引用型別的變數,則需要在__clone()方法進行一些處理,否則修改了複製物件的引用變數中的內容,會對原型物件中的內容有影響。
我們的手機作業系統(也可以想象一下PC電腦的作業系統),都是怎樣安裝到裝置中呢?其實都是不停的複製拷貝最初的那一套系統。用微軟的例子非常好說明這個問題,當年微軟能夠成為一個帝國,其實也是因為他不停的將winodws作業系統拷貝複製到光碟中,然後賣給千家萬戶(當然,這裡沒中國什麼事兒)。而中國市場呢,大量的高手破解了windows之後也是由這一份檔案不停的複製拷貝才裝到了我們的電腦中。手機、智慧裝置等各類產品的作業系統、軟體都是如此。一次開發無限拷貝正是軟體行業暴利的原因。畢竟我們的系統也是由不少的工程師日以繼夜的996在Android原生系統的基礎上開發出來的,趕緊不斷的複製到即將出廠的手機上吧!!
完整程式碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/08.prototype/source/prototype.php
例項
同樣還是拿手機來說事兒,這次我們是根據不同的運營商需要去開發一批定製機,也就是套餐機。這批手機說實話都並沒有什麼不同,大部分都是相同的配置,但是運營商系統不同,而且偶爾有一些型號的CPU和記憶體也可能存在不同。這個時候,我們就可以用原型模式來進行快速的複製並且只修改一部分不相同的地方啦。
原型模式生產手機類圖
完整原始碼:https://github.com/zhangyue0503/designpatterns-php/blob/master/08.prototype/source/prototype-phone.php
<?php interface ServiceProvicer { public function getSystem(); } class ChinaMobile implements ServiceProvicer { public $system; public function getSystem(){ return "中國移動" . $this->system; } } class ChinaUnicom implements ServiceProvicer { public $system; public function getSystem(){ return "中國聯通" . $this->system; } } class Phone { public $service_province; public $cpu; public $rom; } class CMPhone extends Phone { function __clone() { // $this->service_province = new ChinaMobile(); } } class CUPhone extends Phone { function __clone() { $this->service_province = new ChinaUnicom(); } } $cmPhone = new CMPhone(); $cmPhone->cpu = "1.4G"; $cmPhone->rom = "64G"; $cmPhone->service_province = new ChinaMobile(); $cmPhone->service_province->system = 'TD-CDMA'; $cmPhone1 = clone $cmPhone; $cmPhone1->service_province->system = 'TD-CDMA1'; var_dump($cmPhone); var_dump($cmPhone1); echo $cmPhone->service_province->getSystem(); echo $cmPhone1->service_province->getSystem(); $cuPhone = new CUPhone(); $cuPhone->cpu = "1.4G"; $cuPhone->rom = "64G"; $cuPhone->service_province = new ChinaUnicom(); $cuPhone->service_province->system = 'WCDMA'; $cuPhone1 = clone $cuPhone; $cuPhone1->rom = "128G"; $cuPhone1->service_province->system = 'WCDMA1'; var_dump($cuPhone); var_dump($cuPhone1); echo $cuPhone->service_province->getSystem(); echo $cuPhone1->service_province->getSystem();
說明
- 列印了很多東西呀,不過主要的還是看看移動手機,也就是CMPhone中的__clone()方法,我們沒有重新去初始化一個新物件。這時,複製的圖片cmPhone中的是同一個物件。沒錯,這就是引用的複製問題。引用只是複製了引用的地址,他們指向的是同一個物件。當圖片cmPhone裡面的service_province物件裡面的屬性也跟著改變了。
- 在CUPhone中,我們重新new了一個新的service_province物件。這次外面的圖片cuPhone中引用物件的值。
- 原型模式中最主要的就是要注意上述兩點,而普通的值屬性會直接進行復制,不會產生這個問題。這裡又牽涉出另外兩個概念:淺複製和深複製
- 淺複製,是指被複制物件的所有變數都含有與原來物件相同的值,而所有的對其他物件的引用都仍然指向原來的物件
- 深複製把引用物件的變數指向複製過的新物件,而不是原有的被引用的物件
- 關於引用和值的問題,我們將在其他的文章中進行講解
下期看點
原型模式雖然平常用得不多,但是學習之後發現還真是挺有用的,特別是需要大量的重複物件時,可以大大節約新建物件的資源需求,以後還是需要多多練習早日應用在實際的業務場景中。下一個又會是誰呢?別急別急,先去下個館子,廚師、服務員、顧客,這三個要素就能組成一個神奇的模式:命令模式
總結
到此這篇關於PHP設計模式之原型模式的文章就介紹到這了,更多相關PHP原型模式內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!