設計模式(四)原型模式Prototype
1. 概述
我們都知道,建立型模式一般是用來建立一個新的對象,然後我們使用這個對象完成一些對象的操作,我們通過原型模式可以快速的建立一個對象而不需要提供專門的new()操作就可以快速完成對象的建立,這無疑是一種非常有效方式,快速的建立一個新的對象。
例子1:孫悟空拔下一嘬猴毛,輕輕一吹就會變出好多的孫悟空來。
例子2:寄個快遞
下面是一個郵寄快遞的情境:
“給我寄個快遞。”顧客說。
“寄往什麼地方?寄給……?”你問。
“和上次差不多一樣,只是郵寄給另外一個地址,這裡是郵寄地址……”顧客一邊說一邊把寫有郵寄地址的紙條給你。
“好!”你愉快地答應,因為你儲存了使用者的以前郵寄資訊,只要複製這些資料,然後通過簡單的修改就可以快速地建立新的快遞資料了。
2. 問題
當對象的建構函式非常複雜,在產生新對象的時候非常耗時間、耗資源的情況?我們是怎麼來建立呢?
3. 解決方案 通過複製(複製、拷貝)一個指定類型的對象來建立更多同類型的對象。這個指定的對象可被稱為“原型”對象,也就是通過複製原型對象來得到更多同類型的對象。即原型設計模式。在php的很多模板庫,都用到clone。如smarty等。4. 適用性
原型模式的主要思想是基於現有的對象複製一個新的對象出來,一般是有對象的內部提供複製的方法,通過該方法返回一個對象的副本,這種建立對象的方式,相比我們之前說的幾類建立型模式還是有區別的,之前的講述的原廠模式與抽象工廠都是通過工廠封裝具體的new操作的過程,返回一個新的對象,有的時候我們通過這樣的建立工廠建立對象不值得,特別是以下的幾個情境的時候,可能使用原型模式更簡單也效率更高。
• 1)當一個系統應該獨立於它的產品建立、構成和表示時,要使用 Prototype模式
• 2)當要執行個體化的類是在運行時刻指定時,例如,通過動態裝載;
• 3)為了避免建立一個與產品類層次平行的工廠類層次時
• 4)當一個類的執行個體只能有幾個不同狀態組合中的一種時。建立相應數目的原型並複製它們可能比每次用合適的狀態手工執行個體化該類更方便一些。(也就是當我們在處理一些對象比較簡單,並且對象之間的區別很小,可能只是很固定的幾個屬性不同的時候,可能我們使用原型模式更合適)。
5. 結構 原型模式結構如下頁所示:
6. 組成
客戶(Client)角色:使用原型對象的客戶程式
抽象原型(Prototype)角色:規定了具體原型對象必須實現的介面(如果要提供深拷貝,則必須具有實現clone的規定)
具體原型(ConcretePrototype):從抽象原型派生而來,是客戶程式使用的對象,即被複製的對象。此角色需要實現抽象原型角色所要求的介面。
7. 效果
Prototype模式有許多和Abstract Factory模式 和 Builder模式一樣的效果:它對客戶隱藏了具體的產品類,因此減少了客戶知道的名字的數目。此外,這些模式使客戶無需改變即可使用與特定應用相關的類。
下面列出Prototype模式的另外一些優點。
1 ) 運行時刻增加和刪除產品: Prototype允許只通過客戶端註冊原型執行個體就可以將一個新的具體產品類併入系統。它比其他建立型模式更為靈活,因為客戶可以在運行時刻建立和刪除原型。
2 ) 改變值以指定新對象: 高度動態系統允許你通過對象複合定義新的行為—例如,通過為一個物件變數指定值—並且不定義新的類。你通過執行個體化已有類並且將這些執行個體註冊為客戶對象的原型,就可以有效定義新類別的對象。客戶可以將職責代理給原型,從而表現出新的行為。這種設計使得使用者無需編程即可定義新“類” 。實際上,複製一個原型類似於執行個體化一個類。Prototype模式可以極大的減少系統所需要的類的數目。
3) 改變結構以指定新對象:許多應用由組件和子組件來建立對象。
4) 減少子類的構造 Factory Method 經常產生一個與產品類層次平行的 Creator類層次。Prototype模式使得你複製一個原型而不是請求一個Factory 方法去產生一個新的對象。因此你根本不需要Creator類層次。這一優點主要適用於像 C + +這樣不將類作為一級類對象的語言。像Smalltalk和Objective
C這樣的語言從中獲益較少,因為你總是可以用一個類對象作為產生者。在這些語言中,類對象已經起到原型一樣的作用了。
5) 用類動態配置應用 一些運行時刻環境允許你動態將類裝載到應用中。在像 C + +這樣的語言中,Prototype模式是利用這種功能的關鍵。一個希望建立動態載入類的執行個體的應用不能靜態引用類的構造器。而應該由運行環境在載入時自動建立每個類的執行個體,並用原型管理器來註冊這個執行個體(參見實現一節) 。這樣應用就可以向原型管理器請求新裝載的類的執行個體,這些類原本並沒有和程式相串連。 E T + +應用程式框架[ W G M 8 8 ]有一個運行系統就是使用這一方案的。
Prototype的主要缺陷是每一個Prototype的子類都必須實現clone操作,這可能很困難。例如,當所考慮的類已經存在時就難以新增 clone操作。當內部包括一些不支援拷貝或有循環參考的對象時,實現複製可能也會很困難的。
8. 實現
<?php/** * 原型模式 */ /** * 抽象原型角色 */interface Prototype { public function copy();} /** * 具體原型角色 */class ConcretePrototype implements Prototype{ private $_name; public function __construct($name) { $this->_name = $name; } public function setName($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function copy() { /** 深拷貝 */ return clone $this; /** 淺拷貝 */ //return $this; }} class Client { /** * Main program. */ public static function main() { $object1 = new ConcretePrototype(11); $object_copy = $object1->copy(); var_dump($object1->getName()); echo '<br />'; var_dump($object_copy->getName()); echo '<br />'; $object1->setName(22); var_dump($object1->getName()); echo '<br />'; var_dump($object_copy->getName()); echo '<br />'; }} Client::main();?>
9. 淺拷貝和深拷貝
原型模式的原理圖:
淺拷貝被拷貝對象的所有變數都含有與原對象相同的值,而且對其他對象的引用仍然是指向原來的對象。即淺拷貝只負責當前對象執行個體,對引用的對象不做拷貝。淺複製後的對象和對象副本的情況:
深拷貝
被拷貝對象的所有的變數都含有與原來對象相同的值,除了那些引用其他對象的變數。那些引用其他對象的變數將指向一個被拷貝的新對象,而不再是原有那些被引用對象。即 深拷貝把要拷貝的對象所引用的對象也都拷貝了一次,而這種對被引用到的對象拷貝叫做間接拷貝。深複製的對象和對象副本的情況:
深拷貝要深入到多少層,是一個不確定的問題。
在決定以深拷貝的方式拷貝一個對象的時候,必須決定對間接拷貝的對象是採取淺拷貝還是深拷貝還是繼續採用深拷貝。
因此,在採取深拷貝時,需要決定多深才算深。此外,在深拷貝的過程中,很可能會出現循環參考的問題。
10. 帶Prototype Manager的原型模式
原型模式的第二種形式是帶原型管理器的原型模式,其UML圖如下:
原型管理器(Prototype Manager)角色:建立具體原型類的對象,並記錄每一個被建立的對象。
下面這個例子示範了在原型管理器中儲存使用者預先定義的顏色原型,客戶通過原型管理器複製顏色對象。
<?php/** * abstract Prototype * */abstract class ColorPrototype{ //Methodsabstract function copy();}/** * Concrete Prototype * */class Color extends ColorPrototype{//Fieldsprivate $red;private $green;private $blue;//Constructorsfunction __construct( $red, $green, $red) { $this->red = $red; $this->green = $green; $this->blue = $red; } /** * set red * * @param unknown_type $red */public function setRed($red) { $this->red = $red; } /** * get red * */public function getRed(){ return $this->red; } /** *set Green * * @param $green */public function setGreen($green) { $this->green = $green; } /** * get Green * * @return unknown */public function getGreen() { return $this->green ; } /** *set Blue * * @param $Blue */public function setBlue($Blue) { $this->blue = $Blue;} /** * get Blue * * @return unknown */public function getBlue() { return $this->blue ;}/** * Enter description here... * * @return unknown */function copy(){return clone $this;}function display() {echo $this->red , ',', $this->green, ',', $this->blue ,'<br>';}}/** * Enter description here... * */class ColorManager{// Fieldsstatic $colors = array();// Indexerspublic static function add($name, $value){self::$colors[$name] = $value;}public static function getCopy($name) {return self::$colors[$name]->copy();}}/** *Client * */class Client{public static function Main(){//原型:白色ColorManager::add("white", new Color( 255, 0, 0 ));//紅色可以由原型白色對象得到,只是重新修改白色: r$red = ColorManager::getCopy('white');$red->setRed(255);$red->display();//綠色可以由原型白色對象得到,只是重新修改白色: g$green = ColorManager::getCopy('white');$green->setGreen(255);$green->display();//綠色可以由原型白色對象得到,只是重新修改白色: b$Blue = ColorManager::getCopy('white');$Blue->setBlue(255);$Blue->display();}}ini_set('display_errors', 'On');error_reporting(E_ALL & ~ E_DEPRECATED);Client::Main();?>