享元模式,亦稱:緩存、Cache、Flyweight。
設計模式是解決開發時遇到普遍存在(反覆出現)的問題的各種解法。但並不是絕對的,遇到問題才使用解法而不是為了使用而使用。
切記: 不要拿了錘子,看什麼都是釘子
介紹
享元模式是一種結構型設計模式,透過減少創建物件內的重複數據,來減少內存佔用和提高性能。讓你能在有限的內存容量中載入更多物件。
情境
玩過戰略型遊戲(如: 世紀帝國)的人都知道,遊戲內是可以大量生產各種士兵的。相同種類的士兵外型、能力、行為都是相同的,但會有各自的位置及血量。
若是系統將大量士兵物件各自實例化,那可能導致佔用過多系統資源,導致遊玩順暢度不盡理想。
讓我們來看看如何利用享元來解決此問題。
範例
士兵類型(定義了士兵種類的名稱、能力、行為)
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
| # 士兵抽象類別 abstract class ASoldier { # 出生 public function initXY(int $key, int $x, int $y) { echo $this->name . ' ' . $key . ' 出生,初始位置 X: ' . $x . ' Y: ' . $y . '<br/><br/>'; }
# 移動 public function move(int $key, int $x, int $y) { echo $this->name . ' ' . $key . ' 移動至 X: ' . $x . ' Y: ' . $y . '<br/><br/>'; } }
# 步兵 class Infantry extends ASoldier { protected $name = '步兵'; protected $power = 10; }
# 弓箭手 class Archer extends ASoldier { protected $name = '弓箭手'; protected $power = 20; }
|
士兵產生工廠(用於進行士兵類型取得)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # 士兵工廠 class SoldierFactory { protected $soldier = [];
# 取得對應士兵實作 public function getSoldierType($key) { if (!isset($this->soldier[$key])) { $this->soldier[$key] = new $key(); }
return $this->soldier[$key]; }
# 計算物件數量 public function countObj() { echo '士兵類型數量: ' . count($this->soldier); } }
|
產生各種種類的士兵並讓他們亂動吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| # 士兵生產工廠 $soldierFactory = new SoldierFactory();
# 生產步兵 1 號 $Infantry1 = $soldierFactory->getSoldierType('Infantry'); $Infantry1->initXY(1, 100, 200);
# 生產步兵 2 號 $Infantry2 = $soldierFactory->getSoldierType('Infantry'); $Infantry2->initXY(2, 100, 200);
# 生產弓兵 1 號 $Archer1 = $soldierFactory->getSoldierType('Archer'); $Archer1->initXY(1, 100, 200);
# 開始亂跑 $Infantry1->move(1, 400, 100); $Infantry2->move(2, 300, 200); $Archer1->move(1, 500, 700);
$soldierFactory->countObj();
|
可獲得結果
1 2 3 4 5 6 7 8 9 10 11 12 13
| 步兵 1 出生,初始位置 X: 100 Y: 200
步兵 2 出生,初始位置 X: 100 Y: 200
弓箭手 1 出生,初始位置 X: 100 Y: 200
步兵 1 移動至 X: 400 Y: 100
步兵 2 移動至 X: 300 Y: 200
弓箭手 1 移動至 X: 500 Y: 700
士兵類型數量: 2
|
結論
我們可以看到,就算我們產生了 3 隻士兵(不同種類),但士兵類型的物件卻只實作了 2 個。
因為我們將士兵拆成了內在狀態(屬性)與外在狀態(編號、座標)。進而透過共享內在狀態(屬性)去節省系統效能的使用。
討論
若物件中有很多相似屬性, 那麼享元將可以節省大量內存。但由於物件屬性的拆分,程式碼將會會變得更加復雜。
效能提升的代價就是維護的效率。
享元 VS 單例
享元模式與單例模式結構上非常相似,都是透過重複使用實例化物件來節省系統效能。差別在於,單例模式只會有一個實體, 而享元可以有多個實體, 各實體的內在狀態也可以不同。