> 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/oop/oop-n-design.md).

# OOP + Power of Design

ผมเชื่อว่าหลายๆคนน่าจะได้เห็นตัวอย่างการนำ OOP ไปใช้งานไปใช้ในบทก่อนหน้าแล้วล่ะ และก็อาจจะคิดว่านั่นคือ **การนำ OOP มาใช้งานได้อย่างมีประสิทธิภาพแล้วชิมิ?** ... เสียใจด้วยเพราะนั่นคือ **หลุมพรางในการนำ OOP ไปใช้ต่างหาก** แต่ก็ไม่ใช่เรื่องแปลกถ้าเราจะเข้าใจผิด เพราะมันเป็นเรื่องที่คนส่วนใหญ่เข้าใจผิดอันดับต้นๆการนำ OO ไปใช้เลยก็ว่าได้ ดังนั้นในบทความนี้เราจะมาแก้ไขข้อผิดพลาดพวกนั้นกัน โดยนำหลัก **การออกแบบ** เข้ามาช่วยเพื่อให้เราเข้าถึงแก่นแท้มันกันครัช

{% hint style="danger" %}
**แนะนำให้อ่านก่อนอ่านบทนี้**\
สำหรับใครยังไม่ได้อ่านบทความก่อนหน้าให้รีบไปอ่านด่วนจะได้เข้าใจบทความนี้มากยิ่งขึ้น ซึ่งสามารถไปอ่านได้จากลิงค์นี้เบย [📝 ลองเขียน OOP ดูดิ๊](https://saladpuk.gitbook.io/learn/beginner-1/oop/example)
{% endhint %}

{% hint style="info" %}
**ขอขอบคุณ**\
ตอนแรกว่าจะไม่ได้เขียนบทความนี้แล้ว แต่ได้รับแรงบันดาลใจจากเพื่อนๆชาว developer เพราะหลายๆท่านกลัวว่าเพื่อนๆที่อ่านบทความนั้นแล้วจะเอาไปใช้งานจริงๆเลยนั่นเอง และผมก็เขียนเพลินจนลืมนึกถึงคนที่พึ่งศึกษา OOP ใหม่ๆด้วย ดังนั้นบทความนี้เลยถือกำเหนิดขึ้นเป็นภาคจบที่สมบูรณ์(มั๊ง) ของคอร์ส Object-Oriented Programming นี้ครับ

🙏 ขอขอบคุณท่าน **Bee Yodrak** และเพื่อนๆใน **Programmer Thai Blood** ด้วยนะครับที่ช่วยชี้แนะในจุดที่ผมลืม หรือ มองข้ามไปมากครับ ❤️
{% endhint %}

## 🔥 เก็บตก

จากรอบก่อนมันมีเรื่องที่ลืมทำให้ดูอยู่ 2 อย่าง ดังนั้นเราจะมาทำโจทย์เพิ่มกันนิดนุงนะ

### 🧐 โจทย์ 05

เนื่องด้วยอะไรมาดลใจก็ไม่รู้ทำให้บริษัทคิดว่า ถ้าตัวละคร **นั่งพัก** จนครบ 10 วินาที จะทำให้ค่าพลังชีวิตของตัวละครเพิ่มขึ้นฟรีๆเลย ซึ่งแต่ละตัวละครจะแตกต่างกันตามนี้

* **นักดาป (Swordman)** - เมื่อนั่งจนครบเวลาพลังชีวิตจะเพิ่มขึ้น 20 หน่วย
* **พระ (Acolyte)** - เมื่อนั่งจนครับเวลา พลังชีวิตจะเพิ่มขึ้น 11 หน่วย
* **เด็กฝึกหัด (Novice)** - ไม่ว่าจะนั่งแค่ไหนก็ตาม พลังชีวิตก็จะไม่เพิ่มเด็ดขาด

แล้วเราจะออกแบบมันยังไงดี ?

### 🧒 แก้โจทย์

ก่อนที่จะไปออกแบบเรากลับมาดูว่าตัวอย่างที่แล้ว Models เราเป็นยังไงบ้างกันก่อน

![](/files/-Lr30V7GhAhJ01XBeyyg)

ซึ่งจากรูปจะเห็นว่าใน **Character** นั้นมีเรื่อง **การนั่ง** หรือเมธอด **Sit** อยู่แล้ว ซึ่งปรกติการนั่งมันจะไม่ได้เพิ่มพลังชีวิตอะไรอยู่แล้ว ดังนั้นคลาส **Novice** ไม่ต้องทำอะไรก็ได้ แต่พวกคลาส **Swordman** กับ **Acolyte** เราต้องการให้มัน **ทำงานต่างจากเดิม** ตามรูป

![](/files/-LpU07IumT5UHc7JLgWF)

ซึ่งในกรณีนี้เราสามารถทำได้เลย โดยการไปแก้ไขการทำงานของเมธอด Sit ในคลาสลูก ที่เราอยากให้มันทำงานต่างจากเดิมนั่นเอง ซึ่งในภาษา C# ถ้าเราอยากจะให้คลาสลูกมีการทำงานที่ต่างจากคลาสแม่ได้เราจะใช้ **virtual** keyword กำกับไว้นั่นเอง ดังนั้นไปจัดกันเบย

```csharp
public class Character
{
    public virtual void Sit()
    {
        Console.WriteLine("Sit");
    }
}
```

ส่วนคลาสลูกที่ต้องการทำงานต่างจากเดิมก็ไปทำสิ่งที่เรียกว่า **override** การทำงานของคลาสแม่นั่นเอง ตามนี้

```csharp
public class Swordman : Character
{
    public override void Sit()
    {
        base.Sit(); // ไปเรียก Sit ของคลาสแม่
        Console.WriteLine("+20 HP");
    }
}

public class Acolyte : Character
{
    public override void Sit()
    {
        // ไม่อยากใช้ Sit ของแม่ ก็สร้างของตัวเองใหม่ก็ได้
        Console.WriteLine("Sit");
        Console.WriteLine("+20 HP");
    }
}
```

จากโค้ดด้านบนเวลาที่เราไปทำงานเราก็จะยังสามารถใช้ **Polymorphism** ได้ตามปรกติ แถมตอนที่เรียกใช้งานเมธอด Sit มันก็จะไปทำงานกับ data type ที่แท้จริงของมันอีกด้วย ตามรูปเบย

![](/files/-LngiL9paLHTQmzXLHff)

### 🧐 โจทย์ 06

บ่อยครั้งที่ทีมพัฒนาเกมด้วยกันเองหลงไปสร้าง object จากคลาส **Character** ขึ้นมา ซึ่งมันไม่ใช่ Novice, Swordman และ Acolyte ใดๆทั้งสิ้นเลย ซึ่งคนในทีมไม่อยากให้เจ้าคลาสนั้นมันถูกเอาไปสร้าง object ได้ เราจะแก้ไงดี ?

### 🧒 แก้โจทย์

การก็แค่เปลี่ยนเจ้าคลาสนั้นให้กลายเป็นสิ่งที่เรียกว่า **abstract class** ซะซิ หรือพูดง่ายๆคือเรามองว่าเจ้าคลาส Character นั้นมันเป็นแค่ **concept** เท่านั้น ไม่สามารถเอาไปใช้งานได้จริงๆยังไงล่ะ (อ่านเรื่อง abstract class ต่อได้จากบทความนี้ [Abstract Class](https://saladpuk.gitbook.io/learn/beginner-1/csharp101/intermediate/abstract-class)) ดังนั้นเราก็จะได้โค้ดออกมาเป็นแบบนี้ขอรับ

```csharp
public abstract class Character
{
    ...
}
```

## 🔥 หลุมพรางแห่งการออกแบบ

หลังจากเจอโจทย์เข้าไป 6 ข้อ เราก็จะได้คลาสที่เป็น OOP ออกมาทำงานได้เรียบร้อยละ แต่อย่างที่เกริ่นไปว่า ทั้งหมดที่ทำให้ดูมันเป็น **หลุมพราง** ที่คนส่วนใหญ่จะเข้าใจและใช้กันผิดบ่อยมากในการเขียน OOP นั่นเอง ... หักมุมไหมละ? ผมเชื่อว่าหลายๆคนก็อาจจะยัง งงๆ ด้วยว่า **มันผิดยังไง?** ตูก็ออกแบบอย่างนี้เหมือนกันนะ บลาๆ ดังนั้นตรงจุดนี้ขอเฉลยก่อนเลยว่า **"มันผิดในแง่ของเหตุผลที่เราใช้ Inheritance นั่นเอง"** เพราะหัวใจหลักของ Inheritance คือการมองความสัมพันธ์ในสิ่งที่เรียกว่า **"IS A"** นั่นเอง

{% hint style="success" %}
**ความสัมพันธ์แบบ "IS A"**\
เป็นมุมมองในการมองความสัมพันธ์ของ Model ว่า **มันเป็นหนึ่งในตระกูลนั้นหรือเปล่า** และ **มันจะต้องเป็น type นั้นตั้งแต่เกิดจนตาย** นั่นเอง
{% endhint %}

ถ้าอ่านถึงตรงนี้ก็ยัง งงๆ อยู่ก็ไม่เป็นไร ลองดูตัวอย่างกันก่อนละกันว่ามันควรจะออกแบบยังไงกันดีกว่า

### 😱 หลุมพรางข้อที่ 1

ในจุดนี้เราลองเอา Models ทั้งหมดมากางออกให้ชัดๆกันก่อน

![](/files/-LpxAcKMqILdfiyy70Bz)

ซึ่งเราจะเห็นว่าคลาส Novice มันสืบทอดมาจาก Character แต่ว่ามันไม่ได้มีอะไรต่างจาก Character เลย!! ดังนั้นนี่คือ **การทำ Inheritance ที่ไม่เหมาะสม** เพราะผมก็สามารถนำ Character ไปสร้าง Novice ได้เหมือนกันยังไงล่ะ!!

### 😱 หลุมพรางข้อที่ 2

จำกันได้ไหมว่าตัวละครแต่ละตัวมันมีความสามารถที่ไม่เหมือนกันนั่นคือ

* **นักดาป (Swordman)** - มีท่าโจมตีพิเศษ
* **พระ (Acolyte)** - สามารถรักษาตัวเองและเพื่อนๆได้

แต่ลองคิดดูนะว่าถ้าเราเอา **นักดาป** หรือ **พระ** เปลี่ยนรูป (Polymorphism) ไปเป็น Character ตามโค้ดด้านล่างแล้วล่ะก็ เราก็จะไม่สามารถเรียกเมธอด **SuperAttack** หรือ **Heal** ได้อีกเลย นอกจากเราจะทำการ cast มันกลับมา

```csharp
Character character1 = new Swordman();
character1.SuperAttack();   // error เพราะคลาสแม่ไม่รู้จัก

Character character2 = new Acolyte();
character2.Heal();          // error เพราะคลาสแม่ไม่รู้จัก
```

### 😱 หลุมพรางข้อที่ 3

จากที่ออกแบบมา ถ้าเราอยาก **เพิ่มความความสามารถอื่นๆเข้าไปล่ะ** เช่น นักดาปมีท่าโจมตีพิเศษแบบที่ 2 ล่ะ? หรือ พระสามารถเพิ่มความพลังโจมตีให้เพื่อนๆได้ล่ะ? เราจะออกแบบยังไงดีไม่ให้เราต้องไปแก้คลาสเดิมที่เรามีอยู่ ?

![](/files/-Lmz5J0JvBqKNVFrCPb6)

## 🔥 พลังแห่งการออกแบบที่แท้จริง

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

![](/files/-Lwn8prckI_c7mbucteX)

ดังนั้นที่ผมจะทำก่อนก็คือสร้าง Model ที่ชื่อว่า **Character** สำหรับ **โซนสีขาว** เพราะมันเหมือนกัน เลยสามารถใช้ Model ร่วมกันได้หมดนั่นเอง เลยได้ผลลัพท์ออกมาเป็นแบบรูปด้านล่าง

![](/files/-LuseJVgXRVpEo0VCirk)

ดังนั้นคำถามถัดไปคือ แล้วเจ้าเหลืองๆที่ไม่เหมือนเพื่อนพวกนั้นคืออะไร? ... ในมุมมองของผมมันคือ **ความสามารถ** หรือ **Skill** นั่นเอง ซึ่งผมมองว่าของพวกนี้มันไม่ได้มีแค่นี้หรอกมันจะ **มาเพิ่มขึ้นเรื่อยๆ** แน่นอน

![](/files/-LsoSOGtdJHYbEQzBfHL)

และในบางที Skill เดียวกัน อาจจะเอาไปใช้กับหลายตัวละครก็ได้เหมือนกันนะ เช่น

* **นักดาป** กับ **อัศวิน** - ใช้ Bash และ Magnum Break ได้
* **พระ** กับ **นักบวช** - ใช้ Heal และ Divine Protection ได้
* **Crusader** - **ใช้ได้หมดเลย** (Bash, Magnum Break, Heal และ Divine Protection)

![](/files/-Lv0Gn8eK1-ZzzcAUYkD)

ดังนั้นเมื่อเรามองแล้วจริงๆความสามารถหรือเจ้า Skill มันเป็น **ของคนละประเภทกัน** กับตัวละคร เพราะมันจับคู่กับตัวละครได้เยอะมาก ซึ่งลักษณะความสัมพันธ์แบบนี้เราเรียกว่า **"HAS A"**

### **ความสัมพันธ์แบบ HAS A**

ลักษณะความสัมแบบ **"HAS A"** มันจะอยู่ในรูปของการ **ถือครอง** เช่น

* นักดาป **มี** Bash และ Magnum Break
* พระ **มี** Divine Protection

ดังนั้นในการออกแบบของที่เป็น **"HAS A"** เราจะใช้ **Composition** หรือไม่ก็ **Aggregation** นั่นเอง (ไม่รู้เรื่องช่างมันอ่านต่อไปเรย) ดังนั้นสิ่งแรกที่เราต้องทำคือใช้ Abstraction แปลงเจ้า ความสามารถ หรือ Skill ให้กลายมาเป็น Model เสียก่อน ซึ่งผมก็จะได้ออกมาเป็นแบบนี้

```csharp
public class Skill
{
    public string Name { get; set; }
    public int EffectOnHP { get; set; }
    public int EffectOnAttack { get; set; }
    public bool IsRequiredTarget { get; set; }
}
```

และทำการเชื่อม Model ทั้งสองตัวเข้าด้วยกันด้วยความสัมพันธ์แบบ **HAS A** เลย ซึ่งตัวละครหนึ่งตัวสามารถมี Skill ได้หลายชนิด ทำให้ได้ผลลัพท์ตามรูป

![](/files/-LqvtsX_31mfyuZVCtUD)

จากรูปด้านบนเลยทำให้ตัวละครเรามีได้หลาย skill ขึ้นอยู่กับว่าเราจะยอมให้มันมี skill อะไรบ้างนั่นเอง

![](/files/-LvAFuw7RtDtLWiOVqED)

ดังนั้นเราก็จะได้โค้ดจากที่ออกแบบไว้เป็นตามนี้

```csharp
public abstract class Character
{
    public IEnumerable<Skill> Skills { get; set; }

    ...
}
```

> **หมายเหตุ**\
> IEnumerable ในภาษา C# ก็คือ Collection นั่นเอง

{% hint style="warning" %}
**คำเตือน**\
😤 Skills ยังไม่ได้ทำ Encapsulation นะ ทำให้ดูหลายครั้งแล้วลองไปหัดทำต่อเอาละกัน
{% endhint %}

### 🤔 แล้วจะเรียกใช้ Skill ยังไง?

วิธีการใช้ skill แบบก่อนที่จะแก้ให้เป็นโครงสร้างแบบนี้ มันมีการใช้ 2 แบบจำได้ไหม? นั่นก็คือ

* ใช้ได้เลยไม่ต้องมีเป้าหมาย
* ต้องเลือกเป้าหมายก่อนที่จะใช้ เช่น รักษาให้ตัวเอง หรือ รักษาให้เพื่อน

ดังนั้นถ้าเราจะใช้ skill ในรอบนี้ก็ทำเช่นเคยนั่นก็คือ เพิ่มเมธอด ให้กับคลาส Character งุยล่ะ ตามนี้เลย

```csharp
public abstract class Character
{
    public void Spell(Skill skill) { }
    public void Spell(Skill skill, Character target) { }

    ...
}
```

![](/files/-LpsMchkr1pquwlC2qVH)

เพียงเท่านี้เราก็จะ **สามารถรองรับอาชีพใหม่ๆ และ สกิลใหม่ๆ ในอนาคตแล้ว** เพียงแค่ใช้ 2 Model นี้เท่านั้นนั่นเอง

![](/files/-LnSE2OLbr8NGCvx46L7)

แถมไม่เพียงเท่านั้นจริงๆแล้วคลาส Character ของเรามันยังสามารถรองรับให้เราใส่พวก **ศตรู** แบบต่างๆเข้าไปได้ด้วยนะ

![](/files/-LuRiIfnWFhSgwvoaTpd)

### 🤔 แล้วใส่หมวกยังไงอ่ะ ?

เรื่องสุดท้ายละคือหมวกที่ยังทำไม่ได้ ซึ่งหมวกก็จะเป็นลักษณะของความสัมพันธ์แบบ **HAS A** เช่นเคย แต่ในรอบนี้เราจะไม่สามารถทำใส่ในคลาส Character ได้แล้ว เพราะว่าไรรู้ป่าวววววว? ... พวก **ศตรู** ทั้งหลายมันใส่หมวกไม่ได้เหมือนตัวผู้เล่นยังไงล๊าาาาาา ดังนั้นภาพด้านล่างเลยไม่ควรทำ ... แล้วเราจะทำไงดีหว่า ???

![](/files/-LnrJUWe-kZrIj2UqFhZ)

วิเคราะห์กันต่อนิสสส ตัวผู้เล่นกับตัวศตรูใช้คลาสเดียวกัน พวกศตรูมันใส่หมวกไม่ได้ แต่ตัวผู้เล่นใส่หมวกได้!! นั่นแสดงว่ามันคือตัวละครที่มีความสามารถพิเศษที่ไม่เหมือน Character แต่ยังคงเป็น Character นั่นเอง ดังนั้นนี่แหละจุดที่เราจะใช้ Inheritance เข้ามานั่นเองงงงงงง จัดเบย

![](/files/-LrKQWGpauYemBR1hpBO)

โอ้วววว เรียบร้อยงอดแงมตามท้องเรื่อง (จะได้ไปนอนซะที ฮ่าๆ) ซึ่งทั้งหมดที่ทำให้ดูนี้เราก็จะสามารถนำ Models ต่างๆไปประกอบกันจนสุดท้ายเราก็สร้าง **ตัวละค**ร และ **ศตรู** ที่มี **skill** หลายๆแบบแตกต่างกันได้หมดละ โดยที่โค้ดของเราสามารถเพิ่มสิ่งใหม่ๆเข้าไปได้โดยที่เราไม่ต้องไปแก้ไขโค้ดเดิมนั่นเอง ซึ่งตรงกับหลักในการออกแบบพื้นฐานที่ชื่อว่า [**Open & Close Principle**](https://saladpuk.gitbook.io/learn/basic/solid/ocp) นั่นเอง

## ❓ คำถามทิ้งท้าย

ก่อนจะจบบทเรียนตัวนี้ขอทิ้งท้ายคำถามไว้ให้คิดกันต่อนิดหน่อยละกัน จะได้ได้ลองรีดจักระเค้นเน็นออกมาใช้กันบ้าง

* ตัวละครของเรา และ พวกศตรูแต่ละตัวมันจะมี skill ติดตัวมาไม่เหมือนกัน แล้วเราจะออกแบบยังไงให้รองรับของพวกนั้นกันดีนะ ?
* ตอนที่ตัวละครของเรา Level Up เราจะได้โบนัสพิเศษทำให้พลังโจมตี หรือ พลังชีวิตเพิ่มขึ้นนิดหน่อยด้วย ซึ่งแต่ละตัวละครเมื่อ Level Up มันจะได้โบนัสไม่เท่ากัน แล้วเราจะออกแบบของพวกนี้ยังไงดีนะ ?

## 🎯 บทสรุป

จะเห็นว่าการนำ **OOP** มาใช้ควบคู่กับ **หลักในการออกแบบ** นั้นจริงๆมันทรงพลังมาก จากความวุ่นวายทั้งหมดที่เคยมีก็มลายหายไปเหลือเพียงแค่ Model ที่ **SIMPLE** แต่ดันรองรับการทำงานในอนาคตเพียบเลย ดังนั้นต่อให้มีอาชีพมาอีกเป็น 100ๆ หรือมีสกิลใหม่ๆเข้ามา ถ้าของพวกนั้นยังอยู่ในร่าง Model นี้ได้ เราก็ไม่ต้องไปแก้โค้ดเลยนั่นเอง

> ตัวอย่างผมตกเรื่องไหน อยากให้เสริมเรื่องไหน หรือ ไม่เห็นด้วย สามารถแนะนำติชมได้หมดครับ ผมน้อมรับเอาไปปรับปรุงเสมอ เจอกันได้ที่ [**Facebook Mr.Saladpuk**](https://www.facebook.com/mr.saladpuk) ครัช (จิ้มไปติดตามโลดจะได้ไม่พลาดอัพเดทใหม่ๆ)

{% hint style="success" %}
**แนะนำให้อ่าน**\
หลักในการออกแบบที่จะช่วยให้เราทำงานได้ง่ายขึ้น ในตอนที่เราเจอปัญหาคล้ายๆกับเคสตัวอย่างอันนี้นั่นคือ

* \*\*\*\*[**Strategy Pattern**](https://saladpuk.gitbook.io/learn/software-design/designpatterns/behavioral-patterns/strategy)\*\*\*\*
* \*\*\*\*[**Bridge Pattern**](https://saladpuk.gitbook.io/learn/software-design/designpatterns/structural-patterns/bridge)\*\*\*\*
* \*\*\*\*[**Visitor Pattern**](https://saladpuk.gitbook.io/learn/software-design/designpatterns/behavioral-patterns/visitor)\*\*\*\*

ซึ่งถ้าเพื่อนๆสนใจอยากเรียนรู้หลักในการออกแบบปัญหาที่เรามักจะเจอกันบ่อยๆในวงการซอฟต์แวร์แล้วล่ะก็ สามารถศึกษาได้จากลิงค์ตัวนี้เบยครัช

## [🤴 Design Patterns](https://saladpuk.gitbook.io/learn/software-design/designpatterns)

{% endhint %}

{% hint style="success" %}
**แนะนำให้อ่าน**\
หลักในการออกแบบขั้นพื้นฐานที่จะช่วยเป็นแนวทางในการออกแบบ Object-Oriented concept นั้นมีหลายตัว ซึ่งหนึ่งในนั้นมีชื่อว่า **SOLID Design Principles** ถ้าเพื่อนๆสนใจศึกษาเพิ่มเติมก็สามารถกดจากลิงค์ด้านล่างไปอ่านได้เบยครัช

## [👦 SOLID Design Principles](https://saladpuk.gitbook.io/learn/basic/solid)

{% endhint %}
