設計模式--原型模式Prototype(建立型)

benbenxiongyuan發表於2014-04-11

轉自:http://blog.csdn.net/hguisu/article/details/7518947

 

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模式使得你克隆一個原型而不是請求一個工廠方法去產生一個新的物件。因此你根本不需要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
{
  //Methods
	abstract function  copy();
}

/**
 * Concrete Prototype
 *
 */
class Color extends ColorPrototype{
	//Fields
	private  $red;
	private  $green;
	private  $blue;
	//Constructors
	function __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
{
	// Fields
	static  $colors = array();
	// Indexers
	public 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();
?>

相關文章