設計模式 - 享元模式

享元模式,亦稱:緩存、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 個。

因為我們將士兵拆成了內在狀態(屬性)與外在狀態(編號、座標)。進而透過共享內在狀態(屬性)去節省系統效能的使用。

圖1

討論

若物件中有很多相似屬性, 那麼享元將可以節省大量內存。但由於物件屬性的拆分,程式碼將會會變得更加復雜。

效能提升的代價就是維護的效率。

享元 VS 單例

享元模式與單例模式結構上非常相似,都是透過重複使用實例化物件來節省系統效能。差別在於,單例模式只會有一個實體, 而享元可以有多個實體, 各實體的內在狀態也可以不同。

Author

LinYoYo

Posted on

2022-03-20

Updated on

2022-03-20

Licensed under