每日一記之php原型模式
原型模式是指通過現有的執行個體通過拷貝得到新的執行個體。
在程式的設計中,有的時候我們去執行個體化某個對象需要做太多的初始化工作,非常耗時的時候,我們可以考慮採用原型模式來得到新的執行個體。
其實在php中我們很容易通過clone關鍵字去實現對象的複製。另外通過魔術方法__clone()指定在clone的時候需要進行的操作。這個其實就是原型模式的實現方式了。當然,有時候為了讓代碼看起來比較優雅,比較完善。我們可以自己去寫相關的實現方式,當然也需要用到clone關鍵字。
name = $name; $this->startTime = time(); } public function copy(){ return clone $this; }}
在這裡我們可以建立一個task
$task1 = new Task("Task1");
現在我們已經有了一個Task的執行個體了,我們要得到一個新的task執行個體就可以通過clone的方法
$task2 = $task1->copy();
可是現在這樣我們列印$task1和$task2的startTime,兩者是一樣的,而我們又希望clone出來的對象時間應該是目前時間,怎麼做呢?所以我們就得去寫__clone方法,該方法在一個對象嘗試進行clone的時候會自動調用。改良後的代碼如下
class Task implements Cloneable{ public $name; public $startTime; public function __construct($name){ //這裡執行個體化Task需要做很多工作 $this->name = $name; $this->startTime = time(); } public function __clone(){ //在複製的時候把startTime設為目前時間 $this->startTime = time(); //複製的時候需要執行的操作 } public function copy(){ return clone $this; }}
這樣複製出來的對象的startTime就更正為目前時間了。
當然了,這隻是一個簡單的原型模式,在實際的複製中,又分為淺複製和深複製。
淺複製: 即對象在調用clone方法時只複製基本的資料類型,而如果對象中包含其他對象的引用時,則copy其他對象的引用
深複製:即除了複製基本的資料類型外,引用的類型的資料也一併複製。
舉個例子,現在我的task類裡面有一個其他對象的引用,比如Parent。相關代碼如下:
class TaskParent{ public $name; public function __construct($name){ $this->name = $name; }}class Task implements Cloneable{ public $name; public $startTime; public $parent; public function __construct($name){ //這裡執行個體化Task需要做很多工作 $this->name = $name; $this->startTime = time(); //這裡直接new一個parent $this->parent = new TaskParent("do some work"); } public function __clone(){ //在複製的時候把startTime設為目前時間 $this->startTime = time(); //複製的時候需要執行的操作 } public function copy(){ return clone $this; }}
在上面的代碼裡,我們新定義了一個TaskParent的類,然後Task持有該對象的一個引用,用戶端代碼:
$task1 = new Task("Task1");echo $task1->parent->name."\r\n";//clone一個對象$task2 = $task1->copy();echo $task2->parent->name."\r\n";//將task1的parent的name設為另外的值$task1->parent->name = "do another work";//列印task2的parent的值echo $task2->parent->name;上面的代碼最後一句話將列印 do another work,也就是說我們更改task1的parent影響到了task2。這就是典型的淺複製,如果想實現深複製,則可以進行如下改裝
class Task implements Cloneable{ public $name; public $startTime; public $parent; public function __construct($name){ //這裡執行個體化Task需要做很多工作 $this->name = $name; $this->startTime = time(); //這裡直接new一個parent $this->parent = new TaskParent("do some work"); } public function __clone(){ //在複製的時候把startTime設為目前時間 $this->startTime = time(); //複製parent $this->parent = clone $this->parent; //複製的時候需要執行的操作 } public function copy(){ return clone $this; }}
我們在__clone方法裡面對parent也進行了一次複製,所以現在列印剛才的代碼就沒有問題了。這就是深複製。
在真實的編碼環境中,可能一個對象持有很多其他的對象的引用,而其他對象對象又持有很多的引用。由於引用的不確定性,我們一開始的時候就應該注意,到底哪些對象需要深複製,那些對象不需要。
如果想實現快速的深複製,網上一哥們提供了一個簡單的方法代碼如下:
class Task implements Cloneable{ public $name; public $startTime; public $parent; public function __construct($name){ //這裡執行個體化Task需要做很多工作 $this->name = $name; $this->startTime = time(); //這裡直接new一個parent $this->parent = new TaskParent("do some work"); } public function __clone(){ //在複製的時候把startTime設為目前時間 $this->startTime = time(); //複製parent $this->parent = clone $this->parent; //複製的時候需要執行的操作 } public function copy(){ //return clone $this; //這裡不使用clone關鍵字,而是使用序列化和還原序列化來進行 return unserialize(serialize($this)); }}
上述代碼中通過serialize和unserialize來得到新的執行個體,經測試,完全是深複製。關於serialize和unserialize的效率以及具體講解將在以後補充上