> For the complete documentation index, see [llms.txt](https://www.saladpuk.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.saladpuk.com/beginner-1/design-patterns/creational/prototype-pattern.md).

# Prototype Pattern

เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า **ต้นแบบ** ซึ่งมันอยู่ในกลุ่มของ 🤰 [**Creational Patterns**](https://saladpuk.gitbook.io/learn/beginner-1/design-patterns/creational) โดยเจ้าตัวนี้มันจะมาช่วยให้เราสามารถ ก๊อปปี้ object ตัวหนึ่ง ออกไปเป็นอีกตัวหนึ่งได้แบบง่ายๆ ซึ่งอ่านแล้วก็อาจจะ งงๆ ว่าทำไมต้องทำ ดังนั้นไปดูตัวอย่างปัญหาของเรากันดีกั่ว

{% hint style="info" %}
**แนะนำให้อ่าน**\
บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช [👦 **Design Patterns**](https://saladpuk.gitbook.io/learn/beginner-1/design-patterns)
{% endhint %}

{% hint style="warning" %}
**หมายเหตุ**\
เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยใช้เกม Ragnarok เป็นการอธิบาย ซึ่งบางอย่างอาจจะไม่ตรงกับตัวเกมจริงๆนะขอรับ *Gravity อย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล* 😭

เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย [**Mr.Saladpuk**](https://www.facebook.com/mr.saladpuk) และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺
{% endhint %}

## 🧐 โจทย์

สมมุติว่าเรามี object ของตัวละครอยู่ 1 ตัวซึ่งมีข้อมูลคือ มีเลเวล 2, ใส่หมวกกวาง, อาชีพนักผจญภัยฝึกหัด, พลังชีวิต 20 และ พลังเวทมนต์ 12 ตามรูปด้านล่าง

![](/files/-M2tGHYlxZ-C7Pkm7rom)

แล้วด้วยเหตุผลอะไรก็ตามแต่ ทำให้เราต้องสร้าง object ที่มีข้อมูลแบบเดียวกันแป๊ะๆเลย เราจะทำยังงุยกันน๊า ?

![อยากได้ object ใหม่ที่มีข้อมูลเหมือนตัวเดิมเลยอ่าาาา](/files/-M2tHVvC_I3i-MwXv1Mx)

## 🧒 แก้โจทย์ครั้งที่ 1

หลายคนพอเห็นโจทย์แล้วก็ยิ้มหวานหมูเลย เพราะเราก็แค่ไปสร้าง object ใหม่ขึ้นมา โดยเอาข้อมูลจาก object เก่าไปยัดใส่ object ใหม่เท่านั้นเองก็เสร็จแล้วนิน่า ซึ่งสมมุติว่าตัวละครมันถูกสร้างมาจากคลาสประมาณนี้ละกันนะ

![](/files/-M2tJ64EdYYVBx45kjzu)

ดังนั้นโค้ดเราก็จะออกมาราวๆนี้ชิมิ ?

```csharp
Character object2 = new Character();
object2.Level = object1.Level;
object2.Hat = object1.Hat;
object2.Class = object1.Class;
object2.HP = object1.HP;
object2.SP = object1.SP;
```

> สมมุติว่า object ต้นฉบับของเราอยู่ในตัวแปรที่ชื่อว่า object1 นะ

จากโค้ดด้านบนก็ดูเหมือนว่าเราจะได้ผลลัพท์เป็นแบบด้านล่างละชิมิ?

![](/files/-M2tLo7O7T3NSmakzgQ_)

### 🐱‍🐉 ปัญหาที่ 1

อะเชคราวนี้เรา **ลองให้ object2 เปลี่ยนไปใส่หมวกแซนต้า** กับ **แก้พลังชีวิตเป็น 55** ดูดิ๊ ตามโค้ดด้านล่าง

```csharp
object2.Hat.Name = "Santa Hat";
object2.HP = 55;
```

ซึ่งผลลัพท์ที่เราจะได้ก็คือ

![หมวกของ object 1 ถูกเปลี่ยนด้วยเฉยเลย](/files/-M2tOXb47zUnRcgp8zgz)

โค้ดของเราแก้ไขข้อมูลให้กับ object2 เท่านั้น ไม่ได้ยุ่งอะไรกับ object1 เลย แต่การแก้ไขครั้งนี้ดันมีผลกับ object1 ด้วยในบางอย่าง เช่น หมวกโดนเปลี่ยนเหมือนกันทั้งคู่ แต่พลังชีวิตกลับเปลี่ยนแค่ object2 เท่านั้น

### 🐱‍🐉 ปัญหาที่ 2

แล้วถ้าคลาสของเรามันไม่ได้ง่ายแบบนั้นล่ะ เช่นมันมีพวก **`Private members`** อยู่ในนั้นด้วยล่ะ **เราจะเอาค่ามันมาใส่ใน object 2 ได้ยังไง?** ตามรูปด้านล่าง

![อุ๊ต๊ะมี private member ด้วย !](/files/-M2tT4Nsg-rfGtAkI_4t)

> จากรูปด้านบน **เราไม่มีทางเข้าถึงตัวแปร privateData เลย** ดังนั้นเราก็จะไม่มีทางกำหนดค่านี้ให้กับ object 2 ได้เลยเช่นกัน

### 🐱‍🐉 ปัญหาที่ 3

ถ้ากรณีที่เลวร้ายกว่านั้นคือ **เราไม่รู้ว่า object ที่เราได้มามันถูกสร้างมาจาก class อะไรล่ะ** เช่น เรามี object 1 โดยมันอยู่ในรูปของ interface ตามโค้ดด้านล่าง

```csharp
public interface ICharacter { ... }

ICharacter object1;
ICharacter object2 = new ???
```

> จากโค้ดด้านบนมันจะทำให้เราไม่สามารถสร้าง object2 ได้เลย เพราะเราไม่รู้ว่าจะไป new class อะไรนั่นเอง

### 🐱‍🐉 ปัญหาที่ 4

ถ้า Object 1 ของเรามันไปใช้คลาสอื่นๆอีกเต็มไปหมดเลย ตามรูปด้านล่าง แล้วเราจะต้องไปค่อยๆสร้างข้อมูลแต่ละตัวให้กับ Object 2 แล้วล่ะก็ นั่นหมายความว่าเรา **ทุกครั้งที่เราจะก๊อปปี้ object 1 มันจะเกิด Dependency วุ่นวายเต็มแถวๆนั้นไปหมดเลย** อ่ะดิ

![ใช้คลาสอื่นๆเต็มไปหมดเบย](/files/-M2tcr3qhCESp4A58D_g)

> จะสร้าง Object 2 ได้ต้องไป import เจ้าพวกคลาส Equipment, Inventory, Skill, Status มาก่อนไม่งั้นสร้าง object พวกนั้นไม่ได้

นี่มันเกิดอะไรกันขึ้นนะ? ปัญหามันจะเยอะไปหน๋ายยย แล้วเราจะแก้ปัญหาพวกนี้ยังไงดีอ๊าาาาา 😭

## 🧒 แก้โจทย์ครั้งที่ 2

### 🔥 วิเคราะห์ปัญหา

**(จากปัญหาที่ 1)** เรื่องนี้ไม่ได้เกี่ยวข้องโดยตรงกับ Design Pattern ตัวนี้เท่าไหร่ แต่มันเป็นเรื่องพื้นฐานในการเขียนโปรแกรม นั่นคือเรื่องของการใช้ **`Value type`** กับ **`Reference type`** นั่นเอง **ซึ่งมันทำให้เกิดปัญหาหมวกเปลี่ยนทั้ง 2 objects โดยที่เราไม่ตั้งใจ** ส่วนวิธีการแก้ไขเจ้าเรื่องนี้เราต้อง **เข้าใจว่า Value type ต่างกับ Reference type ยังไงเสียก่อน** ถึงจะสามารถแก้ปัญหาตัวนี้และตัวถัดไปได้ขอรับ

{% hint style="success" %}
**แนะนำให้อ่าน**\
สำหรับใครที่อยากศึกษาต่อว่า **Value type** กับ **Reference type** คืออะไร สามารถไปทำความเข้าใจได้กับบทความตัวนี้ขอรับ [**Value type vs Reference type**](https://www.saladpuk.com/beginner-1/csharp101/intermediate/value-vs-reference) มันเป็นเรื่องพื้นฐานสุดๆของการเขียนโปรแกรม ดังนั้นขอไม่ลงรายละเอียดตัวนี้ต่อนะ
{% endhint %}

**(จากปัญหาที่ 2)** ตัวข้อมูลที่เราเข้าถึงไม่ได้ เช่น protected, private, internal เราสามารถแก้ให้เข้าถึงด้วยช่องทางอื่นๆก็ได้ แต่ไม่ใช่เรื่องที่ดี เพราะมันจะเสียคุณสมบัติ **Encapsulation** และความปลอดภัยไป (ดังนั้นไม่ขอพูดถึงวิธีนี้) ซึ่งตัวที่มีสิทธิ์เข้าถึงข้อมูลพวกนั้นได้จริงๆก็คือ **ตัวคลาสมันเองนั่นแหละ** ถึงจะเหมาะสมสุด

{% hint style="success" %}
**แนะนำให้อ่าน**\
อะไรนะลืมการทำ encapsulation ไปแล้วรึ งั้นไปทบทวนได้จากลิงค์นี้เบย [💖 **Encapsulation**](https://www.saladpuk.com/beginner-1/oop/encapsulation)
{% endhint %}

**(จากปัญหาที่ 3)** เราไม่มีทางรู้เลยว่า class ที่แท้จริงของมันคืออะไร ถ้าเราไม่นั่ง debug หรือไล่ไปดู source code ซึ่งการไล่หา class ที่แท้จริงของมันไม่ใช่เรื่องที่ดีนัก เพราะหลักในการออกแบบจริงๆเรา **ไม่ควรทำงานกับ Concrete class** ยังไงล่ะ

{% hint style="success" %}
**แนะนำให้อ่าน**\
หลักในการออกแบบขั้นพื้นฐานคือการ **ทำงานกับ Abstraction** นั่นเอง ซึ่งถ้าอยากศึกษาว่าทำไมต้องเป็นแบบนั้นก็ไปอ่านได้จากลิงค์นี้ [**Creational Pattern**](https://www.saladpuk.com/beginner-1/design-patterns/creational) ส่วนถ้าอยากเข้าใจลึกๆถึงแก่นจริงๆลองศึกษาได้จากบทความนี้ [**Dependency-Inversion Principle**](https://www.saladpuk.com/basic/solid/dip)
{% endhint %}

### 🔥 แก้ไขปัญหา

จากปัญหาต่างๆที่ว่ามาถ้าวิเคราะห์ดีๆจะพบว่ามันเกิดจาก **เราพยายามจะไปสร้าง object เอง** ยังไงล่ะ ซึ่งของบางอย่างเราก็ไม่มีสิทธิ์จะเข้าถึงได้ หรือ เราก็ไม่รู้ด้วยซ้ำว่าจริงๆแล้วมันคือตัวอะไร ดังนั้น**แทนที่เราจะไปสร้าง object ขึ้นมาเอง เราก็โยนภาระให้กับ object ที่มีสิทธิ์เข้าถึงตัวแปรต่างๆได้ และ มันรู้ว่ามันเกิดจากคลาสอะไรด้วยนั่นเอง** ซึ่งในที่นี้ก็คือ **ตัวมันเอง** นั่นแหละ

ดังนั้นเราก็จะเพิ่มเมธอดในการก๊อปปี้ให้กับคลาส Character เข้าไป 1 ตัวนั่นก็คือ **`Clone`** นั่นเองตามรูป

![](/files/-M2tfggAEHHy5Z9ygmqi)

เพราะคลาส Character สามารถเข้าถึง private members ได้ทุกตัวอยู่แล้วเลยไม่มีปัญหาที่จะเข้าถึงตัวแปร **privateData** ยังไงล่ะ ตามโค้ดด้านล่างเบย

```csharp
public class Character
{
    public int HP;
    public int SP;
    public int Level;
    public string Class;
    private int privateData;

    public Character Clone()
    {
        return new Character
        {
            HP = HP,
            SP = SP,
            Level = Level,
            Class = Class,
            privateData = privateData
        };
    }
}
```

> เพียงแค่นี้ปัญหาข้อ 2 ก็ถูกแก้ไขไปเรียบร้อยแล้ว (ส่วนข้อ 1 ก็ทำโคลนออกมาเหมือนกัน หรือจะสร้าง object ใหม่ก็แล้วแต่ความยากง่ายของมัน)

หรือต่อให้มันเป็น interface ตามรูปด้านล่าง (ที่เป็นปัญหาในข้อ 3) เจ้าเมธอด Clone ก็ทำงานได้เช่นเคยนะจ๊ะ

![](/files/-M2thePCoKFCZl1Kvx_v)

จากที่ทำมาทั้งหมด ปัญหาข้อ 4 ก็จะหายไปเอง เพราะเวลาใครอยากจะก๊อปปี้ object ก็เรียกใช้งานเมธอด Clone เท่านั้นเอง ไม่ทำให้เกิด Dependency ไปเลอะที่อื่นแล้วตามโค้ดด้านล่างเบย

```csharp
ICharacter object1;
ICharacter object2 = object1.Clone();
```

จากโค้ดด้านบน ไหนลองเอา object2 มาเปลี่ยนเป็นหมวกแซนต้าแบบเดิมดูดิ๊

```csharp
object2.Hat.Name = "Santa Hat";
```

ซึ่งผลลัพท์ที่ได้ก็จะทำให้ ตัวละครเป็น object คนละตัวกันอย่างแท้จริง เพียงแต่ข้อมูลมันถูกก๊อปปี้มาจาก object1 เท่านั้นเอง ตามรูปด้านล่าง

![](/files/-M2w6T0EXJITNkNPvcql)

### 🤔 แล้วถ้ามันเป็นคลาสลูกล่ะ ?

จากที่ทำมาทั้งหมดเราจะสามารถก๊อปปี้ object จากคลาส Character ได้แล้ว แต่ถ้าเกิดว่ามีคลาสอื่นมา inheritance จากคลาสนี้ไปตามรูปด้านล่างล่ะ ซึ่งคลาสเหล่านั้นอาจจะมี members หรือ private members ที่ต่างจากคลาสแม่ก็เป็นได้นะ

![](/files/-M2tkE9CRAunKYE57dtU)

เพื่อการออกแบบให้ยืดหยุ่นรองรับกรณีคลาสลูกด้วย ดังนั้นเราก็จะทำให้เจ้าเมธอด Clone มันเป็นกลางๆนั่นเอง เพื่อให้คลาสอื่นๆสามารถเอาไปปรับแก้ในรูปแบบของตัวเองต่อได้ง่ายๆ โดยการเปลี่ยนให้มันส่งค่า object กลับไป และให้คลาสลูกสามารถทำ override จากคลาสแม่ต่อได้

![](/files/-M2tledqDKvxS0hBy1RJ)

ดังนั้นเวลาที่เราอยากจะให้คลาสมันมีความสามารถในการก๊อปปี้ได้ เราสามารถเลือกทำได้หลายวิธีเช่นทำเป็น abstract class ไว้ เพื่อให้ concrete class เป็นคนจัดการต่อ

![ทำเป็น abstract class ซะ](/files/-M2vZqJsXFkRzpXsg5xy)

หรือเราจะสร้าง interface สำหรับทำก๊อปปี้ที่เป็นกลางๆเอาไว้ แล้วให้ concrete class ไปจัดการก็ได้เช่นกัน

![ทำเป็น interface กลางๆ ใช้ได้ทุกสถานะการณ์](/files/-M2vh8euZDYW2WWH5Ghd)

## 🤔 Prototype คือไย ?

### 🔥 จุดกำเหนิด

ในบางทีการก๊อปปี้ object ก็ไม่ใช่เรื่องง่าย เพราะบางทีเราก็ไม่รู้เหมือนกันว่าเราจะต้องสร้างมันจากคลาสไหน หรือบางทีเราก็ไม่สามารถเข้าไปกำหนดค่า private members ของมันได้ แถมไหนจะเรื่อง dependency ที่จะตามมาอีก

### 🔥 ผลจากการใช้

เราสามารถก๊อปปี้ object ที่มีค่าด้านในเหมือนกับต้นฉบับได้แบบไม่ผิดเพี้ยน เข้าถึงไส้ในมันได้ทะลุถึงไส้ถึงพุง แถมยังไม่ต้องมากังวลในเรื่องของ concrete class ที่จะสร้าง และ dependency ต่างๆที่จะตามมาด้วย โดยการเรียกผ่าน method เดียวเท่านั้นเอง

### 🔥 วิธีการใช้

ตรงจุดนี้จะขออธิบายออกเป็นทีละขั้นตอนแบบนี้ละกัน คนที่พึ่งหัดออกแบบจะได้เข้าใจได้ง่ายๆนะ ซึ่งในรอบนี้จะอธิบายในการใช้ interface ดูบ้างนะ เพราะเห็นตัวอย่างการทำแบบ abstraction ไปแล้ว

ถ้าเราอยากจะให้คลาสต่างๆมีความสามารถในการก๊อปปี้ได้ล่ะก็ เราก็จะ**สร้าง interface กลาง**ๆขึ้นมา 1 ตัว ที่เอาไว้ใช้ในการก๊อปปี้อะไรก็ได้ ตามรูปด้านล่าง

![](/files/-M2vh8euZDYW2WWH5Ghd)

ส่วนคลาสไหนอย่าจะให้มันมีความสามารถในการก๊อปปี้ เราก็แค่ implement interface ตัวนั้นซะ ตามรูปด้านล่าง

![](/files/-M2w7C63Rap6qnQ4hlim)

ส่วนถ้ามันมีคลาสลูก ก็ให้คลาสลูกทำการ override ไปจัดการในส่วนที่มันต่างกับของคลาสแม่ซะ ตามรูปด้านล่าง

![](/files/-M2w7V3YwyDlaPo8C9qC)

จากที่ร่ายยาวมาทั้งหมดเราก็จะได้แนวทางในการออกแบบที่ชื่อว่า **Prototype Pattern** ออกมา ซึ่งมีหน้าตาประมาณรูปด้านล่างนี้นั่นเองครัช

![](/files/-M2w7yaFArMfN3glrZoM)

ไหนลองเอาที่เราออกแบบมาเทียบกันดูดิ๊ ... เหมือนกันเปี๊ยบเบย ต่างกันแค่ในตัวอย่างนี้ไม่ได้ทำเป็น interface เฉยๆ ซึ่งจะทำก็ได้ไม่ทำก็ได้แบ๊วแต่เรย

![](/files/-M2w8eCo4jn9DyTuwoRw)

## 🤔 ทำไมต้องใช้ด้วย ?

จากปัญหาต่างๆที่ว่าไว้ในตอนแรก เราจะเห็นถึงความวุ่นวายในการสร้าง object แล้วชิมิ ดังนั้นถ้าเรานำ Prototype Pattern ไปใช้ มันจะ **ช่วยลดความซับซ้อนในการสร้างและก๊อปปี้ข้อมูล** จาก object A ไป object B ได้ด้วย เพราะความซับซ้อนทั้งหมดมันถูกซ่อนไว้ภายในเมธอด Clone แล้วยังไงล่ะ

## 🎯 บทสรุป

### 👍 ข้อดี

การนำ **Prototype Pattern** มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง เพราะเราไม่ต้องไปวุ่นวายกับ Concrete class และ Dependency ต่างๆของมันอีกแล้ว แถมยังใช้งานได้ง่ายอีกด้วย

### 👎 ข้อเสีย

**เพิ่มความซับซ้อนโดยไม่จำเป็น** ถ้า object มันสามารถ copy ค่าได้ง่ายๆอยู่แล้วจะ new object ใหม่ธรรมดาอาจจะง่ายกว่า และถ้า **object มีการอ้างกันแบบงูกินหาง (Circular references) จะทำได้ยาก** และอาจเกิดปัญหาตามมาภายหลัง

{% hint style="success" %}
**เกร็ดความรู้**\
**Circular references** คือการที่ object A ไปอ้างถึง object B และเจ้า object B ก็ดันมีการอ้างกลับมาที่ object A เช่นกัน ตามโค้ดด้านล่างนี้

```csharp
var a = new ObjectA();
var b = new ObjectB();
a.Ref = b;
b.Ref = a;
```

{% endhint %}

### 🤙 ทางเลือก

โดยปรกติเรื่องการก๊อปปี้ object นั้นเป็นปัญหามานานแล้ว ดังนั้นในแต่ละภาษาเขาจะมีวิธีการจัดการของเขาเองอยู่แล้ว เช่นของภาษา C# ก็จะมี interface ที่ชื่อว่า [**`ICloneable`**](https://docs.microsoft.com/en-us/dotnet/api/system.icloneable?view=netcore-3.1) อยู่แล้วเราไม่ต้องไปสร้างเอง หรือพวกเมธอด [**`MemberwiseClone`**](https://docs.microsoft.com/en-us/dotnet/api/system.object.memberwiseclone?view=netcore-3.1) ดังนั้นจงไปศึกษามาตรฐานของภาษาที่ตัวเองใช้ให้ดีก่อนนะขอรับ

{% hint style="danger" %}
**ข้อควรระวัง**\
**อย่านำ Prototype Pattern ไปใช้มั่วซั่ว** เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะใช้คำสั่ง **new** แบบปรกติ ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด
{% endhint %}

{% hint style="success" %}
เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย [**Mr.Saladpuk**](https://www.facebook.com/mr.saladpuk) และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺
{% endhint %}
