owned this note
owned this note
Published
Linked with GitHub
# Game Programming Patterns
這是我學習DisignPatterns的學習筆記,內容為我用我能夠理解的方式整理出來的,方便日後複習,所以不是非常完善,未經許可禁止分享,感謝!
## 我對於DisignPatterns和程式框架的理解
我認為應該遵循幾個原則:
1. 讓程式碼好修改
2. 讓程式碼好查找
3. 讓程式碼好理解
DisignPatterns和設計原則在Unity中是必要的,可以維持程式碼功能的切分和控制,框架的部分可以採用比較輕量的框架(例QFramework,EventBus)。
有些DI框架在寫其他的非Unity的程式一定也有其必要性,DI框架最主要的目的就是解耦並切開Mono以實現單元測試的功能,如Zenject可以很方便的實踐控制反轉,但對於Unity來說有點過於肥大了。
Unity本身就是一個DI框架的實現,Class的注入可以在Editor用拖放的方式,如果想切得更細可以自己寫介面實現func的注入等,至於要解偶到什麼程度就是程序員自己該把控的了,我認為做互動需要讓程式有一定的耦合性,耦合性低雖然可以降低程式出bug的機會,但卻增加了體驗出bug的機會,體驗一定是最重要的,單元測試過了是一回事但我認為真正重要的是體驗的測試,所以還是需要實際Play來測試才會是最好的。
總結來說其實只要遵循設計原則和使用一些DisignPatterns就可以達到好修改、好查找、好理解的目的了,不需要依賴太複雜的框架反而使程式碼變得更複雜,只要再遵循以上原則並找到和速度效能等的平衡就是很好的程式碼了。
## 物件導向設計中常見的設計原則
### 單一職責原則(Single Responsibility Principle)
顧名思義「當設計封裝一個類別時,該類別應該只負責一件事」,在此基礎上還須加上一條「一個模組應只對唯一的一個角色負責」的定義,避免單個class過於擁腫,且能夠分清楚每個class切分及歸屬。
參考文章: [使人瘋狂的 SOLID 原則:單一職責原則 (Single Responsibility Principle)](
https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E5%96%AE%E4%B8%80%E8%81%B7%E8%B2%AC%E5%8E%9F%E5%89%87-single-responsibility-principle-c2c4bd9b4e79)
### 開放封閉原則(Open-Closed Principle)
一個類別應該要「對擴充開放,對修改關閉」,使程式碼能夠"重新實作一個新的子類別"和"繼承舊有的實作類別,並在新的子類別中實作新增的功能",以此實踐對舊有的功能實作都可以保持不變(關閉),同時又能夠對功能新增的需求保持開放。
參考文章: [使人瘋狂的 SOLID 原則:開放封閉原則 (Open-Closed Principle)](
https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E9%96%8B%E6%94%BE%E5%B0%81%E9%96%89%E5%8E%9F%E5%89%87-open-closed-principle-f7eaf921eb9c)
### 里氏替代原則(Liskov Substitution Principle)
「子類別必須能夠替換父類別」,父類別能做到的事情,子類別也要能做到。
參考文章: [菜雞與物件導向 (12): 里氏替換原則](https://igouist.github.io/post/2020/11/oo-12-liskov-substitution-principle/)
### 介面隔離原則(Interface Segregation Principle)
「模組與模組之間的依賴,不應有用不到的功能可以被對方呼叫。」
參考文章: [使人瘋狂的 SOLID 原則: 介面隔離原則 (Interface Segregation Principle)](
https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E4%BB%8B%E9%9D%A2%E9%9A%94%E9%9B%A2%E5%8E%9F%E5%89%87-interface-segregation-principle-50f54473c79e)
### 依賴反轉原則 (Dependency Inversion Principle)
「高階模組不應該依賴於低階模組。兩者都應該依賴抽象。」:為了解除耦合,必須用介面這種抽象層進行隔離。
「抽象不應該依賴細節。細節應該依賴抽象。」:介面應該是高階模組提出的要求,然後才去使用實作了這些要求的低階模組。這些實作應該圍繞著這些要求,而不是讓要求去配合實作,更不要讓要求中包含實作。
「為了解決介面實例化仍然會產生依賴的問題,就有了控制反轉。」:把控制權交給第三方,藉此讓使用者能夠不用關心實例化的過程,而注重在使用並達成目標的職責上。
「而控制反轉的具體實現方法是依賴注入,」:藉由從建構式傳遞、更改目標的屬性等方式,把低階模組交給高階模組使用者。當我們藉由依賴注入的方式實現控制反轉,就能夠讓物件的設計符合依賴反轉原則。
參考文章: [菜雞與物件導向 (14): 依賴反轉原則](
https://igouist.github.io/post/2020/12/oo-14-dependency-inversion-principle/)
### 最少知識原則(Least Knowledge Principle)
「一個物件應該對其他物件應該只有最少的了解。」,不應該使用其他類別的方法所回傳的類別的方法,最佳狀態為只使用自身類別中的方法等,以提高類別的重用性。
參考文章: [菜雞與物件導向 (15): 最少知識原則](
https://igouist.github.io/post/2020/12/oo-15-least-knowledge-principle/)
## GoF設計模式(物件導向)
### State Pattern | 狀態模式
-「有狀態的物件,把複雜的邏輯判斷分配到不同的狀態物件中,允許狀態物件在其內部狀態發生改變時改變行為。」
用來製作狀態機等。
優點:
1. 減少錯誤發生並降低維護難度
2. 省效能,避免在Update中用switch判斷狀態
3. 單純化(單一職責原則),可以很清楚的知道每個狀態需要的物件和操作
範例:
State.cs
```
public class State
{
// 狀態初始化
public virtual void StateInit()
{}
// 狀態更新
public virtual void StateUpdate()
{}
// 狀態退出
public virtual void StateExit()
{}
}
```
SampleStateA.cs 、 SampleStateB.cs
```
public class SampleStateA : State
{
// 狀態初始化
public override void StateInit()
{}
// 狀態更新
public override void StateUpdate()
{}
// 狀態退出
public override void StateExit()
{}
}
public class SampleStateB : State
{
// 狀態初始化
public override void StateInit()
{}
// 狀態更新
public override void StateUpdate()
{}
// 狀態退出
public override void StateExit()
{}
}
```
StateController.cs
```
using UnityEngine;
public class StateController : MonoBehaviour
{
// 當前狀態
private State currentState;
//範例狀態(可以用Directory等控管)
public State SampleStateA;
public State SampleStateB;
private void Start()
{
SetState(SampleStateA);
}
private void Update()
{
currentState?.StateUpdate();
}
//狀態切換
private void SetState(State state)
{
currentState?.StateExit();
currentState = state;
currentState.StateInit();
}
}
```
### Facade Pattern | 外觀模式
-「替子系統定義一組統一的介面,這個高階的介面會讓子系統更容易被使用。」
主要用來切分職責和控管等,像是我們常實作的AudioManager等。
優點:
1. 各Class不須知道更複雜的底層實作只需使用需要使用的功能即可(最少知識原則)
2. 使程式更單純提高複用機會(單一職責原則)
3. 易於分工開發,其他共用腳本的人只需調用功能即可。
4. 增加系統安全性,以一個Class主管啟動流程等,可以有效避免程式執行順序引起的bug等。
範例:
AudioManager.cs
```
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public void PlayAudio()
{
//AudioClip選擇
//音量設置
//撥放設置
//.....
}
}
```
其他class不用管撥放音檔需要多複雜的設置,只要在需要撥放時call AudioManager.PlayAudio就好了
### Singleton Pattern | 單例模式
-「確認類別只有一個物件,並且提供一個全域的方法來取得這個物件。」
實作上有很多方法,只要符合以上定義都算Singleton,使用時要注意避免過度使用,因Singleton違反了(開放封閉原則),若需要變更或有需求時無法將Singleton Class替換成其他class一定需要修改Singleton Class,故無法滿足對修改封閉的原則。
避免使用Singleton最好的方法是採用DI注入的方式,使其他Class可以獲得Singleton Class實例。
優點:
1. 使用方便、快速
範例:
Singleton.cs
```
public class Singleton
{
public string Name {get; set;}
private static Singleton _instance;
public static Singleton Instance
{
get
{
if (_instance == null)
{
Debug.Log("產生Singleton");
_instance = new Singleton();
}
return _instance;
}
}
private Singleton(){}
}
```
### Mediator Pattern | 仲介者模式
-「仲介者模式(Mediator)用一個仲介物件來封裝一系列的物件互動。仲介者使個物件不需要顯式地互相參考,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。」
用於整合Class間的互動,所有功能都透過Mediator完成,使得Class對外的依賴度縮小的只剩一個類別。
優點:
1. 使Class不會引入太多的系統
2. Class被依賴的程度降低,當Class被更動時候影響的僅限於Mediator
使用時要注意避免Mediator變得過於龐大
### Bridge Pattern | 橋接模式
-「將抽象部分與實現部分分離,使它們都可以獨立的變化。」
用合成關係代替繼承關係,進而降低抽象和實作的合度。
優點:
1. 降低耦合度,如以下範例新增顏色和種類皆不會影響到其他群組
範例: 有不同種類、顏色的包包
IColor.cs
```
public interface IColor {
string getColor();
}
```
ColorWhite.cs
```
public class ColorWhite : IColor
{
public string getColor()
{
return "White";
}
}
```
ColorBlack.cs
```
public class ColorBlack : IColor
{
public string getColor()
{
return "Black";
}
}
```
Bag.cs
```
public abstract class Bag
{
protected IColor color;
public void SetColor(IColor color)
{
this.color = color;
}
public abstract string getName();
}
```
BagBackpack.cs
```
public class BagBackpack : Bag
{
public override string getName()
{
return color.getColor() + "Backpack";
}
}
```
BagWallet.cs
```
public class BagWallet : Bag
{
public override string getName()
{
return color.getColor() + "Wallet";
}
}
```
GetBag.cs
```
public class GetBag
{
public void GetBagTest()
{
IColor white = new ColorWhite();
IColor black = new ColorBlack();
Bag backpackWhite = new BagBackpack();
backpackWhite.SetColor(white);
backpackWhite.getName();
Bag backpackBlack = new BagBackpack();
backpackBlack.SetColor(black);
backpackBlack.getName();
Bag walletWhite = new BagWallet();
walletWhite.SetColor(white);
walletWhite.getName();
Bag walletBlack = new BagWallet();
walletBlack.SetColor(black);
walletBlack.getName();
}
}
```
### Strategy Pattern | 策略模式
-「定義一群演算法,並封裝每個演算法,讓他們可以彼此交換使用。策略模式讓這些演算法在客戶段使用它們時能更加獨立」
在策略模式中,不同的計算方式就是所謂的"演算法",將每個演算法獨立出來,將"計算細節"加以封裝隱藏,客戶端只要依情況選擇對應的演算法類別即可。
優點:
1. 好維護
2. 減少使用if else、switch判斷,減少可能產生的維護問題。
3. 策略替換方便
與狀態模式(State Pattern)的差別:
1. State是在一群狀態中切換,彼此之間有對應連接的關係,Strategy不知道彼此的存在。
2. State受限於狀態機的規則,後期追加需要和現有狀態有關連,而Strategy是由封裝計算演算法而形成的設計模式,演算法之間不存在任何依賴,有新增可以馬上加入替換。
理解上其實和狀態模式差不多。
學習資源:[Clean Code using the Strategy Pattern](https://www.youtube.com/watch?v=QrxiD2dfdG4&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=3)
### Template Method Pattern | 樣板方法模式
-「在一個操作方法中定義演算法流程,當中某些步驟由子類別完成。樣板方法模式讓子類別在不更動原有演算法的流程下,還能重新定義當中的步驟」
將重複的程式碼封裝成func並將需要自定義的部分開放給子類別複寫。
優點:
1. 減少重複的程式碼。
2. 同上好修改,避免修改時需大量修改等。
範例:
TemplateMethodPatternParent.cs
```
using UnityEngine;
public abstract class TemplateMethodPatternParent : MonoBehaviour
{
public void TemplateMethod()
{
Func1();
Func2();
Func3();
}
protected abstract void Func1();
private void Func2() { }
protected abstract void Func3();
}
```
TemplateMethodPatternChild.cs
```
using UnityEngine;
public class TemplateMethodPatternChild : TemplateMethodPatternParent
{
private void Start()
{
TemplateMethod();
}
protected override void Func1()
{
Debug.Log("子類新Func1");
}
protected override void Func3()
{
Debug.Log("子類新Func3");
}
}
```
### Factory Method | 工廠方法模式
-「定義一個可以產生物件的介面,但是讓子類別決定要產生哪一個類別的物件。工廠方法模式讓類別的實例化程序延遲到子類別中實行」
將類別"產生物件的流程"集合管理。
優點:
1. 能針對物件產生的流程制定規則
2. 減少客戶端參與物件生成的過程,避免客戶端生產過於複雜的類別時產生耦合度過高的問題
3. 生成物件後續設定也在工廠類別實作,讓開發人員能快速了解類別之間的關連性及設定的先後順序
以下範例有2種較常使用的實作方式:
範例:
Product.cs
```
public class ConcreteProductA : Product
{
public ConcreteProductA()
{
Debug.Log("生成物件類型A");
}
}
// 成品物件類型B
public class ConcreteProductB : Product
{
public ConcreteProductB()
{
Debug.Log("生成物件類型B");
}
}
```
ConcreteCreator_MethodType.cs : Switch case 控制
```
public class ConcreteCreator_MethodType: Creator_MethodType
{
public ConcreteCreator_MethodType()
{
Debug.Log("產生工廠:ConcreteCreator_MethodType");
}
public override Product FactoryMethod(int Type)
{
switch( Type )
{
case 1:
return new ConcreteProductA();
case 2:
return new ConcreteProductB();
}
Debug.Log("Type["+Type+"]無法產生物件");
return null;
}
}
```
ConcreteCreator_GenericMethod.cs : 泛型Class控制
```
public class ConcreteCreator_GenericMethod : Creator_GenericMethod
{
public ConcreteCreator_GenericMethod()
{
Debug.Log("產生工廠:ConcreteCreator_GenericMethod");
}
public Product FactoryMethod<T>() where T: Product, new()
{
return new T();
}
}
```
use :
```
//switch case
Creator_MethodType theCreatorMethodType = new ConcreteCreator_MethodType();
theProduct = theCreatorMethodType.FactoryMethod(1);
theProduct = theCreatorMethodType.FactoryMethod(2);
//ConcreteCreator_GenericMethod
ConcreteCreator_GenericMethod theCreatorGM = new ConcreteCreator_GenericMethod();
theProduct = theCreatorGM.FactoryMethod<ConcreteProductA>();
theProduct = theCreatorGM.FactoryMethod<ConcreteProductB>();
```
個人覺得switch case的方式比較好,使用功能的class只要傳入如enum參數就好了,不用耦合Product的class,Product的class耦合在工廠class的switch case內,使使用功能的class保持獨立性。
### * Builder Pattern | 建造者模式
-「將一個複雜物件的建構流程,與他的物件表現分離出來,讓相同的建構流程可以產生不同的物件行為表現」
將複雜物件的"建立流程"與"功能實作"拆分,讓系統調整及維護變得更容易,且不需更新實作者的情況下就可以調整建立流程的順序。
分為兩步驟實行
1. 將複雜的建構流程獨立出來,並且在一個構建方法中將這些步驟串接起來。
2. 定義一個專門實作這些步驟的實作者,實作者知道每部分該如何完成,並且能接受參數來決定要產出的功能,但不知道整個組裝流程
優點:
1. 將複雜物件的"建立流程"與"功能實作"拆分,讓系統調整及維護變得更容易
2. 不需更新實作者的情況下就可以調整建立流程的順序。
Builder Pattern和 Factory Pattern的不同:
1. Builder著重在隱藏複雜的建置步驟,最後只傳回一個產品。
2. Abstract Factory則是為了維護一系列產品的關聯,會產出某系列的多項產品。
3. Builder模式中,Client不需要認識各個零件的型態。(只要『吃』產出的餐點)
4. Abstract Factory中,Client認識各項的抽象型別或介面,並能使用它們。
使用時機:
1. 構造函數過多(>4)
2. 複雜的創建步驟
Product.cs(欲產生的複雜物件)
```
public class Product
{
private List<string> m_Part = new List<string>();
public Product(){}
public void AddPart(string Part)
{
m_Part.Add(Part);
}
public void ShowProduct()
{
Debug.Log("ShowProduct Functions:");
foreach(string Part in m_Part)
Debug.Log(Part);
}
}
```
Builder.cs(介面用來生成Product的各零件)
```
public abstract class Builder
{
public abstract void BuildPart1(Product theProduct);
public abstract void BuildPart2(Product theProduct);
}
```
ConcreteBuilderA.cs(Builder介面的具體實作A)
```
public class ConcreteBuilderA : Builder
{
public override void BuildPart1(Product theProduct)
{
theProduct.AddPart( "ConcreteBuilderA_Part1");
}
public override void BuildPart2(Product theProduct)
{
theProduct.AddPart( "ConcreteBuilderA_Part2");
}
}
```
ConcreteBuilderB.cs(Builder介面的具體實作B)
```
public class ConcreteBuilderB : Builder
{
public override void BuildPart1(Product theProduct)
{
theProduct.AddPart( "ConcreteBuilderB_Part1");
}
public override void BuildPart2(Product theProduct)
{
theProduct.AddPart( "ConcreteBuilderB_Part2");
}
}
```
Director.cs(利用Builder介面來建構物件)
```
public class Director
{
private Product m_Product;
public Director(){}
// 建立
public void Construct(Builder theBuilder)
{
// 利用Builder產生各部份加入Product中
m_Product = new Product();
theBuilder.BuildPart1( m_Product );
theBuilder.BuildPart2( m_Product );
}
// 取得成品
public Product GetResult()
{
return m_Product;
}
}
```
use:
```
// 建立
Director theDirectoir = new Director();
Product theProduct = null;
// 使用BuilderA建立
theDirectoir.Construct( new ConcreteBuilderA());
theProduct = theDirectoir.GetResult();
theProduct.ShowProduct();
// 使用BuilderB建立
theDirectoir.Construct( new ConcreteBuilderB());
theProduct = theDirectoir.GetResult();
theProduct.ShowProduct();
```
### Flyweight Pattern | 享元模式
-「使用共享的方式,讓一大群小規模物件能更有效地運作」
享元模式是以提高性能為目的的設計模式,如果存在著多個可以重複使用的對象,那麼就只需要共享同一份就好,不需要每次都去創建新的對象。
設計上"只能讀取不能寫入"的共享部分為"內在(intrinsic)狀態","不能被共享"的部分則為"外在(extrinsic)狀態。
享元模式(Flyweight)用於產生物件時,將能夠共享的"內在(intrinsic)狀態"加以管理,並且將屬於個物件能自由變更的"外在(extrinsic)狀態"也一併設定給新產生的物件中。
優點:
1. 減少記憶體負擔,提升效能
2. 數值設定更清楚,方便閱讀及設定
範例:
Flyweight.cs(可以被共用的Flyweight介面)
```
public abstract class Flyweight
{
protected string m_Content; //顯示的內容
public Flyweight(){}
public Flyweight(string Content)
{
m_Content= Content;
}
public string GetContent()
{
return m_Content;
}
public abstract void Operator();
}
```
ConcreteFlyweight.cs(共用的元件)
```
public class ConcreteFlyweight : Flyweight
{
public ConcreteFlyweight(string Content):base( Content )
{
}
public override void Operator()
{
Debug.Log("ConcreteFlyweight.Content["+m_Content+"]");
}
}
```
UnsharedCoincreteFlyweight.cs (不共用的元件,可以不必繼承)
```
public class UnsharedCoincreteFlyweight //: Flyweight
{
Flyweight m_Flyweight = null; // 共享的元件
string m_UnsharedContent; // 不共享的元件
public UnsharedCoincreteFlyweight(string Content)
{
m_UnsharedContent = Content;
}
// 設定共享的元件
public void SetFlyweight(Flyweight theFlyweight)
{
m_Flyweight = theFlyweight;
}
public void Operator()
{
string Msg = string.Format("UnsharedCoincreteFlyweight.Content[{0}]",m_UnsharedContent);
if( m_Flyweight != null)
Msg += "包含了:" + m_Flyweight.GetContent();
Debug.Log(Msg);
}
}
```
FlyweightFactor.cs
```
public class FlyweightFactor
{
Dictionary<string,Flyweight> m_Flyweights = new Dictionary<string,Flyweight>();
// 取得共用的元件
public Flyweight GetFlyweight(string Key,string Content)
{
if( m_Flyweights.ContainsKey( Key) )
return m_Flyweights[Key];
// 產生並設定內容
ConcreteFlyweight theFlyweight = new ConcreteFlyweight( Content );
m_Flyweights[Key] = theFlyweight;
Debug.Log ("New ConcreteFlyweigh Key["+Key+"] Content["+Content+"]");
return theFlyweight;
}
// 取得元件(只取得不共用的Flyweight)
public UnsharedCoincreteFlyweight GetUnsharedFlyweight(string Content)
{
return new UnsharedCoincreteFlyweight( Content);
}
// 取得元件(包含共用部份的Flyweight)
public UnsharedCoincreteFlyweight GetUnsharedFlyweight(string Key,string SharedContent,string UnsharedContent)
{
// 先取得共用的部份
Flyweight SharedFlyweight = GetFlyweight(Key, SharedContent);
// 產出元件
UnsharedCoincreteFlyweight theFlyweight = new nsharedCoincreteFlyweight( UnsharedContent);
theFlyweight.SetFlyweight( SharedFlyweight ); // 設定共享的部份
return theFlyweight;
}
}
```
use:
```
FlyweightFactor theFactory = new FlyweightFactor();
// 產生共用元件
theFactory.GetFlyweight("1","共用元件1");
theFactory.GetFlyweight("2","共用元件1");
theFactory.GetFlyweight("3","共用元件1");
// 取得一個共用元件
Flyweight theFlyweight = theFactory.GetFlyweight("1","");
theFlyweight.Operator();
// 產生不共用的元件
UnsharedCoincreteFlyweight theUnshared1 = theFactory.GetUnsharedFlyweight("不共用的資訊1");
theUnshared1.Operator();
// 設定共用元件
theUnshared1.SetFlyweight( theFlyweight );
// 產生不共用的元件2,並指定使用共用元件1
UnsharedCoincreteFlyweight theUnshared2 = theFactory.GetUnsharedFlyweight("1","","不共用的資訊2");
// 同時顯示
theUnshared1.Operator();
theUnshared2.Operator();
//Log
//New ConcreteFlyweigh Key[1] Content[共用元件1]
//New ConcreteFlyweigh Key[2] Content[共用元件1]
//New ConcreteFlyweigh Key[3] Content[共用元件1]
//ConcreteFlyweight.Content[共用元件1]
//UnsharedCoincreteFlyweight.Content[不共用的資訊1]
//UnsharedCoincreteFlyweight.Content[不共用的資訊1]包含了:共用元件1
//UnsharedCoincreteFlyweight.Content[不共用的資訊2]包含了:共用元件1
```
通常用於製作untiy物件池
### Composite Pattern | 組合模式
-「將物件以樹狀結構組合,用以表現部分-全體的階層關係。組合模式讓客戶端在操作個別物件或組合物件時是一致的」
主要結構分為2個,
-Composite(組合節點):根節點,會包含Leaf也可以包含其他Composite,會實作與子節點相關的func如Add,Remove,Get等。
-Leaf(葉節點):不包含任何節點的最終節點。
如Unity中Hierarchy中的結構就是Composite Pattern的實作,GameObject為Composite,Gameobject上的Component如Transform、Rigidbody等為Leaf。
優點:
1. 介面與功能分離
2. 功能切分清楚,更動時不更動到其他功能
3. 結構清楚
範例:
IComponent.cs(複合體內含物件之介面)
```
public abstract class IComponent
{
protected string m_Value;
public abstract void Operation(); // 一般操作
// 加入節點
public virtual void Add( IComponent theComponent)
{
Debug.LogWarning("子類別沒實作");
}
// 移除節點
public virtual void Remove( IComponent theComponent)
{
Debug.LogWarning("子類別沒實作");
}
// 取得子節點
public virtual IComponent GetChild(int Index)
{
Debug.LogWarning("子類別沒實作");
return null;
}
}
```
Composite.cs(代表複合結構的節點之行為)
```
public class Composite : IComponent
{
List<IComponent> m_Childs = new List<IComponent>();
public Composite(string Value)
{
m_Value = Value;
}
// 一般操作
public override void Operation()
{
Debug.Log("Composite["+m_Value+"]");
foreach(IComponent theComponent in m_Childs)
theComponent.Operation();
}
// 加入節點
public override void Add( IComponent theComponent)
{
m_Childs.Add ( theComponent );
}
// 移除節點
public override void Remove( IComponent theComponent)
{
m_Childs.Remove( theComponent );
}
// 取得子節點
public override IComponent GetChild(int Index)
{
return m_Childs[Index];
}
}
```
Leaf.cs(代表複合結構之終端物件)
```
public class Leaf : IComponent
{
public Leaf(string Value)
{
m_Value = Value;
}
public override void Operation()
{
Debug.Log("Leaf["+ m_Value +"]執行Operation()");
}
}
```
Use:
```
// 根節點
IComponent theRoot = new Composite("Root");
// 加入兩個最終節點
theRoot.Add ( new Leaf("Leaf1"));
theRoot.Add ( new Leaf("Leaf2"));
// 子節點1
IComponent theChild1 = new Composite("Child1");
// 加入兩個最終節點
theChild1.Add ( new Leaf("Child1.Leaf1"));
theChild1.Add ( new Leaf("Child1.Leaf2"));
theRoot.Add (theChild1);
// 子節點2
// 加入3個最終節點
IComponent theChild2 = new Composite("Child2");
theChild2.Add ( new Leaf("Child2.Leaf1"));
theChild2.Add ( new Leaf("Child2.Leaf2"));
theChild2.Add ( new Leaf("Child2.Leaf3"));
theRoot.Add (theChild2);
// 顯示
theRoot.Operation();
```
### Command Pattern | 命令模式
-「將請求封裝成為物件,讓你可以將客戶端的不同請求參數化,並配合序列、紀錄、復原等方法來操作請求」
也可以用來實現func的注入,需要注意可能會產生過多的類別。
所以如果不是因為框架限制的話,可以只將有需要操作的func用命令模式實作就好了
學習資源:
* [Level up your code with game programming patterns: Command pattern | Tutorial
](https://www.youtube.com/watch?v=attURV3JWKQ&list=PLX2vGYjWbI0TmDVbWNA56NbKKUgyUAQ9i)
* [How to use the Command Pattern (Skill Combos Example)
](https://www.youtube.com/watch?v=GL1w7IZ1OYU&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=5)
優點:
1. 將命令封裝為物件後,可以加上額外的操作及參數化。
範例:
Command.cs(執行命令的介面)
```
public abstract class Command
{
public abstract void Execute();
}
```
ConcreteCommand1.cs(將命令和Receiver1物件繫結起來)
```
public class ConcreteCommand1 : Command
{
Receiver1 m_Receiver = null;
string m_Command = "";
public ConcreteCommand1( Receiver1 Receiver, string Command )
{
m_Receiver = Receiver;
m_Command = Command;
}
public override void Execute()
{
m_Receiver.Action(m_Command);
}
}
```
Receiver1.cs(負責執行命令1)
```
public class Receiver1
{
public Receiver1(){}
public void Action(string Command)
{
Debug.Log ("Receiver1.Action:Command["+Command+"]");
}
}
```
Invoker.cs(命令管理者)
```
public class Invoker
{
List<Command> m_Commands = new List<Command>();
// 加入命令
public void AddCommand( Command theCommand )
{
m_Commands.Add( theCommand );
}
// 執行命令
public void ExecuteCommand()
{
// 執行
foreach(Command theCommand in m_Commands)
theCommand.Execute();
// 清空
m_Commands.Clear();
}
}
```
Use:
```
Invoker theInvoker = new Invoker();
Command theCommand = null;
// 獎命令與執行結合
theCommand = new ConcreteCommand1( new Receiver1(),"你好");
theInvoker.AddCommand( theCommand );
// 執行
theInvoker.ExecuteCommand();
```
### Chain of Responsibility Pattern | 責任鏈模式
-「讓一群物件都有機會來處理一項請求,以減少請求發送者與接收者之間的耦合度。將所有的接收者物件串接起來,讓請求沿著串接傳遞,直到有一個物件可以處理為止」
使一群訊息的接收者能夠被串聯起來管理,使判斷上能有更一致的操作介面,不需要因為不同的接收者而轉換類別操作,在後續的系統維護上可以輕易地增加接收者類別。
(可以用於製作關卡流程控制)
優點:
1. 改善原本冗長的寫法,如if做關卡判斷改為替換判斷物件
2. 使程式更佳靈活,可以有多種形式的組合。
範例:
Handler.cs (處理訊息的介面)
```
public abstract class Handler
{
protected Handler m_NextHandler = null;
public Handler( Handler theNextHandler )
{
m_NextHandler = theNextHandler;
}
public virtual void HandleRequest(int Cost)
{
if(m_NextHandler!=null)
m_NextHandler.HandleRequest(Cost);
}
}
```
ConcreteHandler1.cs(處理所負責的訊息1)
```
public class ConcreteHandler1 : Handler
{
private int m_CostCheck = 10;
public ConcreteHandler1( Handler theNextHandler ) : base( theNextHandler )
{}
public override void HandleRequest(int Cost)
{
if( Cost <= m_CostCheck)
Debug.Log("ConcreteHandler1.核準");
else
base.HandleRequest(Cost);
}
}
```
ConcreteHandler2.cs(處理所負責的訊息2)
```
public class ConcreteHandler2 : Handler
{
private int m_CostCheck = 20;
public ConcreteHandler2( Handler theNextHandler ) : base( theNextHandler )
{}
public override void HandleRequest(int Cost)
{
if( Cost <= m_CostCheck)
Debug.Log("ConcreteHandler2.核準");
else
base.HandleRequest(Cost);
}
}
```
ConcreteHandler3.cs(處理所負責的訊息3)
```
public class ConcreteHandler3 : Handler
{
public ConcreteHandler3( Handler theNextHandler ) : base( theNextHandler )
{}
public override void HandleRequest(int Cost)
{
Debug.Log("ConcreteHandler3.核準");
}
}
```
use:
```
// 建立Cost驗証的連接方式
ConcreteHandler3 theHandle3 = new ConcreteHandler3(null);
ConcreteHandler2 theHandle2 = new ConcreteHandler2(theHandle3);
ConcreteHandler1 theHandle1 = new ConcreteHandler1(theHandle2);
// 確認
theHandle1.HandleRequest(10);
theHandle1.HandleRequest(15);
theHandle1.HandleRequest(20);
theHandle1.HandleRequest(30);
theHandle1.HandleRequest(100);
//Log:
//ConcreteHandler1.核準
//ConcreteHandler2.核準
//ConcreteHandler2.核準
//ConcreteHandler3.核準
//ConcreteHandler3.核準
```
### Observer Pattern | 觀察者模式
-「在物件之間定義一個一對多的連接方法,當一個物件變換狀態時,其他關連的物件都會自動收到通知」
設定一個主題(Subject),讓主題發布時可同時通知關心這個主題的觀察者,且主題不必理會觀察者接下來會執行哪些動作。
和命令模式相同容易產生類別過多的問題,如果想減少類別可以在向主題註冊時改為使用"回呼函式Callback"等方法,再用一個類別統一管理。
優點:
1. 解除事件發生和相關功能呼叫的耦合
2. 同時呼叫多個處理事件引發後續動作的系統
和命令模式的差別:
兩個模式都是著重在"發生"與"執行"的解偶,當觀察者模式中主題只有一個觀察者時就非常像命令模式的架構,可以用應用時機分辨兩個設計模式。
* 命令模式(Command): 該模式的另一個重點在於"命令的管理,應用的系統對命令有新增、刪除、紀錄、排序、回復等的需求。
* 觀察者模式(Observer):對於"觀察者"可進行管理,觀察者在系統執行階段可決定訂閱或退訂等動作,讓觀察者可被管理。
use:
Observer.cs(觀察者介面)
```
public abstract class Observer
{
public abstract void Update();
}
```
Subject.cs(主題介面)
```
public abstract class Subject
{
List<Observer> m_Observers = new List<Observer>();
// 加入觀察者
public void Attach(Observer theObserver)
{
m_Observers.Add( theObserver );
}
// 移除觀察者
public void Detach(Observer theObserver)
{
m_Observers.Remove( theObserver );
}
// 通知所有觀察者
public void Notify()
{
foreach( Observer theObserver in m_Observers)
theObserver.Update();
}
}
```
ConcreteSubject.cs(主題實作)
```
public class ConcreteSubject : Subject
{
string m_SubjectState;
public void SetState(string State)
{
m_SubjectState = State;
Notify();
}
public string GetState()
{
return m_SubjectState;
}
}
```
ConcreteObserver.cs(實作的Observer)
```
public class ConcreteObserver1 : Observer
{
string m_ObjectState;
ConcreteSubject m_Subject = null;
public ConcreteObserver1( ConcreteSubject theSubject)
{
m_Subject = theSubject;
}
// 通知Subject更新
public override void Update ()
{
Debug.Log ("ConcreteObserver1.Update");
// 取得Subject狀態
m_ObjectState = m_Subject.GetState();
}
public void ShowState()
{
Debug.Log ("ConcreteObserver1:Subject目前的主題:"+m_ObjectState);
}
}
```
use:
```
// 主題
ConcreteSubject theSubject = new ConcreteSubject();
// 加入觀察者
ConcreteObserver1 theObserver1 = new ConcreteObserver1(theSubject);
theSubject.Attach( theObserver1 );
theSubject.Attach( new ConcreteObserver2(theSubject) );
// 設定Subject
theSubject.SetState("Subject狀態1");
```
### Memento Pattern | 備忘錄模式
-「在不違反封裝的原則下,取得一個物件內部狀態並保留在外部,讓該物件可以在日後恢復到原先保留的狀態。」
備忘錄模式提供了一個,不破壞原有類別封裝性的「物件保存狀態」方案,並讓物件狀態保存可以存在多個版本,並且還可以選擇要返回哪個版本。
優點:
1. 獨立負責存檔的類別,不影響原本系統的封裝性。
學習資源:
* [Memento: Capture Game State Snapshots in Unity
](https://www.youtube.com/watch?v=sDNLXnIJP6k&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=9)
範例:
Memento.cs(存放Originator物件的內部狀態)
Unity中通常有幾種儲存方法
* PlayerPrefs
* System.IO.File
* System.Xml
```
public class Memento
{
string m_State;
public string GetState()
{
return m_State;
}
public void SetState(string State)
{
m_State = State;
}
}
```
Originator.cs(需要儲存內容資訊)
```
public class Originator
{
string m_State; // 狀態,需要被保存
public void SetInfo(string State)
{
m_State = State;
}
public void ShowInfo()
{
Debug.Log("Originator State:"+m_State);
}
// 產生要儲存的記錄
public Memento CreateMemento()
{
Memento newMemento = new Memento();
newMemento.SetState( m_State );
return newMemento;
}
// 設定要回復的記錄
public void SetMemento( Memento m)
{
m_State = m.GetState();
}
}
```
Caretaker.cs(保管所有的Memento)
```
public class Caretaker
{
Dictionary<string, Memento> m_Memntos = new Dictionary<string, Memento>();
// 增加
public void AddMemento(string Version , Memento theMemento)
{
if(m_Memntos.ContainsKey(Version)==false)
m_Memntos.Add(Version, theMemento);
else
m_Memntos[Version]=theMemento;
}
// 取回
public Memento GetMemento(string Version)
{
if(m_Memntos.ContainsKey(Version)==false)
return null;
return m_Memntos[Version];
}
}
```
use.cs
```
Originator theOriginator = new Originator();
Caretaker theCaretaker = new Caretaker();
// 設定資訊
theOriginator.SetInfo( "Version1" );
theOriginator.ShowInfo();
// 保存
theCaretaker.AddMemento("1",theOriginator.CreateMemento());
// 設定資訊
theOriginator.SetInfo( "Version2" );
theOriginator.ShowInfo();
// 保存
theCaretaker.AddMemento("2",theOriginator.CreateMemento());
// 設定資訊
theOriginator.SetInfo( "Version3" );
theOriginator.ShowInfo();
// 保存
theCaretaker.AddMemento("3",theOriginator.CreateMemento());
// 退回到第2版,
theOriginator.SetMemento( theCaretaker.GetMemento("2"));
theOriginator.ShowInfo();
// 退回到第1版,
theOriginator.SetMemento( theCaretaker.GetMemento("1"));
theOriginator.ShowInfo();
//log
// 1
// 2
// 3
// 2
// 1
```
### Visitor Pattern | 訪問者模式
-「定義一個能夠實行在一個物件結構中對於所有元素的操作。訪問者讓你可以定義一個新的操作,而不必更動到被操作元素的類別介面」
訪問者模式提供一個解決方案,讓針對一群物件走訪或判斷的功能,都能套用同一組介面方法來完成,過程中只會新增該功能本身的實作檔,對於原有的介面並不會產生任何的更動。
須注意訪問者模式的缺點有"當有新的類別時vistor須連帶新增對應呼叫方法"的問題,所以比較適合用在元素結構穩定時,避免新增元素或刪改時要大量改動。
文章:https://zhuanlan.zhihu.com/p/380161731
學習資源:[Visitor: How I Mastered the Toughest Programming Pattern](https://www.youtube.com/watch?v=Q2gQs6gIzCM&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=6)
優點:
1. 增加系統穩定性
2. 減少部必要的類別介面修改
Visitor.cs(固定的元素, 定義給Visitor存取的介面)
```
public abstract class Visitor
{
// 可以寫一個通用的函式名稱但以用不同的參數來產生多樣化方法
public abstract void VisitConcreteElement( ConcreteElementA theElement);
public abstract void VisitConcreteElement( ConcreteElementB theElement);
// 或可以針對Element的子元件做不同的執行動作
public abstract void VisitConcreteElementA( ConcreteElementA theElement);
public abstract void VisitConcreteElementB( ConcreteElementB theElement);
}
```
Element.cs(制訂以Visitor物件當參數的Accept()介面)
```
public abstract class Element
{
public abstract void Accept( Visitor theVisitor);
}
```
ConcreteElementA(元素A)
```
public class ConcreteElementA : Element
{
public override void Accept( Visitor theVisitor)
{
theVisitor.VisitConcreteElement( this );
theVisitor.VisitConcreteElementA( this );
}
public void OperationA()
{
Debug.Log("ConcreteElementA.OperationA");
}
}
```
ConcreteElementB(元素B)
```
public class ConcreteElementB : Element
{
public override void Accept( Visitor theVisitor)
{
theVisitor.VisitConcreteElement( this );
theVisitor.VisitConcreteElementB( this );
}
public void OperationB()
{
Debug.Log("ConcreteElementB.OperationB");
}
}
```
ConcreteVicitor1(實作功能操作Visitor1)
```
public class ConcreteVicitor1 : Visitor
{
// 可以寫一個通用的函式名稱但以用不同的參數來產生多樣化方法
public override void VisitConcreteElement( ConcreteElementA theElement)
{
Debug.Log ("ConcreteVicitor1:VisitConcreteElement(A)");
}
public override void VisitConcreteElement( ConcreteElementB theElement)
{
Debug.Log ("ConcreteVicitor1:VisitConcreteElement(B)");
}
public override void VisitConcreteElementA( ConcreteElementA theElement)
{
Debug.Log ("ConcreteVicitor1.VisitConcreteElementA()");
theElement.OperationA();
}
public override void VisitConcreteElementB( ConcreteElementB theElement)
{
Debug.Log ("ConcreteVicitor1.VisitConcreteElementB()");
theElement.OperationB();
}
}
```
ConcreteVicitor2(實作功能操作Visitor2)
```
public class ConcreteVicitor2 : Visitor
{
// 可以寫一個通用的函式名稱但以用不同的參數來產生多樣化方法
public override void VisitConcreteElement( ConcreteElementA theElement)
{
Debug.Log ("ConcreteVicitor2:VisitConcreteElement(A)");
}
public override void VisitConcreteElement( ConcreteElementB theElement)
{
Debug.Log ("ConcreteVicitor2.VisitConcreteElement(B)");
}
public override void VisitConcreteElementA( ConcreteElementA theElement)
{
Debug.Log ("ConcreteVicitor2.VisitConcreteElementA()");
theElement.OperationA();
}
public override void VisitConcreteElementB( ConcreteElementB theElement)
{
Debug.Log ("ConcreteVicitor2.VisitConcreteElementB()");
theElement.OperationB();
}
}
```
ObjectStructure.cs(用來存儲所有的Element)
```
public class ObjectStructure
{
List<Element> m_Context = new List<Element>();
public ObjectStructure()
{
m_Context.Add( new ConcreteElementA());
m_Context.Add( new ConcreteElementB());
}
// 載入不同的Action(Visitor)來判斷
public void RunVisitor(Visitor theVisitor)
{
foreach(Element theElement in m_Context )
theElement.Accept( theVisitor);
}
}
```
use:
```
ObjectStructure theStructure = new ObjectStructure();
// 將Vicitor走訪ObjectStructure裡的各元表
theStructure.RunVisitor(new ConcreteVicitor1());
theStructure.RunVisitor(new ConcreteVicitor2());
```
可以看出不同Visitor類有同Element,但有不同的行為
也就是有新的需求時新增Vistor並更換就好了
### Decorator Pattern | 裝飾模式
-「動態地附加格外的責任給一個物件。裝飾模式提供了一個靈活的選擇,讓子類別可以用來擴展功能」
裝飾模式(Decorator)適合專案後期增加系統功能時使用。對於已經進入後期及維護周期的專案來說使用裝飾模式(Decorator)來增加現有系統的附加功能是較穩定的方式。
若在專案前期就有規劃的功能應避免使用過多的裝飾模式(Decorator)免得過度疊加功能,造成除錯上的困難。
優點:
1. 透過裝飾模式(Decorator)新增功能時,可以避免更動到已經實作的程式碼,增加系統穩定性及靈活性。
2. 更方便的組裝及新增想要效果。
學習資源:[How to use the Decorator Pattern (Card Game Example)](https://www.youtube.com/watch?v=o5Iwu5wpINQ&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=4)
範例:
Component.cs(制訂可被Decorator動態增加權責的物件介面)
```
public abstract class Component
{
public abstract void Operator();
}
```
ConcreteComponent.cs(定義可被Decorator動態增加權責的物件)
```
public class ConcreteComponent : Component
{
public override void Operator()
{
Debug.Log("ConcreteComponent.Operator");
}
}
```
Decorator.cs(持有指向Component的reference,須符合Component的介面)
```
public class Decorator : Component
{
Component m_Component;
public Decorator(Component theComponent)
{
m_Component = theComponent;
}
public override void Operator()
{
m_Component.Operator();
}
}
```
ConcreteDecoratorA.cs(增加額外的權責/功能)
```
public class ConcreteDecoratorA : Decorator
{
Component m_Component;
public ConcreteDecoratorA(Component theComponent) : base( theComponent)
{
}
public override void Operator()
{
base.Operator();
AddBehaivor();
}
private void AddBehaivor()
{
Debug.Log("ConcreteDecoratorA.AddBehaivor");
}
}
```
ConcreteDecoratorB.cs(增加額外的權責/功能)
```
public class ConcreteDecoratorB : Decorator
{
Component m_Component;
public ConcreteDecoratorB(Component theComponent) : base( theComponent)
{
}
public override void Operator()
{
base.Operator();
AddBehaivor();
}
private void AddBehaivor()
{
Debug.Log("ConcreteDecoratorB.AddBehaivor");
}
}
```
use:
```
// 物件
ConcreteComponent theComponent = new ConcreteComponent();
// 增加Decorator
ConcreteDecoratorA theDecoratorA = new ConcreteDecoratorA( theComponent );
theDecoratorA.Operator();
ConcreteDecoratorB theDecoratorB = new ConcreteDecoratorB( theComponent );
theDecoratorB.Operator();
// 再增加一層
ConcreteDecoratorB theDecoratorB2 = new ConcreteDecoratorB( theDecoratorA );
theDecoratorB2.Operator();
//log:
//ConcreteComponent.Operator
//ConcreteDecoratorA.AddBehaivor
//ConcreteComponent.Operator
//ConcreteDecoratorB.AddBehaivor
//ConcreteComponent.Operator
//ConcreteDecoratorA.AddBehaivor
//ConcreteDecoratorB.AddBehaivor
```
### Adapter Pattern | 轉換器模式
-「將一個類別的介面轉換成為客戶端期待的類別介面。轉接器模式讓原本介面不相容的類別能一起合作」
轉換器模式(Adapter)可以用於分離專案與其他第三方的工具和函式庫,
在專案內先寫一個轉接器先自行定義功能再將第三方工具套用在實做中來形成隔離。
優點:
1. 通過使用轉換器模式避免轉換時需要大規模修改。
範例:
Target.cs(應用域領(Client)所需的介面)
```
public abstract class Target
{
public abstract void Request();
}
```
Adaptee.cs(不同於應用域領(Client)的實作,需要被轉換)
```
public class Adaptee
{
public Adaptee(){}
public void SpecificRequest()
{
Debug.Log("呼叫Adaptee.SpecificRequest");
}
}
```
Adapter.cs(將Adaptee轉換成Target介面)
```
public class Adapter : Target
{
private Adaptee m_Adaptee = new Adaptee();
public Adapter(){}
public override void Request()
{
m_Adaptee.SpecificRequest();
}
}
```
use:
```
Target theTarget = new Adapter();
theTarget.Request();
log:
//呼叫Adaptee.SpecificRequest
```
### Proxy Pattern | 代理模式
-「提供一個代理者位置給一個物件,好讓代理者可以控制存取這個物件」
在特定情況下新增一個代理人來管理要存取的物件,有許多的用法及變體。
* 遠端代理(Remote Proxy): 在網路中或是跨 Process 的各種程式,不可能直接存取(assess) 不同程式間的物件,因此就需要遠端代理人。
* 虛擬代理(Virtual Proxy): 可以做為載入實的功能實作,在資源載入完成前提供預設的資源做為替代。
* 保護代理(Protection Proxy): 代理者有職權可以控制是否要真正取用原始物件的資源。
* 智慧型參考(Smart Reference): 當物件被參考到時,進行額外的動作,例如計算此物件被參考的次數。
優點:
1. 使用新增類別的方式來強化原有的功能,對原本的實作不加更動。
2. 對於只是想測試可能產生效能瓶頸的地方,替換實作方式時非常方便。
3. 若將功能是否開啟改為使用設定檔來設定,可讓代理者的實作不更動到原有的介面。
4. 將快取功能由代理者實作,不會破壞原有的類別介面。
範例:
Subject.cs(制訂RealSubject和Proxy所共用遵偱的介面)
```
public abstract class Subject
{
public abstract void Request();
}
```
RealSubject.cs(定義Proxy所代表的真正物件)
```
public class RealSubject : Subject
{
public RealSubject(){}
public override void Request()
{
Debug.Log("RealSubject.Request");
}
}
```
Proxy.cs(持有指向RealSubject物件的reference以便存取真正的物件)
```
public class Proxy : Subject
{
RealSubject m_RealSubject = new RealSubject();
// 權限控制
public bool ConnectRemote{get; set;}
public Proxy()
{
ConnectRemote = false;
}
public override void Request()
{
// 依目前狀態決定是否存取RealSubject
if( ConnectRemote )
m_RealSubject.Request();
else
Debug.Log ("Proxy.Request");
}
}
```
use:
```
// 產生Proxy
Proxy theProxy = new Proxy();
// 透過Proxy存取
theProxy.Request();
theProxy.ConnectRemote = true;
theProxy.Request();
//log:
//Proxy.Request
//RealSubject.Request
```
### Iterator Pattern | 迭代器模式
-「在不知道集合內部細節的情況下,提供一個循序方法去存取一個物件集合體的每一個單位」
已經內化成許多程式語言的語法糖了,如C#的foreach語法。
### Prototype Pattern | 原形模式
-「使用原形物件來產生指定類別物件,所以產生物件時,是使用複製原形物件來完成」
將一個複雜的物件的組合方式先設定好,之後需要使用時就不必在重複組裝,只需從做好的原型(Prototype)複製出來就好。如Untiy的Prefab。
### Interpreter Pattern | 解釋器模式
-「定義一個程式語言所需要的語法,並提供直譯來解析(執行)該語言」
如在Unity中使用C#開發就會經過Unity Engine編譯過。
### Abstract Factory Pattern | 抽象工廠模式
-「提供一個能夠建立一整個類別群組或有關連的物件,而不必指名它們的具體類別」
更換繼承的工廠類別,就可通過更換不同的工廠並用同樣的方法得到不同的產品。
優點:
1. 能將產生的物件整組轉換到不同的類別群組上。
學習資源:[When to use Factory and Abstract Factory Programming Patterns](https://www.youtube.com/watch?v=Z1CDJASi4SQ&list=PLnJJ5frTPwRMCCDVE_wFIt3WIj163Q81V&index=2)
範例:
AbstractFactory.cs(可生成各抽象成品物件的操作)
```
public abstract class AbstractFactory
{
public abstract AbstractProductA CreateProductA();
public abstract AbstractProductB CreateProductB();
}
```
ConcreateFactory1.cs(實作出可建構具象成品物件的操作1)
```
public class ConcreateFactory1 : AbstractFactory
{
public ConcreateFactory1(){}
public override AbstractProductA CreateProductA()
{
return new ProductA1();
}
public override AbstractProductB CreateProductB()
{
return new ProductB1();
}
}
```
ConcreateFactory2.cs(實作出可建構具象成品物件的操作2)
```
public class ConcreateFactory2 : AbstractFactory
{
public ConcreateFactory2(){}
public override AbstractProductA CreateProductA()
{
return new ProductA2();
}
public override AbstractProductB CreateProductB()
{
return new ProductB2();
}
}
```
AbstractProductA.cs(成品物件類型A界面)
```
public abstract class AbstractProductA
{
}
```
ProductA1.cs(成品物件類型A1)
```
public class ProductA1 : AbstractProductA
{
public ProductA1()
{
Debug.Log("生成物件類型A1");
}
}
```
ProductA2.cs(生成物件類型A2)
```
public class ProductA2 : AbstractProductA
{
public ProductA2()
{
Debug.Log("生成物件類型A2");
}
}
```
AbstractProductB.cs(成品物件類型B界面)
```
public abstract class AbstractProductB
{
}
```
ProductA1.cs(成品物件類型A1)
```
public class ProductB1 : AbstractProductB
{
public ProductB1()
{
Debug.Log("生成物件類型B1");
}
}
```
ProductA2.cs(生成物件類型A2)
```
public class ProductB2 : AbstractProductB
{
public ProductB2()
{
Debug.Log("生成物件類型B2");
}
}
```
use:
```
AbstractFactory Factory= null;
// 工廠1
Factory = new ConcreateFactory1();
// 產生兩個產品
Factory.CreateProductA();
Factory.CreateProductB();
// 工廠2
Factory = new ConcreateFactory2();
// 產生兩個產品
Factory.CreateProductA();
Factory.CreateProductB();
//log:
//生成物件類型A1
//生成物件類型B1
//生成物件類型A2
//生成物件類型B2
```
## 參考、學習來源
* 書籍:設計模式與遊戲開發的完美結合
* 網路文章: [使人瘋狂的-solid-原則-目錄](https://medium.com/%E7%A8%8B%E5%BC%8F%E6%84%9B%E5%A5%BD%E8%80%85/%E4%BD%BF%E4%BA%BA%E7%98%8B%E7%8B%82%E7%9A%84-solid-%E5%8E%9F%E5%89%87-%E7%9B%AE%E9%8C%84-b33fdfc983ca)
* 網路文章: [菜雞與物件導向 系列的文章](https://igouist.github.io/series/%E8%8F%9C%E9%9B%9E%E8%88%87%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91/)
* 網路文章: [什麼是Design Pattern?](https://ianjustin39.github.io/ianlife/design-pattern/builder-pattern/)
* 網路文章: [DESIGN PATTERN MURMUR 深入淺出設計模式 (Head First Design Patterns) 的筆記](https://corrupt003-design-pattern.blogspot.com/)
* Youtube: [Unity_Game Programming Patterns Tutorials](https://www.youtube.com/@unity)
* Youtube: [git-amend](https://www.youtube.com/@git-amend)
* Unirt教程: [Level up your programming with game programming patterns](https://unity.com/cn/resources/level-up-your-code-with-game-programming-patterns?isGated=false&utm_source=youtube&utm_medium=social&utm_campaign=engine_global_ebook_2023-12-18_levelupcode-factorypatternsyt-ebook)