設計模式 - Prototype Pattern 原型模式
Intro
假設我們今天要瘋狂產出同一台MacBook裝置,而裝置中皆含有相同規格的配件
實作
首先,如果是一般的建立物件方法及使用方法,我們會先寫一個物件,然後再需要用到它的時候將他new出來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class MacBook { private $cpu; private $ssd; private $ram; private $monitor; private $keyboard;
public function __construct() { $this->cpu = 'i7'; $this->ssd = '256G'; $this->ram = '16G'; $this->monitor = '13 inch'; $this->keyboard = 'default keyboard'; } }
|
假設製造MacBook的建構子中做的事情相當複雜,我要生成很多MacBook就意味著我必須要負擔相同且重複又複雜的建構過程,而這些過程卻是完全一樣的。
1 2 3 4 5
| $MacBook1 = new MacBook(); $MacBook2 = new MacBook(); $MacBook3 = new MacBook(); $MacBook4 = new MacBook(); $MacBook5 = new MacBook();
|
這無疑是造成資源上的浪費,所以底下我們實作普遍上來說的Prototype
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class MacBook { private $cpu; private $ssd; private $ram; private $monitor; private $keyboard;
public function __construct() { $this->cpu = 'i7'; $this->ssd = '256G'; $this->ram = '16G'; $this->monitor = '13 inch'; $this->keyboard = 'default keyboard'; } public function clone() { return $this; } }
|
我們多新增了一個clone方法,我們的MacBook可以透過clone回傳一個已經建構好的MacBook而不需要再經過那相同且複雜的建構子過程。
1 2 3 4 5 6
| $MacBook = new MacBook(); $MacBook1 = $MacBook->clone(); $MacBook2 = $MacBook->clone(); $MacBook3 = $MacBook->clone(); $MacBook4 = $MacBook->clone(); $MacBook5 = $MacBook->clone();
|
注意上方程式碼只是將Prototype的精神實作出來
事實上這樣做的話每個被clone的類別的reference會變成完全相同。
其實現在一般的語言都會實作這個模式讓大家可以直接使用了,例如PHP本身就有clone()語法可以使用,也提供了magic method __clone()讓人可以自定義當呼叫clone的時候可以額外多做的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class MacBook { private $cpu; private $ssd; private $ram; private $monitor; private $keyboard;
public function __construct() { $this->cpu = 'i7'; $this->ssd = '256G'; $this->ram = '16G'; $this->monitor = '13 inch'; $this->keyboard = 'default keyboard'; }
public function __clone() { } }
|
1 2 3 4 5 6
| $MacBook=new MacBook(); $MacBook1 = clone $MacBook; $MacBook2 = clone $MacBook; $MacBook3 = clone $MacBook; $MacBook4 = clone $MacBook; $MacBook5 = clone $MacBook;
|
淺複製(Shallow Copy)
我們常常遇到一種狀況,就是物件內的變數其實還是一個物件,而這種時候如果使用的是預設的clone則會發生一種狀況
在clone完之後會發現物件內的物件reference會全部指向同一個位置
此現象我們稱為淺複製
如底下的例子,如果我將RAM獨立出來變成一個類別,這樣子去clone的話我們會發現所有的MacBook中的RAM的reference都會指向同一個位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class MacBook2RAM { private $cpu; private $ssd;
private $ram; private $monitor; private $keyboard;
public function __construct() { $this->cpu = 'i7'; $this->ssd = '256G'; $this->ram = new RAM(); $this->monitor = '13 inch'; $this->keyboard = 'default keyboard'; } public function __clone() { } }
class RAM { public $ram1 = '8G'; public $ram2 = '8G'; }
|
1 2 3 4 5 6
| $MacBook=new MacBook(); $MacBook1 = clone $MacBook; $MacBook2 = clone $MacBook; $MacBook3 = clone $MacBook; $MacBook4 = clone $MacBook; $MacBook5 = clone $MacBook;
|
深複製(Deep Copy)
如上面的情況,如果我們沒有要更改各自的RAM內容的話,就不會有什麼問題發生
但如果我們今天想要更改某幾台的RAM配置的話,在相同reference的前提下,會發生我改一個全部就一起被更改的問題,導致我不能單獨為特定幾台去更改RAM的配置
所以底下實作了搭配__clone去進行深複製的寫法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class MacBook2RAM { private $cpu; private $ssd;
private $ram; private $monitor; private $keyboard;
public function __construct() { $this->cpu = 'i7'; $this->ssd = '256G'; $this->ram = new RAM(); $this->monitor = '13 inch'; $this->keyboard = 'default keyboard'; }
public function __clone() { $this->ram = clone $this->ram; } }
|
時機
- 當目標物件的建構需付出龐大代價時
- 當我們需要對象物件的副本時
目的
制定可以用原型個體生成的物件類型,之後只需要複製此物件即可生成新物件
類別圖
Plant UML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @startuml
skinparam classAttributeIconSize 0
interface Prototype{ {method} +clone() }
class ConcretePrototypeA{ {method} +clone() } class ConcretePrototypeB{ {method} +clone() }
Prototype <|.. ConcretePrototypeA Prototype <|.. ConcretePrototypeB
@enduml
|
優點
- 不需要跟其他的類別有耦合(例如工廠)
- 避免反覆付出建構子中的初始化邏輯代價,方便的生成複雜的類別
缺點