C++ 設計模式 - 原型模式 Prototype Pattern

想像一下你手上有一個心愛的玩偶,這個玩偶有著獨特的外觀、材質和手感。如果你想要再擁有一個一模一樣的玩偶,你會怎麼做呢?重新製作一個可能需要花費很多時間與精力,但如果有一個神奇的「複製器」,只要按一下按鈕,就能夠完美複製出另一個相同的玩偶,那該有多方便!這就是「原型模式(Prototype Pattern)」在軟體設計中的核心思想。這種模式讓我們能夠快速複製已經存在的物件,而不需要從頭再建立一遍。

什麼是原型模式?

原型模式是一種建立型設計模式,它允許我們透過複製現有的物件來建立新物件,而不是透過傳統的方式來構建新物件。這種方式特別適合於物件建立成本高昂或結構複雜的情境。具體來說原型模式包含了一個介面,這個介面定義了複製自身的能力。每個實現這個介面的物件都能夠建立一個自己的複本,並且複製出來的新物件與原始物件在狀態上是相同的。

在C++中,這通常是透過深拷貝或淺拷貝來實現的。深拷貝會複製物件中的所有資料,包括內部指標所指向的資源,而淺拷貝則只複製物件的基本結構,而共享內部資源。

想像你正在開發一個角色扮演遊戲。遊戲中有各種各樣的怪物,每種怪物都有自己的屬性和技能。如果每次需要新怪物時都要從頭建立,傳入很多參數跟設定數值,那將是一個繁瑣的過程。這時原型模式就能派上用場了。

再舉個例子,想像你正在開發一個圖形設計軟體,其中有些常用的圖形,例如矩形,客戶端在使用時可能需要頻繁地建立相似的矩形,而這些矩形的基本屬性是相同的(顏色、寬度、高度),為了提高客戶端的工作效率,就能夠使用原型模式快速複製產生新的矩形物件。

這個模式的基本角色包括:

  1. 原型(Prototype):宣告一個複製自己本身的介面。
  2. 具體原型(Concrete Prototype):實現複製自己本身的方法。
  3. 客戶端(Client):透過呼叫原型的複製方法來取得新的物件。

原型模式在遊戲角色建立中的應用

讓我們以遊戲開發為例,來看看原型模式的應用。一個典型的角色扮演遊戲(RPG)中,玩家可以選擇不同的職業,每個職業都有其獨特的屬性、技能和裝備。如果每次建立一個新角色都要從頭設定這些屬性,那將是非常耗時的事情。但如果我們能夠建立一個基礎角色的原型,然後透過複製這個原型來產生新角色,就能夠大大簡化這個過程。

首先我們定義一個 Prototype 介面,它包含了一個 clone() 方法,這個方法用來複製物件,

1
2
3
4
5
6
7
// 原型 Prototype
class CharacterPrototype {
public:
virtual CharacterPrototype* clone() const = 0;
virtual void showAttributes() const = 0;
virtual ~CharacterPrototype() = default;
};

接著我們建立一個具體的角色類別,例如一個戰士角色,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 具體原型 Concrete Prototype
class Warrior : public CharacterPrototype {
private:
int strength;
int defense;
std::string weapon;

public:
Warrior(int str, int def, const std::string& weap)
: strength(str), defense(def), weapon(weap) {}

CharacterPrototype* clone() const override {
return new Warrior(*this);
}

void showAttributes() const override {
std::cout << "Warrior with strength: " << strength
<< ", defense: " << defense
<< ", weapon: " << weapon << std::endl;
}
};

現在我們在客戶端可以使用這個角色原型來建立新角色,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
// 建立初始角色
CharacterPrototype* originalWarrior = new Warrior(100, 50, "Sword");

// 複製角色
CharacterPrototype* clonedWarrior = originalWarrior->clone();

originalWarrior->showAttributes(); // 輸出:Warrior with strength: 100, defense: 50, weapon: Sword
clonedWarrior->showAttributes(); // 輸出:Warrior with strength: 100, defense: 50, weapon: Sword

delete originalWarrior;
delete clonedWarrior;

return 0;
}

執行上述程式碼,我們會得到以下輸出:

1
2
Warrior with strength: 100, defense: 50, weapon: Sword
Warrior with strength: 100, defense: 50, weapon: Sword

在這個例子中,我們建立了一個戰士角色作為原型,並使用原型的 clone() 方法產生了一個一模一樣的新角色。這樣,我們不需要每次建立新角色時都手動設定屬性,而是透過簡單的複製操作快速產生所需的角色。

原型模式的優缺點

原型模式的一大優點是它提供了一種高效的物件建立方式,特別是在需要多次建立類似物件的情況下,能夠節省大量時間和資源。由於原型模式直接複製現有的物件,我們可以保證新物件與原始物件具有相同的狀態,這在某些需要精確控制物件狀態的場景中尤為重要。

然而原型模式的缺點在於複製過程的複雜性。當物件的結構非常複雜或包含指標、動態分配的資源時,正確地實現深拷貝可能會變得困難。這要求開發者對物件的內部結構有非常清晰的理解,並且小心處理拷貝過程中的細節。另外如果物件中包含了不應該被複製的部分,或需要在複製後進行一些特定處理,那麼在實際應用中可能會遇到額外的挑戰。

整理來說原型模式在適合的情境下能夠極大地提高開發效率,但也需要謹慎使用,確保在複製物件時沒有引入額外的複雜度或潛在的錯誤。

總結

原型模式在我們的日常生活中也隨處可見,影印機複印文件時,原始文件就是原型,複印出來的每一份都是複製品。在細胞分裂中,新的細胞是從原有細胞複製而來的。這些例子都體現了原型模式的核心思想:透過複製現有的物件來建立新物件。

原型模式是一種強大而靈活的建立型設計模式,適合於那些需要複製大量相似物件的應用場景。透過原型模式,我們可以輕鬆地產生具有相同屬性和狀態的新物件,從而提高開發效率,降低複雜度。然而在使用時需要注意物件複製過程中的潛在風險,特別是涉及到深拷貝和資源管理時。

設計模式是解決問題的工具,而不是目的本身。靈活運用才能寫出既優雅又實用的程式碼。在軟體開發的道路上,讓我們能像原型模式一樣,在「複製」中不斷成長,創造出屬於自己的精彩作品。