<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>猴子靈藥 [Monkey Potion] &#187; 設計模式</title>
	<atom:link href="http://blog.monkeypotion.net/category/gameprog/pattern/feed" rel="self" type="application/rss+xml" />
	<link>http://blog.monkeypotion.net</link>
	<description>遊戲開發‧遊戲程式‧遊戲設計</description>
	<lastBuildDate>Thu, 02 Feb 2012 11:32:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Strategy &amp; State：條件判斷式的消除者</title>
		<link>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state</link>
		<comments>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state#comments</comments>
		<pubDate>Sun, 22 Feb 2009 16:48:05 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[設計模式]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=1185</guid>
		<description><![CDATA[身為一位程式設計者，你是否曾面臨條件判斷式繁殖過盛的困擾？經常用折疊不完的 N 層 if-else 結構來考驗自己的腦力？或是只能瞪著超過 500 行的 switch-case 條件判斷式舉手投降？您的困擾我瞭解，請容許我向您引薦本篇文章的雙主打星——Strategy 與 State 範式，讓它們帶領我們一同邁向美妙動人的範式之道吧！ 首先要瞭解的是，為什麼要將 Strategy 與 State 範式放在同一篇文章裡介紹？因為兩者雖然在設計層面的動機與出發點有所差異，但是在實際的應用面中非常地相近。根據《物件導向設計模式》(Design Patterns) 書中的定義，只要將右圖中的 Strategy、ConcreteStrategyA 與 ConcreteStrategyB 角色，更改為 State、ConcreteStateA 與 ConcreteStateB，就會變成 State 範式的結構圖，可以說兩者就像是孿生兄弟般密切相關。 如果真的要區分出 Strategy 與 State 範式之間的差異，可以參考《重構——向範式前進》中論述的內容： State pattern 對「必須在一整族 state classes 的實體之間輕鬆轉換」的 class 有益，而 Strategy pattern 則是有助於讓 class 把演算法執行任務委託給「一整族 Strategy classes 的某一個實體」。 從我理解的角度來解釋，Strategy 範式比較著重於包裝相同派系的演算法，而 State 範式則特別注重在各狀態之間的轉換邏輯。所以只要瞭解 Strategy 或 [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_1268" class="wp-caption alignright" style="width: 466px"><img src="http://blog.monkeypotion.net/wp-content/uploads/2009/02/strategy-pattern.jpg" alt="strategy-pattern" title="strategy-pattern" width="456" height="231" class="size-full wp-image-1268" /><p class="wp-caption-text">Strategy Pattern</p></div>
<p>身為一位程式設計者，你是否曾面臨條件判斷式繁殖過盛的困擾？經常用折疊不完的 N 層 if-else 結構來考驗自己的腦力？或是只能瞪著超過 500 行的 switch-case 條件判斷式舉手投降？您的困擾我瞭解，請容許我向您引薦本篇文章的雙主打星——Strategy 與 State 範式，讓它們帶領我們一同邁向美妙動人的範式之道吧！</p>
<p>首先要瞭解的是，為什麼要將 Strategy 與 State 範式放在同一篇文章裡介紹？因為兩者雖然在設計層面的動機與出發點有所差異，但是在實際的應用面中非常地相近。根據《物件導向設計模式》(Design Patterns) 書中的定義，只要將右圖中的 Strategy、ConcreteStrategyA 與 ConcreteStrategyB 角色，更改為 State、ConcreteStateA 與 ConcreteStateB，就會變成 State 範式的結構圖，可以說兩者就像是孿生兄弟般密切相關。</p>
<p>如果真的要區分出 Strategy 與 State 範式之間的差異，可以參考《重構——向範式前進》中論述的內容：</p>
<blockquote><p>
State pattern 對「必須在一整族 state classes 的實體之間輕鬆轉換」的 class 有益，而 Strategy pattern 則是有助於讓 class 把演算法執行任務委託給「一整族 Strategy classes 的某一個實體」。
</p></blockquote>
<p>從我理解的角度來解釋，<strong>Strategy 範式比較著重於包裝相同派系的演算法，而 State 範式則特別注重在各狀態之間的轉換邏輯</strong>。所以只要瞭解 Strategy 或 State 範式兩者之一，就等於學會了兩種設計模式，真的是太划算太值得啦！雖然 Strategy 與 State 範式非常單純而且易於理解，但這兩項看似不起眼的小小範式，卻經常能夠在程式系統中發揮很好的應用效果，絕對是程式設計者不可不學的必備基礎知識。本文中將以 Strategy 範式為主，說明兩者在遊戲系統中的相關應用。</p>
<p><span id="more-1185"></span></p>
<p>如上面的 UML 結構圖所示，Strategy 範式由三種角色的交互作用而生：</p>
<blockquote>
<ul>
<li><strong>Strategy</strong>：制訂出一組共通的介面，使 Context 能夠透過此介面呼叫由 ConcreteStrategy 所實做的演算法。</li>
<li><strong>ConcreteStrategy</strong>：根據 Strategy 所制訂的介面，具體實做出合適的演算法。</li>
<li><strong>Context</strong>：Strategy 的操作者，另可定義介面以供 Strategy 存取自己的資料。</li>
</ul>
</blockquote>
<p>讓我們直接來看看，單純的條件判斷式在遊戲系統中產生的問題，以及 Strategy 範式如何解決問題。</p>
<p>在遊戲系統的程式碼中，<strong>特別是與遊戲邏輯相關的部分，經常會面臨需依照各項條件要素，切換不同控制邏輯的情況。</strong>以一般的遊戲主迴圈流程為例，多半由偵測鍵盤滑鼠等輸入裝置、更新遊戲世界，以及繪製遊戲世界三項程序組成：</p>
<pre name="code" class="cpp">
while (m_GameIsRunning) {
	// 處理輸入裝置訊息
	ProcessInput();
	// 更新遊戲邏輯
	Update();
	// 繪製遊戲世界
	Render();
}
</pre>
<p>但是對於一個完整的遊戲來說，光是區分為上述這三項處理程序，無法完整滿足遊戲中的各種設計需求以及狀態轉換。例如，在剛啟動遊戲時、進入遊戲主選單時，以及遊戲運行中時，可能就會需要三種完全不同的偵測、更新與繪製程序，所以在此可以宣告一些列舉值，用來區分各種遊戲狀態，接著再以 if-else 條件判斷句在處理程序中分項處理：</p>
<pre name="code" class="cpp">
enum GameState
{
	GameState_Init,
	GameState_Menu,
	GameState_InGame,
};

void ProcessInput()
{
	if (m_State == GameState_Init) {
		// 處理剛啟動遊戲時的輸入偵測邏輯
	}
	else if (m_State == GameState_Menu) {
		// 處理進入遊戲主選單時的輸入偵測邏輯
	}
	else if (m_State == GameState_InGame) {
		// 處理遊戲運行時的輸入偵測邏輯
	}
}
</pre>
<p>除了 ProcessInput() 函式以外，Update() 與 Render() 函式也以同樣的方法區分成不同的流程控制邏輯。如此一來，只要我們在程式執行的過程中改變 m_State 的數值，當遊戲主迴圈下次進入這三項程序時，自然就會切換至不同的處理流程中了。這樣的程式邏輯看起來完全無害，不是嗎？</p>
<p>但實際上，使用 if-else 敘述句的作法不夠直覺化，有另外一位控制結構的候選者更合適於擔綱這項任務。只要是熟習程式語言基本語法的讀者，應該不難想到可以將上面的 if-else 敘述句，轉換成另外一種作用相同的 switch-case 敘述句：</p>
<pre name="code" class="cpp">
void ProcessInput()
{
	switch (m_State)	{
		case GameState_Init: {
			// 處理剛啟動遊戲時的輸入偵測邏輯
			break;
		}
		case GameState_Menu: {
			// 處理進入遊戲主選單時的輸入偵測邏輯
			break;
		}
		case GameState_InGame: {
			// 處理遊戲運行時的輸入偵測邏輯
			break;
		}
	}
}
</pre>
<p>相較於 if-else 敘述句，使用 switch-case 敘述句的優點，在於每個分支選項都會擁有同等的重要性。所以對於程式設計者來說，只要一見到 switch-case，首先就會聚焦在控制變數 m_State 的身上，並且直覺化地理解其中的各項 case 程序，都是具有同等重要性的程式碼。而也正因為 switch-case 比較符合程式設計者的直覺觀感，所以在遊戲系統的程式碼中，<strong>switch-case 敘述句也是最常被濫用與誤用的條件判斷式</strong>。</p>
<p>當遊戲專案剛啟始，同時整個程式系統仍處於萌芽階段時，使用前述 switch-case 的方法並不會產生任何問題。然而，隨著與日俱增的專案進度與設計需求，程式系統也會無可避免地越來越加錯綜複雜。可能一開始時，程式設計者只是單純想使用一個最簡單明快的方法，以解決切換控制邏輯的需求；然而，隨之而來的程式碼增修幅度，總是遠遠超過原先的預期程度。即使只是偶爾增加個十幾行程式碼，到了專案中後期時，卻有可能使一個小小的 switch-case 敘述句膨脹至 500 行以上的巨型怪物。</p>
<p>為了避免 switch-case 毫無節制地接受程式設計者的餵食，最終成為體態臃腫的大傢伙，我們立即可想到的一項改善方式，就是把原來散落在 case 敘述句中的程式碼，依照功能分門別類，包裝成一個個的中小型函式：</p>
<pre name="code" class="cpp">
void Update()
{
	switch (m_State)
	{
		case GameState_Init:
		{
			UpdateNetwork();
			UpdateGameWorld();
			UpdatePhysicsEngine();
			UpdateAudioEngine();
			UpdateGraphicsEngine();
			break;
		}
		case GameState_Menu:
		{
			break;
		}
		case GameState_InGame:
		{
			break;
		}
	}
}
</pre>
<p>有些奉「效能」兩字為最高指導原則的程式設計者，可能會因為遊戲執行效能上的疑慮，而不願意將原來的程式碼包裝成一個個的小型函式。但事實上，這不僅是沒有必要的顧慮，也是一種「過早進行最佳化」的錯誤觀念。在最低限度的情況下，甚至只需要妥善利用 inline 方式來宣告函式，就能夠大幅降低函式呼叫的成本了。</p>
<p>雖然將 case 敘述句中的程式碼分裝處理是個好的開始，但是仍然沒有解決根本上的條件判斷式過多的問題，而且<strong>使用 switch-case 敘述句，就像是中了流行性感冒病毒一樣，會不斷地在整個程式系統中傳染擴散開來，讓程式設計者越來越難以擺脫</strong>。以上述的幾個分裝函式為例，除了在原有的 Update() 函式中判斷不同的運作條件之外，UpdateNetwork() 是不是同樣也需要進行判斷？還有 UpdateGameWorld()、UpdatePhysicsEngine() 和 UpdateGraphicsEngine() 函式呢？應該也免不了需要切換不同的控制流程吧！</p>
<p>所以，分裝函式最終經常會長出像這樣的程式碼：</p>
<pre name="code" class="cpp">
void UpdateNetwork()
{
	switch (m_State) {
		case GameState_Init: {
			break;
		}
		case GameState_Menu: {
			break;
		}
		case GameState_InGame: {
			break;
		}
	}
}

void UpdateGameWorld()
{
	switch (m_State)	{
		case GameState_Init: {
			break;
		}
		case GameState_Menu: {
			break;
		}
		case GameState_InGame: {
			break;
		}
	}
}
</pre>
<p>還有 UpdatePhysicsEngine()、UpdateGraphicsEngine()、UpdateAudioEngine() 和 UpdateOOXX() 等等，到處都是 switch-case 敘述句，想躲也躲不了！</p>
<p>而除了近乎無性繁殖的 switch-case 程式碼以外，更令人頭痛的是，當程式設計者需要加入一項全新的遊戲狀態，例如 GameState_Pause 來控制遊戲暫停時的邏輯運作時，我們必須得回過頭來，前往每一個與 m_State 變數相關的 switch-case 敘述句中，加上一則新的 case 敘述句使 GameState_Pause 狀態能夠正確運作。或許你會認為：「用 Copy-Paste 的方式很快就可以完成，不是嗎？」<strong>但是人為干預的動作越多，也就代表疏忽犯錯的可能性越高。</strong></p>
<p>清楚瞭解濫用 if-else 與 switch-case 的種種問題之後，就輪到本篇的英雄主角上場，拯救我們程式設計者的世界啦！為了能夠妥善管理各種不同遊戲狀態之間的運作邏輯，首先可以利用純虛擬函式，宣告一個無法直接具現化的抽象類別 GameState，來扮演最前面所提到的 Strategy 角色：</p>
<pre name="code" class="cpp">
class GameState
{
public:
	virtual void Update() = 0;
	virtual void Render() = 0;
	virtual void ProcessInput() = 0;
};
</pre>
<p>將 GameState 宣告為抽象類別，優點在於強制使用者必須自行定義子類別的實做細節，藉此可避免對於 GameState 類別的誤用情形。接著，就可以依照遊戲系統的需求，自 GameState 衍生出一個個獨特的 ConcreteStrategy 角色：</p>
<pre name="code" class="cpp">
class GameInitState : public GameState
{
public:
	virtual void Update() {
		// 處理剛進入遊戲時的更新程序
	}

	virtual void Render() {
		// 處理剛進入遊戲時的繪製程序
	}

	virtual void ProcessInput() {
		// 處理剛進入遊戲時的輸入偵測程序
	}
};

class GameMenuState : public GameState
{
public:
	virtual void Update() {
		// 處理遊戲主選單的更新程序
	}

	virtual void Render() {
		// 處理遊戲主選單的繪製程序
	}

	virtual void ProcessInput() {
		// 處理遊戲主選單的輸入偵測程序
	}
};
</pre>
<p>依此方式製作出 GameInitState、GameMenuState 與 GameInGameState 類別後，就可以快樂地和 switch-case 說再見囉！原來的 ProcessInput()、Update() 與 Render() 三項程序就可以修改為：</p>
<pre name="code" class="cpp">
void ProcessInput()
{
	m_CurrentState->ProcessInput();
}

void Update()
{
	m_CurrentState->Update();
}

void Render()
{
	m_CurrentState->Render();
}
</pre>
<p>是的，你沒看錯！就這麼容易！先前由一個 m_State 變數以及 switch-case 敘述句控制的條件判斷式，轉換成僅需要一個 m_CurrentState 物件進行操控的程式碼邏輯。程式設計者只需要依條件更換 m_CurrentState 所指向的 ConcreteStrategy 物件，就能夠輕鬆地享受後續的免費服務而無須費心。所以只要我們能夠善加利用 C++ 語言的「虛擬」與「多型」特性，就能夠為程式設計者節省下許多非常珍貴的腦容量與腦細胞，使用在其他更有意義的難題上。</p>
<p>而 Strategy 與 State 範式，不僅能夠用在遊戲主迴圈的流程控制程序中，更可以運用在人工智慧等許多不同的系統中。像遊戲系統中常見的角色狀態機制，同樣也可以利用 State 範式的方式來實作。首先定義好基礎的 State 類別之後，就可以進一步衍生出 IdleState、MoveState、CombatState 等等不同用途的狀態類別：</p>
<pre name="code" class="cpp">
class Character;

class State
{
public:
	State(Character* context);
};

class IdleState : public State
{
public:
	IdleState(Character* context);
};

class MoveState : public State
{
public:
	MoveState(Character* context);
};

class CombatState : public State
{
public:
	CombatState(Character* context);
};
</pre>
<p>「萬里長城萬里長～」如同萬里長城般綿延不絕而且橫跨數個頁面的 if-else 或 switch-case 敘述句，絕對是任何一位程式設計者最不願意見到的程式碼結構之一。當你爽快地揮舞著 if-else 與 switch-case 連段 Combo 招式時，請先暫停片刻，冷靜下來想想這樣的做法是否合適？是否有其他方法可以簡化條件判斷式？</p>
<p>另外，如果你習慣於撰寫動輒六、七層以上的條件判斷式深度，不但是在考驗自己的邏輯思考空間，同時也是在殘害程式碼的閱讀者與維護者——而很有可能你自己就是那位可憐的閱讀者與維護者。為了避免寫出深達地底三萬呎的迷宮結構，我們可以多加利用 return、break 敘述句，或者判斷反向的 boolean 值，以減少條件判斷式的巢狀深度。</p>
<p>平時要如何自我檢查是否已經中了條件判斷式的流行性感冒？<strong>通常當你發現自己反覆用著 Copy-Paste 在程式碼檔案之間，處理某些「大部分相同而小部分相異」的程式碼時，很可能就是一種濫用條件判斷式的徵兆。</strong>請記得，早期發現早期治療的效果最好，否則當程式碼日益增長茁壯，重構的痛苦也會越來越厚重。當你發現自己在兩個地方以上寫著相同的條件判斷式時，可能就是就是 Strategy 與 State 範式該出馬的時候了！</p>
<p>體認完 Strategy 與 State 如何拯救世界之後，最後也該瞭解它們可能產生的副作用。若從效能層面考量，比起單純的 switch-case 敘述句，使用 Strategy 或 State 範式需要一次額外的 virtual table 提領步驟，多少會造成某種程度的效能衝擊。然而，就像之前所提到的「不要過早進行最佳化」<strong>，程式設計者應該先以 80/20 法則確認系統的效能瓶頸所在，然後再開始動手改善，才能獲得最佳的優化效益</strong>。另外要注意的是，Strategy 與 State 範式並不是任何狀況都適用的銀子彈，如果用在錯誤的地方，將會產生過多小型的物件類別，反而可能降低類別階層的耦合性與內聚力。</p>
<p>只要善加利用 Strategy 與 State 範式，就可以大幅簡化遊戲系統的運作邏輯，使程式碼更加易於閱讀與維護，同時也會變成一種無須太多註解與文件說明，就能夠在程式設計者間達到良好成效的溝通方式。以後再見到 switch-case 敘述句或是準備下手使用之前，不妨先仔細分析一下，想想是否能夠使用 Strategy 或者 State 範式取而代之吧！這樣可以讓你自己也讓其他人過得更快樂唷～</p>
<p><strong>延伸閱讀</strong>：</p>
<ul>
<li>《重構——改善既有程式的設計》：<br />
〈8.15〉以 State/Strategy 取代型別代碼</li>
<li>《重構——向範式前進》：<br />
〈7.2〉以 Strategy 替代條件邏輯<br />
〈7.4〉以 State 替代「狀態變換」條件句</li>
</ul>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=1185&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/pattern/strategy-and-state/feed</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Prototype：物件原型複製者</title>
		<link>http://blog.monkeypotion.net/gameprog/pattern/prototype-pattern</link>
		<comments>http://blog.monkeypotion.net/gameprog/pattern/prototype-pattern#comments</comments>
		<pubDate>Wed, 17 Sep 2008 16:09:19 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[設計模式]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/?p=127</guid>
		<description><![CDATA[在物件導向生成設計模式的範疇中，除了之前曾經介紹過的 Facory Method 以及 Abstract Factory 兩項工廠類型的模式以外，本文將繼續介紹另一項在遊戲程式領域中，經常受到廣泛使用的 Prototype 設計模式。 Prototype，原意為原型或雛形，與工廠模式接收到使用者命令之後即時生產物件的方式不同，在 Prototype 模式中，我們需要先打造出物件的原型樣版，待鑄模程序完成以後，接下來就能夠輕易地以此原型複製產生出全新的物件。複製，也可以是一種生成物件的方法。如 UML 圖所示，在 Prototype 設計模式中，定義了三個參與角色： Prototype：宣告自我複製的介面。 ConcretePrototype：具體實作出自我複製的操作。 Client：叫原型個體自我複製一份，以生出新的物件。 按照上述的責任關係，程式系統的使用者 Client 只需要指涉到基礎的 Prototype 介面，就能夠以此 Prototype 介面所宣告的 Clone() 方法，複製出類別的物件成品；而 ConcretePrototype 們衍生自 Prototype 介面，是 Client 進行 Operation() 方法後將會獲得的實際成品，所以必須一肩扛起實作 Clone() 內容細節的重責大任。 以遊戲中的角色系統架構為例，可以由角色的基底類別 Actor 扮演上述的 Prototype 角色，而分別衍生自 Actor 類別的 Player 與 Monster 類別則是屬於其中的 ConcretePrototype 角色。只要在 Actor 類別中宣告了 Clone() 介面，再經由 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://blog.monkeypotion.net/wp-content/uploads/2008/09/prototype-uml.jpg" alt="prototype-uml" title="prototype-uml" width="386" height="218" class="alignright size-full wp-image-536" />在物件導向<strong>生成設計模式</strong>的範疇中，除了之前曾經介紹過的 <a href="http://blog.monkeypotion.net/gameprog/pattern/factory-method"><strong>Facory Method</strong></a> 以及 <a href="http://blog.monkeypotion.net/gameprog/pattern/abstract-factory"><strong>Abstract Factory</strong></a> 兩項工廠類型的模式以外，本文將繼續介紹另一項在遊戲程式領域中，經常受到廣泛使用的 <strong>Prototype</strong> 設計模式。</p>
<p>Prototype，原意為原型或雛形，與工廠模式接收到使用者命令之後即時生產物件的方式不同，在 Prototype 模式中，我們需要先打造出物件的原型樣版，待鑄模程序完成以後，接下來就能夠輕易地以此原型複製產生出全新的物件。<strong>複製，也可以是一種生成物件的方法。</strong>如 UML 圖所示，在 Prototype 設計模式中，定義了三個參與角色：</p>
<blockquote>
<ul>
<li><strong>Prototype</strong>：宣告自我複製的介面。</li>
<li><strong>ConcretePrototype</strong>：具體實作出自我複製的操作。</li>
<li><strong>Client</strong>：叫原型個體自我複製一份，以生出新的物件。</li>
</ul>
</blockquote>
<p>按照上述的責任關係，程式系統的使用者 Client 只需要指涉到基礎的 Prototype 介面，就能夠以此 Prototype 介面所宣告的 Clone() 方法，複製出類別的物件成品；而 ConcretePrototype 們衍生自 Prototype 介面，是 Client 進行 Operation() 方法後將會獲得的實際成品，所以必須一肩扛起實作 Clone() 內容細節的重責大任。</p>
<p><span id="more-127"></span></p>
<p>以遊戲中的角色系統架構為例，可以由角色的基底類別 Actor 扮演上述的 Prototype 角色，而分別衍生自 Actor 類別的 Player 與 Monster 類別則是屬於其中的 ConcretePrototype 角色。只要在 Actor 類別中宣告了 Clone() 介面，再經由 Player 與 Monster 類別實作出 Clone() 方法的細節以後，在遊戲程式中就能夠輕鬆地使用 Actor 類別的 Clone() 介面，複製出所需的玩家物件與怪物物件：</p>
<pre name="code" class="cpp">
// 創建玩家原型
Player* playerPrototype = new Player();
// 以原型複製方法，產生玩家物件
Actor* playerCloned = playerPrototype->Clone();

// 創建怪物原型，並且設定原型的初始數值
Monster* monsterPrototype = new Monster();
monsterPrototype->SetHP(100);
monsterPrototype->SetMP(50);
monsterPrototype->SetExp(250);
// 以原型複製方法，產生怪物物件
Actor* monster1 = monsterPrototype->Clone();
Actor* monster2 = monsterPrototype->Clone();
Actor* monster3 = monsterPrototype->Clone();
</pre>
<p>Prototype 設計模式的基本結構與應用概念非常易於理解；然而，使用 Prototype 設計模式的困難之處，就在於<strong>如何實作複製方法</strong>。</p>
<p>首先，先重溫一下創造物件的其他方法。在物件導向程式設計中，若要達到生成物件的目標，除了以建構式傳入相關參數，或是使用預設建構式創造物件後再進行 Initialize() 或 Setup() 等初始程序以外，還有另外兩個常見的方法：使用<strong>複製建構式 (Copy Constructor)</strong> 或<strong>賦值運算子 (Assignment Operator)</strong>。</p>
<pre name="code" class="cpp">
// 以預設建構式建立 player1 物件
Player player1;
// 以複製建構式建立 player2 物件
Player player2(player1);
// 以賦值運算子將 player2 物件的內容複製給 player1 物件
player1 = player2;
</pre>
<p>以複製建構式與賦值運算子創建物件的方法，都是藉由已建立完成的物件進行成員資料的複製程序。但是一般來說，由於這兩項方法很容易被使用者誤用而且難以察覺，所以在遊戲程式中，除非有不得不使用的良好理由，否則我們會盡量<strong>避免使用複製建構式與賦值運算子</strong>的方法，而應該以明確的 Clone() 或者 Copy() 方法清楚標示出複製行為的操作方法，以避免造成誤用的可能性。另外，也可以將類別的複製建構式與賦值運算子<strong>宣告為私有成員</strong>，就能夠有效禁止這兩項方法的操作行為：</p>
<pre name="code" class="cpp">
class Player
{
public:
    Player();
    Player(int hp, int mp);

private:
    // 宣告為私有成員，禁止外部程式碼使用
    Player(const Player&#038; p);
    Player&#038; operator=(const Player&#038; p);
};
</pre>
<p>在談到複製機制時非提不可的議題，就是著名的<strong>淺層複製 (Shallow Copy)</strong> 與<strong>深層複製 (Deep Copy)</strong> 問題。簡單來說，淺層複製只會拷貝物件的參考，而深層複製才會完完整整地拷貝物件的一切內容物。對於遊戲系統的應用面來說，如果只有淺層複製機制通常並不足夠；舉個例子來說，如果在 Monster 類別中，內含了一個 Transform 類別用來處理怪物的位移、旋轉與縮放功能，然後我們使用了淺層複製的方式實作 Monster 類別的 Clone() 方法：</p>
<pre name="code" class="cpp">
Actor* Monster::Clone()
{
    Monster* monster = new Monster();
    monster->SetHP(m_MaxHP);
    monster->SetMP(m_MaxMP);
    monster->SetExp(m_Exp);

    // 將物件指標傳入新建立的 monster 物件中
    monster->SetTransform(m_Transform);

    return monster;
}
</pre>
<p>如上所示，這樣的複製程序，就等於將所有複製出來的怪物物件與原來的怪物物件，全部指向同一個 Transform 物件，所以只要移動其中一隻怪物的位置，所有的怪物都會跟著位移到相同的位置上。正確的深層複製 Clone() 程序如下：</p>
<pre name="code" class="cpp">
Actor* Monster::Clone()
{
    Monster* monster = new Monster();
    monster->SetHP(m_MaxHP);
    monster->SetMP(m_MaxMP);
    monster->SetExp(m_Exp);

    // 創建新的 Transform 物件
    Transform* transform = new Transform();
    transform->SetPosition(m_Transform.GetPosition());
    transform->SetRotation(m_Transform.GetRotation());
    transform->SetScale(m_Transform.GetScale());
    // 新的 Monster 物件使用新的 Transform 物件
    monster->SetTransform(transform);

    return monster;
}
</pre>
<p>只要為類別物件實作出深層複製的 Clone() 方法，就能夠建立出一整組的 Prototype 物件，接下來就可以使用一個 PrototypeManager 類別來管理這些 Prototype 物件。以管理特效原型物件的類別為例：</p>
<pre name="code" class="cpp">
// @file EffectPrototypeManager.h
#include "Dictionary.h"

class EffectPrototypeManager
{
public:
    Effect* CreateEffect(std::string&#038; fileName)
    {
        Effect* effect = NULL;

        // 先在 Prototypes Pool 中尋找該特效的原型是否存在
        if (!m_PrototypesPool->GetAt(fileName, effect)) {
            // 若不存在，則創建新的特效原型
            effect = new Effect(fileName);
            // 將原型物件存入 Prototypes Pool 中
            m_PrototypesPool->SetAt(fileName, effect);
        }

        // 以原型物件複製產生新的特效物件
        return effect->Clone();
    }

private:
    Dictionary< std::string, Effect*, true >* m_PrototypesPool;
};
</pre>
<p><strong>在遊戲引擎以及繪圖引擎中，Prototype 設計模式具有相當重要的地位</strong>。舉例來說，假設遊戲中的每個技能特效物件，都儲存在個別的實體檔案中，當遊戲需要產生出 10 個相同檔案名稱的特效物件時，是不是表示系統也需要讀取相同的檔案 10 次之多？如果在程式系統中，能夠先建立起這些特效物件的原型，就不需要重複進行開啟檔案與讀取檔案的程序，只要執行一次 I/O 程序，後續的物件就可以全部交由 CPU 進行複製操作。另外，還能夠藉此機制實作出預先載入遊戲資源的功能。</p>
<p>在 Java 與 C# 語言中，都存在著 <a href="http://msdn.microsoft.com/en-us/library/system.icloneable.aspx">ICloneable</a> 介面類別，使用者能夠繼承此介面以實作出合適的可複製類別 (Cloneable Class)。但是在 C++ 語言中，並沒有提供類似 ICloneable 介面的機制，因此以 C++ 語言撰寫的遊戲引擎與繪圖引擎，就必須自行打造出適當的 Clone 架構與機制。以 OGRE 繪圖引擎為例，OGRE 目前並沒有符合 Prototype 設計模式的架構與介面，僅有少部分的類別如 Animation、Entity、Material 與 Mesh 等等擁有複製方法；其中，Entity 類別的 clone() 程序如下所示：</p>
<pre name="code" class="cpp">
// @file OgreEntity.cpp

Entity* Entity::clone(const String&#038; newName) const
{
    Entity* newEnt = mManager->createEntity(newName, getMesh()->getName());

	if (mInitialised) {
		// Copy material settings
		SubEntityList::const_iterator i;
		unsigned int n = 0;
		for (i = mSubEntityList.begin(); i != mSubEntityList.end(); ++i, ++n) {
			newEnt->getSubEntity(n)->setMaterialName((*i)->getMaterialName());
		}

		if (mAnimationState) {
			delete newEnt->mAnimationState;
			newEnt->mAnimationState = new AnimationStateSet(*mAnimationState);
		}
	}

    return newEnt;
}
</pre>
<p>在之前<a href="http://blog.monkeypotion.net/gameprog/note/ogre-dev-intro"><strong>介紹 OGRE 引擎的文章</strong></a>裡，曾經提到關於<strong>資源物件名稱唯一性</strong>的問題，所以在 OGRE 中複製物件時，必須傳入一個全新的物件名稱做為複製方法的參數值。如上述程式碼所示，OGRE 的 Entity 類別複製方法是先由 SceneManager 建立出全新的 Entity 物件後，再逐一拷貝 Material 與 AnimationState 物件的設定值。雖然 Entity 類別有提供 clone() 方法，但很可惜的是，更為關鍵重要的 Node 與 SceneNode 類別都沒有提供相對應的 clone() 方法，對於以建構大型遊戲系統為目標的程式設計者來說，將會造成許多資源管理層面的困擾。</p>
<p>在如同 OGRE 般使用 <a href="http://en.wikipedia.org/wiki/Scene_graph">Scene Graph</a> 架構的遊戲引擎或者繪圖引擎裡，大多數遊戲中的物件，都是以 Node 與 Entity 物件層層堆疊連結所建立而成的一棵「樹」。所以對於 Scene Graph 架構的引擎來說，如果要達到深層複製的功能，必須要能夠<strong>複製某個 Node 物件底下的整個結構樹</strong>；也就是說，除了複製物件本體以外，還需要複製物件的<strong>連結性</strong>。因此，想要實作出完整的 Clone() 方法，往往不是只用 memcpy() 複製某一區塊的記憶體就能夠單純解決的問題，而必須以遞迴的方式層層向下複製物件的結構與連結關係。</p>
<p>Prototype，就是這樣一個概念簡單而實作細節令人費盡思量的設計模式。如果程式設計者能夠善加利用 Prototype 模式，並且實作出合適的物件複製方法，將能夠為遊戲引擎與遊戲系統帶來很大的助益！</p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=127&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/pattern/prototype-pattern/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Abstract Factory：物件家族的抽象工廠</title>
		<link>http://blog.monkeypotion.net/gameprog/pattern/abstract-factory</link>
		<comments>http://blog.monkeypotion.net/gameprog/pattern/abstract-factory#comments</comments>
		<pubDate>Mon, 24 Mar 2008 16:10:20 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[設計模式]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/pattern/abstract-factory</guid>
		<description><![CDATA[本篇文章將承續前篇「Factory Method：工廠化的物件生產方法」，介紹經常與 Factory Method 模式搭配使用的 Abstract Factory 設計模式，以及 Abstract Factory 模式於遊戲程式中的應用實例。 在複雜的軟體系統架構中，將原來雜亂無章的組件加以分門別類，然後從中取出性質相似與功能相近的物件，使其集中群聚，就能夠大幅地簡化程式系統組件的複雜度。而 Abstract Factory 模式，正是用來協助程式設計者將物件分門別類與集中管理的好方法。 Abstract Factory 的「工廠」，與 Factory Method 的「工廠」目的相同，同樣屬於「生成模式」的分類，都是用來產生出物件成品的製造者。對於設計模式的入門者來說，很容易將 Factory Method 模式與 Abstract Factory 模式兩者的使用目的以及使用時機搞混；雖然都是屬於工廠類型的作業模式，然而 Abstract Factory 與 Factory Method 的不同之處在於，抽象工廠模式是將一組性質相關或相依的物件，放在同一個工廠裡生產。例如對於食品工廠來說，能夠生產的包含飲料、零食與泡麵三項食品類的產品；而皮件工廠，則能夠生產出皮包、皮帶與皮鞋等皮製產品。也就是在一個工廠之內有數個不同的生產線，能夠同時進行不同成品的生產作業。在實際程式系統的應用中，可以說 Abstract Factory 經常是一堆 Factory Method 的組合。 以 Abstract Factory 設計模式的 UML 結構圖來看，根據《物件導向設計模式》(Design Patterns) 書中所述，其中定義了參與抽象工廠模式的幾個角色： AbstractFactory：此介面宣告出可生成各抽象成品物件的操作。 ConcreteFactory：具體實作出可建構具象成品物件的操作。 AbstractProduct：宣告某成品物件類型之介面。 ConcreteProduct：是 ConcreteFactory 所建構的成品物件；也是 AbstractFactory 介面的具象實作。 [...]]]></description>
			<content:encoded><![CDATA[<p><img src='http://blog.monkeypotion.net/wp-content/uploads/2008/03/abstract-factory-uml.jpg' alt='Abstract Factory UML' class='alignright'/>本篇文章將承續前篇<a href="http://blog.monkeypotion.net/gameprog/pattern/factory-method"><strong>「Factory Method：工廠化的物件生產方法」</strong></a>，介紹經常與 Factory Method 模式搭配使用的 <a href="http://en.wikipedia.org/wiki/Abstract_Factory"><strong>Abstract Factory</strong></a> 設計模式，以及 Abstract Factory 模式於遊戲程式中的應用實例。</p>
<p>在複雜的軟體系統架構中，將原來雜亂無章的組件加以分門別類，然後從中取出性質相似與功能相近的物件，使其集中群聚，就能夠大幅地簡化程式系統組件的複雜度。而 Abstract Factory 模式，正是用來協助程式設計者將物件分門別類與集中管理的好方法。</p>
<p>Abstract Factory 的<strong>「工廠」</strong>，與 Factory Method 的「工廠」目的相同，同樣屬於<strong>「生成模式」</strong>的分類，都是用來產生出物件成品的製造者。對於設計模式的入門者來說，很容易將 Factory Method 模式與 Abstract Factory 模式兩者的使用目的以及使用時機搞混；雖然都是屬於工廠類型的作業模式，然而 Abstract Factory 與 Factory Method 的不同之處在於，<strong>抽象工廠模式是將一組性質相關或相依的物件，放在同一個工廠裡生產。</strong>例如對於食品工廠來說，能夠生產的包含飲料、零食與泡麵三項食品類的產品；而皮件工廠，則能夠生產出皮包、皮帶與皮鞋等皮製產品。也就是在一個工廠之內有數個不同的生產線，能夠同時進行不同成品的生產作業。在實際程式系統的應用中，可以說 <strong>Abstract Factory 經常是一堆 Factory Method 的組合</strong>。</p>
<p><span id="more-77"></span></p>
<p>以 Abstract Factory 設計模式的 UML 結構圖來看，根據<a href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9572054112&#038;sid=10828"><strong>《物件導向設計模式》</strong></a>(Design Patterns) 書中所述，其中定義了參與抽象工廠模式的幾個角色：</p>
<blockquote>
<ul>
<li><strong>AbstractFactory</strong>：此介面宣告出可生成各抽象成品物件的操作。</li>
<li><strong>ConcreteFactory</strong>：具體實作出可建構具象成品物件的操作。</li>
<li><strong>AbstractProduct</strong>：宣告某成品物件類型之介面。</li>
<li><strong>ConcreteProduct</strong>：是 <strong>ConcreteFactory</strong> 所建構的成品物件；也是 <strong>AbstractFactory</strong> 介面的具象實作。</li>
<li><strong>Client</strong>：只觸及 <strong>AbstractFactory</strong> 和 <strong>AbstractProduct</strong> 兩抽象類別所訂之介面。</li>
</ul>
</blockquote>
<p>與 Factory Method 的結構相似，首先由 AbstractFactory 宣告出創建物件的抽象介面，然後再由繼承而來的子類別 ConcreteFactory 具體實作出對應於各實體成品的操作細節。AbstractProduct 與 ConcreteProduct 之間的關係亦然類似；AbstractProduct 屬於抽象基底產品，而 ConcreteProduct 是衍生而來的實體產品。對於系統外部的使用者 Client 來說，無須瞭解 ConcreteFactory 與 ConcreteProduct 的實作細節，僅需接觸 AbstractFactory 與 AbstractProduct 兩者的介面，就能夠產生出相對應的實體成品。</p>
<p>以常見的遊戲程式情境為例，假設在我們的 MMO 遊戲世界中，存在著三種不同類型的生物體，分別是玩家角色、怪物角色，以及非玩家角色。做為一位聰明的遊戲程式設計者，很自然地能夠將這三個類別的共同概念，抽取出來成為一個基礎的 Actor 父類別。有了 Actor 類別定義一般生物體的基礎屬性與行為後，就能分別衍生出遊戲生物體的三個子類別：玩家角色的 Player 類別、怪物角色的 Monster 類別，以及非玩家角色的 NPC 類別。</p>
<p>在 MMO 遊戲中，Client 端程式需要接收 Server 端程式的指令，才能夠在玩家的電腦中呈現出遊戲的互動世界。所以在 Client 端接收到 Server 端傳送來的指令，命令 Client 端程式要在遊戲世界的某個位置產生某種生物體時，可以這麼寫：</p>
<pre name="code" class="cpp">
// @file NetworkProtocol.cpp
#include "Player.h"
#include "Monster.h"
#include "NPC.h"

void NetworkProtocol::CreateActor(int _iType, Vector3 _tPos)
{
    Actor* pkActor = NULL;

    if (_iType == 1) {
        pkActor = new Player(_tPos);
    }
    else if (_iType == 2) {
        pkActor = new Monster(_tPos);
    }
    else if (_iType == 3) {
        pkActor = new NPC(_tPos);
    }
    else {
        // Unknown type, Error!
    }

    // Process actor object
}
</pre>
<p>NetworkProtocol 是用來接受網路命令的類別，其中的 CreateActor() 成員函式專門處理遊戲角色物件的生成程序。在此以一個整數型別數值取決要創建的生物體是玩家、怪物或非玩家角色，是最簡單而直覺的實作方法。</p>
<p>然而如果只是達到這樣的程度，絕對無法令求知若渴的程式設計者得到滿足感。所以為了消除 if 敘述句中使用的萬惡 Magic Number，可以進一步利用 Enumeration 的宣告與 switch 敘述句將程式結構變得更加清楚易懂：</p>
<pre name="code" class="cpp">
// @file NetworkProtocol.cpp
#include "Player.h"
#include "Monster.h"
#include "NPC.h"

void NetworkProtocol::CreateActor(int _iType, Vector3 _tPos)
{
    enum ActorType
    {
        Actor_Null = 0,
        Actor_Player = 1,
        Actor_Monster = 2,
        Actor_NPC = 3,
    };

    Actor* pkActor = NULL;

    switch (_iType) {
        case Actor_Player: {
            pkActor = new Player(_tPos);
            break;
        }
        case Actor_Monster: {
            pkActor = new Monster(_tPos);
            break;
        }
        case Actor_NPC: {
            pkActor = new NPC(_tPos);
            break;
        }
        default: {
            // Unknown type, Error!
        }
    }

    // Process actor object
}
</pre>
<p>啊哈！這樣的程式結構是不是看起來清楚許多了？未來如果要加入新類型的生物體，只需要新增一個列舉值，然後在 switch 敘述句中增加對應的 case 結構即可，程式區塊的修改方式也變得更加方便而直覺許多。</p>
<p>然而，即使經過上述的改善程序，已經變得比較易於進行修改與新增的動作，對於這個函式中所產生的 Player、Monster 與 NPC 物件仍然不便於進行集中管理。如果在其他類別中，也需要依參數創建出不同的生物體時，是不是需要把上述的程式碼，照抄一份複製到新的類別成員函式中？萬一不幸又有第三個類別需要進行創建生物體的程序時，就再來一份？如此的情況之下，原來顯而易見的程式結構修改，已經逐漸失去控制了。</p>
<p>利用 Abstract Factory 模式，我們有更好的設計模式與實作方法。在此創建出一個新的 <strong>ActorFactory</strong> 類別，將產生角色物件程序的實做細節隱藏起來，用以產生各種角色物件：</p>
<pre name="code" class="cpp">
// @file ActorFactory.h
class Actor;
class Player;
class Monster;
class NPC;

class ActorFactory
{
public:
    ActorFactory();
    virtual ~ActorFactory();

    virtual Player* CreatePlayer(Vector3 _tPos);
    virtual Monster* CreateMonster(Vector3 _tPos);
    virtual NPC* CreateNPC(Vector3 _tPos);
};
</pre>
<p>將這些相關性很高的類別聚集在同一個工廠中，既能夠達到集中管理的目的，也能夠使未來的系統擴充性更加具有彈性。所以在 ActorFactory 類別的定義中，就能夠對這些物件進行創建的程序：</p>
<pre name="code" class="cpp">
// @file ActorFactory.cpp
#include "Player.h"
#include "Monster.h"
#include "NPC.h"

Actor* ActorFactory::CreatePlayer(Vector3 _tPos)
{
    return new Player(_tPos);
}

Actor* ActorFactory::CreateMonster(Vector3 _tPos)
{
    return new Monster(_tPos);
}

Actor* ActorFactory::CreateNPC(Vector3 _tPos)
{
    return new NPC(_tPos);
}
</pre>
<p>需要 Player 物件？沒問題！交給 CreatePlayer() 窗口就對了。需要 Monster 物件呢？那就交給 CreateMonster() 負責。如果是 NPC 物件呢？當然就是屬於 CreateNPC() 的管轄範圍囉。此後不論在程式系統中，有多少不同的類別需要使用創建生物體的程序，都只要向唯一一個 ActorFactory 類別提出要求就可以了！</p>
<p>只要有一項產品，就有相對應的一項操作方法；<strong>產品與操作，存在著一對一的對應關係。</strong>是不是覺得這個概念有些熟悉？如本文前言所述，在抽象工廠模式中，最常見的做法就是為每一項產品分別定義一個 Factory Method。這裡的程式架構就是利用了這樣的做法，使同一個工廠之中，能夠生產出性質相近但是實體不相同的產品。</p>
<p>更進一步，如果在遊戲系統中，人類與非人類型態的角色，存在著非常大的差異性，並不適合集中於同一個工廠進行管理，就可以將 ActorFactory 類別抽象化，形成一個基底的抽象類別，然後分別衍生出處理人類角色的 HumanActorFactory 工廠類別，以及處理非人類角色的 NonHumanActorFactory 工廠類別：</p>
<pre name="code" class="cpp">
// @file HumanActorFactory.h
#include "ActorFactory.h"

class HumanActorFactory : public ActorFactory
{
public:
    HumanActorFactory();
    virtual ~HumanActorFactory();

    virtual Player* CreatePlayer(Vector3 _tPos);
    virtual NPC* CreateNPC(Vector3 _tPos);
};

// @file NonHumanActorFactory.h
#include "ActorFactory.h"

class NonHumanActorFactory : public ActorFactory
{
public:
    NonHumanActorFactory();
    virtual ~NonHumanActorFactory();

    virtual Monster* CreateMonster(Vector3 _tPos);
};
</pre>
<p>於是每次要新增一種新的生物體角色，只要在 ActorFactory 類別中新增一個 CreateXXX() 的虛擬成員函式，然後在對應的實作工廠中定義產品的實作生產細節，就能在妥善分類與集中管理的情形下，得到良好的物件生成系統架構。</p>
<p>為了管理這些物件，非常合適於使用<a href="http://blog.monkeypotion.net/gameprog/advanced/container-int2type-improvement"><strong>改良版的 STL 容器</strong></a>；以 HumanActorFactory 類別為例，可以在其中新增兩個 Dictionary 用來存放與索引 Player 及 NPC 的物件指標：</p>
<pre name="code" class="cpp">
// @file HumanActorFactory.h
#include "ActorFactory.h"
#include "Dictionary.h"

class HumanActorFactory : public ActorFactory
{
public:
    HumanActorFactory();
    virtual ~HumanActorFactory();

    virtual Player* CreatePlayer(Vector3 _tPos);
    virtual NPC* CreateNPC(Vector3 _tPos);

private:
    Dictionary< int, Actor*, TRUE >* m_pkPlayersPool;
    Dictionary< int, Actor*, TRUE >* m_pkNPCsPool;
};
</pre>
<p>至此，就完成了一套以抽象工廠管理物件家族的系統架構了。</p>
<p>而除了上述每一種物件都有一個相對應的函式 CreateXXX() 的做法，Abstract Factory 模式的另一種實作方法，是僅定義唯一的一個函式介面，然後藉由使用者傳入的整數數值或字串數值，用以決定所要創造的產品。以 HumanActorFactory 類別為例，原有的實作方式可以修改成：</p>
<pre name="code" class="cpp">
// @file HumanActorFactory .h
#include "ActorFactory.h"

class HumanActorFactory : public ActorFactory
{
public:
    HumanActorFactory();
    virtual ~HumanActorFactory();

    virtual Actor* CreateActor(int _iType, Vector3 _tPos);
};

// @file HumanActorFactory.cpp
#include "HumanActorFactory.h"

Actor* HumanActorFactory::CreateActor(int _iType, Vector3 _tPos)
{
    Actor* pkActor = NULL;

    switch (_iType) {
        case Actor_Player: {
            pkActor = new Player(_tPos);
            break;
        }
        case Actor_NPC: {
            pkActor = new NPC(_tPos);
            break;
        }
        default: {
            // Error!
        }
    }

    return pkActor;
}
</pre>
<p>使用上述這個實作方法的優點在於，工廠的抽象類別與實體類別不必隨著新增的產品需求，而需要不斷地增加相對應的成員函式 CreateXXX()，僅需要唯一的一個 CreateActor() 成員函式，藉由傳入一個參數值指定出要創造的物件類型，而能夠達到更便利的工廠擴充性。</p>
<p>然而如果使用這個方法來實作 Abstract Factory 的話，CreateActor() 函式需要嚴格檢查傳入的參數值，確保萬一傳入不明的值也不會產生出例外的錯誤狀況。另外的問題，在於 CreateActor() 函式不論創建了什麼種類的物件，回傳值都是基底抽象類別 Actor，系統使用者需要自行將這個值進行向下轉型 (Downcast) 以進行後續的操作處理，需要注意轉型時可能發生的錯誤情形。</p>
<p>根據<strong>《物件導向設計模式》</strong>(Design Patterns) 書中所述，在程式架構中使用 Abstract Factory 模式的效果如下：</p>
<blockquote>
<ul>
<li>將具象類別隔離開來。</li>
<li>易於將整族成品物件抽換掉。</li>
<li>增進成品物件的一致性。</li>
</ul>
</blockquote>
<p>回頭檢視上述的程式範例，是不是能夠瞭解使用 Abstract Factory 模式所達成的這三項效果了？</p>
<p>再舉一個例子，回到前篇 Factory Method 的文章中所提到的<strong>創建貼圖</strong>範例，如果<strong>使用 Abstract Factory 模式加以延伸</strong>，甚至能夠將所有相關於繪圖渲染程序的物件，例如：Texture、Light、Material、Camera、Font 等等物件，全部聚集在 GraphicsRenderer 類別中；</p>
<pre name="code" class="cpp">
class GraphicsRenderer
{
public:
    GraphicsRenderer();
    virtual ~GraphicsRenderer();

    Texture* CreateTexture();
    Light* CreateLight();
    Material* CreateMaterial();
};
</pre>
<p><img src='http://blog.monkeypotion.net/wp-content/uploads/2008/03/abstract-factory-applied-example.jpg' alt='Abstract Factory Applied Example' class='alignleft'/>由上述類別架構所產生的 UML 結構如圖所示。對程式系統撰寫者來說，在這樣的系統架構之下，就能夠簡化這群高度相依物件的管理與生滅關係；而對於程式系統的使用者來說，也能夠很清楚地意識到：<strong>如果我需要創建與繪圖程序相關的物件，找 GraphicsRenderer 這位負責人就一定不會錯了！</strong></p>
<p>至此，結合<a href="http://blog.monkeypotion.net/gameprog/pattern/factory-method"><strong>「Factory Method：工廠化的物件生產方法」</strong></a>與本篇文章中的知識，就能夠習得生成設計模式中，兩大工廠的基本概念與應用。</p>
<p>對於 Factory 相關的設計模式，還有許多進階的延伸設計模式與變形實作方法；例如很直覺地能夠將上述的 HumanActorFactory 與 NonHumanActorFactory 類別實作成為 <a href="http://en.wikipedia.org/wiki/Singleton_pattern"><strong>Singleton</strong></a> 模式以利使用。另外像是 <strong>Manager Pattern</strong> 之類的設計模式，把物件家族全權交由一位<strong>專業經理人</strong>打點系統內部的管理與外部的溝通行為，也是很常見的作法。</p>
<p>程式碼範例下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=5"><strong>AbstractFactory_SampleCode.zip</strong></a> <small>(下載次數： 538 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=77&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/pattern/abstract-factory/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Factory Method：工廠化的物件生產方法</title>
		<link>http://blog.monkeypotion.net/gameprog/pattern/factory-method</link>
		<comments>http://blog.monkeypotion.net/gameprog/pattern/factory-method#comments</comments>
		<pubDate>Sat, 15 Mar 2008 03:11:04 +0000</pubDate>
		<dc:creator>半路</dc:creator>
				<category><![CDATA[設計模式]]></category>

		<guid isPermaLink="false">http://blog.monkeypotion.net/gameprog/pattern/factory-method</guid>
		<description><![CDATA[本篇文章將介紹在軟體設計模式 (Design Patterns) 中相當著名而且受到廣泛使用的 Factory Method 模式，以及 Factory Method 在遊戲程式設計難題中的實際應用。 想要熟悉 Factory Method 設計模式，首先要瞭解下列幾項問題： 這個設計模式的目的是什麼？ 這個設計模式能夠用來解決什麼樣的問題？ 這個設計模式要如何應用在遊戲程式之中？ 或許對於程式設計者來說，十個人之中有九個半討厭聽到「工廠」或「工廠化管理」的相關術語，但是在軟體設計模式的領域中，工廠化的生產作業方式反而是相當實際而且管用的方法。Factory Method 是軟體領域中，非常基礎同時也非常重要的設計模式之一，或許你從來沒有在書本中學習過相關的知識，但是已經在撰寫程式的經驗過程中，處處使用著這樣的設計方法而不自知。如果能夠進一步瞭解這些前人思維架構的心血結晶，將有助於改善現有的程式架構，並且提升未來撰寫程式系統架構的能力。 在 Factory Method 中的「工廠」，意指製造具體「成品」的場所，也就是在程式中創建出具體物件的創造者。所以 Factory Method 在設計模式的分類中屬於「生成模式」的一種，目的是將物件的具現化過程加以抽象化而提取出來，用來處理與物件相關的創建行為與毀滅行為。根據《物件導向設計模式》(Design Patterns) 書中 Factory Method 的 UML 結構圖描述，定義了參與工廠方法模式的四個角色： Product：定義 Factory Method 所造物件的介面。 ConcreteProduct：具體實作出 Product 介面。 Creator：宣告 Factory Method，它會傳回 Product 型別之物件。 ConcreteCreator：覆寫 Factory Method 以傳回 ConcreteProduct 的物件個體。 這裡的 Creator 扮演著抽象化工廠的角色，僅宣告出可供衍生類別覆寫的成員函式；而真正要製作出實體成品的實做細節，則是交由繼承自 [...]]]></description>
			<content:encoded><![CDATA[<p><img alt="Factory Method UML" src="http://blog.monkeypotion.net/wp-content/uploads/2008/03/factory-method-uml.jpg" class='alignright'/> 本篇文章將介紹在軟體<strong>設計模式 (Design Patterns)</strong> 中相當著名而且受到廣泛使用的 <a href="http://en.wikipedia.org/wiki/Factory_method_pattern"><strong>Factory Method</strong></a> 模式，以及 Factory Method 在遊戲程式設計難題中的實際應用。</p>
<p>想要熟悉 Factory Method 設計模式，首先要瞭解下列幾項問題：</p>
<ul>
<li>這個設計模式的目的是什麼？ </li>
<li>這個設計模式能夠用來解決什麼樣的問題？ </li>
<li>這個設計模式要如何應用在遊戲程式之中？ </li>
</ul>
<p>或許對於程式設計者來說，十個人之中有九個半討厭聽到「工廠」或「工廠化管理」的相關術語，但是在軟體設計模式的領域中，<strong>工廠化的生產作業方式</strong>反而是相當實際而且管用的方法。Factory Method 是軟體領域中，非常基礎同時也非常重要的設計模式之一，或許你從來沒有在書本中學習過相關的知識，但是已經在撰寫程式的經驗過程中，處處使用著這樣的設計方法而不自知。如果能夠進一步瞭解這些前人思維架構的心血結晶，將有助於改善現有的程式架構，並且提升未來撰寫程式系統架構的能力。</p>
<p><span id="more-76"></span></p>
<p>在 Factory Method 中的<strong>「工廠」</strong>，意指製造具體<strong>「成品」</strong>的場所，也就是在程式中創建出具體物件的創造者。所以 Factory Method 在設計模式的分類中屬於<strong>「生成模式」</strong>的一種，目的是將物件的具現化過程加以抽象化而提取出來，用來處理與物件相關的創建行為與毀滅行為。根據<a href="http://tlsj.tenlong.com.tw/WebModule/BookSearch/bookSearchViewAction.do?isbn=9572054112&amp;sid=10828"><strong>《物件導向設計模式》</strong></a>(Design Patterns) 書中 Factory Method 的 UML 結構圖描述，定義了參與工廠方法模式的四個角色： </p>
<blockquote>
<ul>
<li><strong>Product</strong>：定義 Factory Method 所造物件的介面。 </li>
<li><strong>ConcreteProduct</strong>：具體實作出 <strong>Product</strong> 介面。 </li>
<li><strong>Creator</strong>：宣告 Factory Method，它會傳回 <strong>Product</strong> 型別之物件。 </li>
<li><strong>ConcreteCreator</strong>：覆寫 Factory Method 以傳回 <strong>ConcreteProduct</strong> 的物件個體。 </li>
</ul>
</blockquote>
<p>這裡的 Creator 扮演著抽象化工廠的角色，僅宣告出可供衍生類別覆寫的成員函式；而真正要製作出實體成品的實做細節，則是交由繼承自 Creator 的實作工廠 ConcreteCreator 所定義。如 UML 結構圖所示，在 ConcreteCreator 中，就可以指涉並且生成真正的實體成品 ConcreteProduct。就像是鞋子工廠只能製造鞋子，帽子工廠只能製造帽子，在工廠方法模式中，通常是一個「創造者」搭配一項「產品」，所以「實體工廠」與「實體產品」的類別定義，經常會以成對的模式出現在程式系統之中。</p>
<p>以 3D 程式設計中的<strong>創建貼圖 (Create Texture)</strong> 程序為例，由於貼圖生成動作是與繪圖渲染器 (Renderer) 高度相依的行為，所以在一般的情況之下，程式設計者多半會選擇將貼圖相關的程序包含在 Renderer 類別的定義中；假設我們使用 Direct3D 的繪圖 API 進行從檔案創建貼圖的程序，程式碼應該會像是： </p>
<pre name="code" class="cpp">// Direct3D version
void GraphicsRenderer::CreateTexture(std::string&amp; _rkTextureName)
{
    D3DXCreateTextureFromFile(m_pD3DDevice, _rkTextureName.c_str(), m_ppkTexture);
}</pre>
<p>GraphicsRenderer 是繪圖渲染器的處理類別，在 CreateTexture() 函式中，直接使用 D3DX Library 的輔助函式 D3DXCreateTextureFromFile() 就能夠順利地讀取檔案並且建立貼圖。這是看起來沒有任何問題的程式碼。</p>
<p>然而，如果在某些不可抗力的情況之下，需要將原本使用的繪圖 API 由 Direct3D 轉換至 OpenGL 時，該怎麼做？</p>
<p>不管是因為進行全新的第二個專案，還是因為換了 OpenGL 派的程式主管，甚至是因為微軟突然間被惡性併購倒閉。要換成 OpenGL 的繪圖程序？很簡單，只要把相關的貼圖處理程式碼置換掉就沒問題了： </p>
<pre name="code" class="cpp">// OpenGL version
void GraphicsRenderer::CreateTexture(std::string&amp; _rkTextureName)
{
    m_pvImageData = LoadImage(_rkTextureName);
    glGenTextures(1, &amp;m_iTextureID);
}</pre>
<p>OK，同樣沒有任何問題。由於你的傑出表現，前面的兩個遊戲專案都相當地成功而且賣座，到了第三個專案，終於有足夠的經費可以購買 3D 引擎來開發遊戲了！</p>
<p>要如何能夠把原有的程式碼，轉換成適用外部 3D 引擎的用法呢？同樣地，把之前 OpenGL 相關的部分刪除，新增外部 3D 引擎的 API 即可，函式介面完全沒變： </p>
<pre name="code" class="cpp">// SuperEngine version
void GraphicsRenderer::CreateTexture(std::string&amp; _rkTextureName)
{
    SuperEngine::LoadImage(_rkTextureName);
    SuperEngine::CreateTexture(_rkTextureName);
}</pre>
<p>看起來一切正常。然而，在上述的情境中，雖然保全了<strong>函式介面</strong>的不變性與重複利用性 (Reusablility)，但每次更換繪圖 API 時，程式設計者卻不得不在新舊的程式碼的<strong>函式實作版本</strong>間，<strong>使用 Ctrl-C 與 Ctrl-V 進行大量的複製、貼上、刪除與修改的動作</strong>。以上述的程式碼範例來看，複製貼上且進行必要的修改並不是什麼困難的事情，但是當專案的規模成長到某種程度，例如數百個程式碼檔案、數萬行程式碼的時候，往往會令程式設計者望而生畏。與其大興土木地從舊有的程式碼中挖牆修補，許多程式設計者最後會選擇將程式架構整個打掉重建，而很遺憾地<strong>無法達到程式碼的重複利用性</strong>。</p>
<p>在此如果運用 Factory Method 設計模式，就能夠妥善並且優雅地解決這個貼圖物件生成模式的難題。根據<strong>《物件導向設計模式》</strong>所述，Factory Method 的<strong>使用時機</strong>有下列三項： </p>
<blockquote>
<ul>
<li>當類別無法明指欲生成的物件類別時。 </li>
<li>當類別希望讓子類別去指定欲生成的物件類型時。 </li>
<li>當類別將權力下放給一個或多個輔助用途的子類別，你又希望將「交付給哪些子類別」的知識集中在一處時。 </li>
</ul>
</blockquote>
<p>在這個例子中，我們需要產生出不同繪圖 API 實作版本的工廠類別，以及與工廠相對應的實體產品類別，因此相當符合第二項的使用時機。在新的程式架構中，首先需要將創建貼圖的<strong>程序導向化設計</strong>，改變成<strong>物件導向化的設計</strong>，也就是將所有與貼圖相關的屬性和操作封裝成為一個 <strong>Texture</strong> 類別。在此 GraphicsRenderer 所扮演的是 Factory Method 中的<strong>創造者</strong>角色，而 Texture 就是由 GraphicsRenderer 所創建出來的<strong>產品</strong>。 </p>
<pre name="code" class="cpp">// @file Texture.h
class Texture
{
public:
    Texture();
    virtual ~Texture();

    virtual void Load(std::string&amp; _rkFileName);
    std::string GetFileName() const;

protected:
    std::string m_kFileName;
};</pre>
<p>基本上，由於 Texture 扮演的是抽象化的角色，所以類別的內容非常簡單，除了記錄檔案名稱的屬性之外，最重要的是宣告用來讀取檔案的虛擬成員函式 Load()。</p>
<p>接著是 GraphicsRenderer 類別，對於創建貼圖的操作也非常簡單，只要接受一個字串參數，然後回傳創建完成的 Texture 物件指標即可： </p>
<pre name="code" class="cpp">// @file GraphicsRenderer.h
class Texture;

class GraphicsRenderer
{
public:
    GraphicsRenderer();
    virtual ~GraphicsRenderer();

    virtual Texture* CreateTexture(std::string&amp; _rkTextureName);
}

// @file GraphicsRenderer.cpp
#include &quot;Texture.h&quot;
Texture* GraphicsRenderer::CreateTexture(std::string&amp; _rkTextureName)
{
    return NULL;
}</pre>
<p>按照 Factory Method 的 UML 結構圖所示，有了抽象化的 Product 類別與 Creator 類別之後，就可以開始依實作需求創造出不同版本的 ConcreteProduct 類別與 ConcreteCreator 了。以 Direct3D 的實作版本為例，把與 Direct3D 相關的結構都存放在這個新建的 <strong>D3DTexture</strong> 類別中： </p>
<pre name="code" class="cpp">// @file D3DTexture.h
class D3DTexture : public Texture
{
public:
    D3DTexture();
    virtual ~D3DTexture();

    virtual void Load(std::string&amp; _rkFileName);

private:
    LPDIRECT3DTEXTURE9* m_ppkTexture;
};

// @file D3DTexture.cpp
void D3DTexture::Load(std::string&amp; _rkFileName)
{
    D3DXCreateTextureFromFile(m_pD3DDevice, _rkFileName.c_str(), m_ppkTexture);
}</pre>
<p>Direct3D 版本的渲染器類別 <strong>D3DGraphicsRenderer</strong> 繼承自抽象化的 GraphicsRenderer 類別，定義如下： </p>
<pre name="code" class="cpp">// @file D3DGraphicsRenderer.h
class D3DGraphicsRenderer : public GraphicsRenderer
{
public:
    D3DGraphicsRenderer ();
    virtual ~D3DGraphicsRenderer ();

    virtual Texture* CreateTexture(std::string&amp; _rkFileName);
}

// @file D3DGraphicsRenderer.cpp
#include &quot;D3DTexture.h&quot;

Texture* D3DGraphicsRenderer::CreateTexture(std::string&amp; _rkFileName)
{
    return new D3DTexture(_rkFileName);
}</pre>
<p>有了抽象化的 Texture 類別與 GraphicsRenderer 類別，再衍生出 D3DTexture 類別與 D3DGraphicsRenderer 類別之後，就完成了一組 Factory Method 的類別組成架構。可以開始輕鬆簡單地生產 Direct3D 版本的 Texture 物件了： </p>
<pre name="code" class="cpp">
// @file FactoryMethod.cpp
void main()
{
    // Create D3D renderer
    GraphicsRenderer* pkD3DRenderer= new D3DGraphicsRenderer();

    // Create textures
    std::string kFileName1 = "123.jpg";
    Texture* pkTex1 = pkD3DRenderer->CreateTexture(kFileName1);
    std::string kFileName2 = "abc.png";
    Texture* pkTex2 = pkD3DRenderer->CreateTexture(kFileName2);
    std::string kFileName3 = "tree.bmp";
    Texture* pkTex3 = pkD3DRenderer->CreateTexture(kFileName3);

    // Destroy textures
    pkD3DRenderer->DestroyTexture(pkTex3);
    pkD3DRenderer->DestroyTexture(pkTex2);
    pkD3DRenderer->DestroyTexture(pkTex1);

    // Destroy D3D renderer
    delete pkD3DRenderer;
}</pre>
<p>如果要增加 OpenGL 版本的創建貼圖程序，只要仿照上述的方式，再定義出一組新的 OGLTexture 類別與 OGLGraphicsRenderer 類別，就能夠輕易地達成更換實作版本的目標。除了一開始對於實體工廠的選擇，需要明確指定出要使用的是 D3DGraphicsRenderer 工廠或 OGLGraphicsRenderer 工廠之外，往後就能夠完全忽略實作層面的細節，僅需要對抽象化工廠 GraphicsRenderer 所提供的介面進行與貼圖相關的操作行為。</p>
<blockquote>
<p>有了 <strong>Factory Method</strong> 設計模式，就不必再將與應用場合高度相依的類別寫死在主程式裡。主程式只須面對 <strong>Product</strong> 介面，因此可和任何未知的 <strong>ConcreteProduct</strong> 類別合作無間。 </p>
</blockquote>
<p>而在上述的例子中，有了創建貼圖的設計模式後，很自然地能夠再增加如 DestroyTexture() 的成員函式處理貼圖毀滅的程序。怎麼來的就怎麼回去；由工廠所創造出來的產品，就交由工廠進行毀滅的程序。所以在新增了 DestroyTexture() 成員函式之後，就能夠讓使用者傳入 Texture 物件指標或是 Texture 的名稱，於實體工廠中對貼圖資源進行毀滅或暫時回收的動作。而同樣地，使用者無須去操心煩惱相關的處理細節，只要全部丟給工廠處理就沒問題了！另外，當然也可以在工廠類別內部，使用<a href="http://blog.monkeypotion.net/gameprog/advanced/container-int2type-improvement"><strong>改良版的 STL 容器</strong></a>存放這些貼圖物件，以達到集中管理的功用。</p>
<p><strong>不論工廠內部的管理方法、製造流程如何地更換變動，都不會影響到系統外部使用者的操作行為，這就是使用 Factory Method 模式所帶來的程式架構益處。</strong>對於系統外部的使用者來說，以 Factory Method 實現出來的程式組件，就像是真實的工廠般準確無誤的運作：只要將事先約定好的資訊（貼圖檔案名稱）交遞給工廠，它就能夠生產出使用者所要求的產品（貼圖物件）。使用者通常並不會也不需要關心成品的生產過程或生成模式，因此利用 Factory Method 創建物件產品，正好能夠<strong>完善地封裝隱藏物件生成過程的實做細節</strong>。如同<strong>黑盒子</strong>的工作程序一樣，使用者不需要去瞭解產品究竟是使用 new operator 配置記憶體，或是利用資源回收系統產生出要求的物件，只管直接使用創建出來的物件即可。</p>
<p><img alt="Factory Method Applied Example" src="http://blog.monkeypotion.net/wp-content/uploads/2008/03/factory-method-applied-example.jpg" class='alignleft'/> 左圖就是上述創建貼圖範例的 UML 結構圖示，不妨與文章開頭的 Factory Method UML 結構圖兩相比對。是不是能夠對於這個有趣又實用的設計模式有了基礎的認識？</p>
<p>在這裡瞭解了 Factory Method 設計模式的實現目的，以及運用在遊戲程式難題中的實用性之後，其實只完成了對於<strong>工廠設計模式</strong>的一半認識。</p>
<p>工廠設計模式的另外半部知識，也就是 Factory Method 的好兄弟——<strong>Abstract Factory</strong> 模式，即將在續篇中接力上場，欲知結局如何，靜待續集分曉！</p>
<p>程式碼範例下載：<a href="http://blog.monkeypotion.net/download-manager.php?id=4"><strong>FactoryMethod_SampleCode.zip</strong></a> <small>(下載次數： 549 )</small></p>
<img src="http://blog.monkeypotion.net/?ak_action=api_record_view&id=76&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://blog.monkeypotion.net/gameprog/pattern/factory-method/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

